feed-the-machine 1.5.0 → 1.6.0
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/LICENSE +21 -21
- package/README.md +170 -170
- package/bin/generate-manifest.mjs +463 -463
- package/bin/install.mjs +491 -491
- package/docs/HOOKS.md +243 -243
- package/docs/INBOX.md +233 -233
- package/ftm/SKILL.md +122 -122
- package/ftm-audit/SKILL.md +623 -541
- package/ftm-audit/references/protocols/PROJECT-PATTERNS.md +91 -91
- package/ftm-audit/references/protocols/RUNTIME-WIRING.md +66 -66
- package/ftm-audit/references/protocols/WIRING-CONTRACTS.md +135 -135
- package/ftm-audit/references/strategies/AUTO-FIX-STRATEGIES.md +69 -69
- package/ftm-audit/references/templates/REPORT-FORMAT.md +96 -96
- package/ftm-audit/scripts/run-knip.sh +23 -23
- package/ftm-audit.yml +2 -2
- package/ftm-brainstorm/SKILL.md +498 -498
- package/ftm-brainstorm/evals/evals.json +100 -100
- package/ftm-brainstorm/evals/promptfoo.yaml +109 -109
- package/ftm-brainstorm/references/agent-prompts.md +224 -224
- package/ftm-brainstorm/references/plan-template.md +121 -121
- package/ftm-brainstorm.yml +2 -2
- package/ftm-browse/SKILL.md +454 -454
- package/ftm-browse/daemon/browser-manager.ts +206 -206
- package/ftm-browse/daemon/bun.lock +30 -30
- package/ftm-browse/daemon/cli.ts +347 -347
- package/ftm-browse/daemon/commands.ts +410 -410
- package/ftm-browse/daemon/main.ts +357 -357
- package/ftm-browse/daemon/package.json +17 -17
- package/ftm-browse/daemon/server.ts +189 -189
- package/ftm-browse/daemon/snapshot.ts +519 -519
- package/ftm-browse/daemon/tsconfig.json +22 -22
- package/ftm-browse.yml +4 -4
- package/ftm-capture/SKILL.md +370 -370
- package/ftm-capture.yml +4 -4
- package/ftm-codex-gate/SKILL.md +361 -361
- package/ftm-codex-gate.yml +2 -2
- package/ftm-config/SKILL.md +345 -345
- package/ftm-config.default.yml +82 -80
- package/ftm-config.yml +2 -2
- package/ftm-council/SKILL.md +416 -416
- package/ftm-council/references/prompts/CLAUDE-INVESTIGATION.md +60 -60
- package/ftm-council/references/prompts/CODEX-INVESTIGATION.md +58 -58
- package/ftm-council/references/prompts/GEMINI-INVESTIGATION.md +58 -58
- package/ftm-council/references/prompts/REBUTTAL-TEMPLATE.md +57 -57
- package/ftm-council/references/protocols/PREREQUISITES.md +47 -47
- package/ftm-council/references/protocols/STEP-0-FRAMING.md +46 -46
- package/ftm-council.yml +2 -2
- package/ftm-dashboard/SKILL.md +163 -163
- package/ftm-dashboard.yml +4 -4
- package/ftm-debug/SKILL.md +1037 -1037
- package/ftm-debug/references/phases/PHASE-0-INTAKE.md +58 -58
- package/ftm-debug/references/phases/PHASE-1-TRIAGE.md +46 -46
- package/ftm-debug/references/phases/PHASE-2-WAR-ROOM-AGENTS.md +279 -279
- package/ftm-debug/references/phases/PHASE-3-TO-6-EXECUTION.md +436 -436
- package/ftm-debug/references/protocols/BLACKBOARD.md +86 -86
- package/ftm-debug/references/protocols/EDGE-CASES.md +103 -103
- package/ftm-debug.yml +2 -2
- package/ftm-diagram/SKILL.md +277 -277
- package/ftm-diagram.yml +2 -2
- package/ftm-executor/SKILL.md +777 -767
- package/ftm-executor/references/STYLE-TEMPLATE.md +73 -73
- package/ftm-executor/references/phases/PHASE-0-VERIFICATION.md +62 -62
- package/ftm-executor/references/phases/PHASE-2-AGENT-ASSEMBLY.md +34 -34
- package/ftm-executor/references/phases/PHASE-3-WORKTREES.md +38 -38
- package/ftm-executor/references/phases/PHASE-4-5-AUDIT.md +72 -72
- package/ftm-executor/references/phases/PHASE-4-DISPATCH.md +66 -66
- package/ftm-executor/references/phases/PHASE-5-5-CODEX-GATE.md +73 -73
- package/ftm-executor/references/protocols/DOCUMENTATION-BOOTSTRAP.md +36 -36
- package/ftm-executor/references/protocols/MODEL-PROFILE.md +59 -44
- package/ftm-executor/references/protocols/PROGRESS-TRACKING.md +66 -66
- package/ftm-executor/runtime/ftm-runtime.mjs +252 -252
- package/ftm-executor/runtime/package.json +8 -8
- package/ftm-executor.yml +2 -2
- package/ftm-git/SKILL.md +441 -441
- package/ftm-git/evals/evals.json +26 -26
- package/ftm-git/evals/promptfoo.yaml +75 -75
- package/ftm-git/hooks/post-commit-experience.sh +92 -92
- package/ftm-git/references/patterns/SECRET-PATTERNS.md +104 -104
- package/ftm-git/references/protocols/REMEDIATION.md +139 -139
- package/ftm-git/scripts/pre-commit-secrets.sh +110 -110
- package/ftm-git.yml +2 -2
- package/ftm-inbox/backend/adapters/_retry.py +64 -64
- package/ftm-inbox/backend/adapters/base.py +230 -230
- package/ftm-inbox/backend/adapters/freshservice.py +104 -104
- package/ftm-inbox/backend/adapters/gmail.py +125 -125
- package/ftm-inbox/backend/adapters/jira.py +136 -136
- package/ftm-inbox/backend/adapters/registry.py +192 -192
- package/ftm-inbox/backend/adapters/slack.py +110 -110
- package/ftm-inbox/backend/db/connection.py +54 -54
- package/ftm-inbox/backend/db/schema.py +78 -78
- package/ftm-inbox/backend/executor/__init__.py +7 -7
- package/ftm-inbox/backend/executor/engine.py +149 -149
- package/ftm-inbox/backend/executor/step_runner.py +98 -98
- package/ftm-inbox/backend/main.py +103 -103
- package/ftm-inbox/backend/models/__init__.py +1 -1
- package/ftm-inbox/backend/models/unified_task.py +36 -36
- package/ftm-inbox/backend/planner/__init__.py +6 -6
- package/ftm-inbox/backend/planner/generator.py +127 -127
- package/ftm-inbox/backend/planner/schema.py +34 -34
- package/ftm-inbox/backend/requirements.txt +5 -5
- package/ftm-inbox/backend/routes/execute.py +186 -186
- package/ftm-inbox/backend/routes/health.py +52 -52
- package/ftm-inbox/backend/routes/inbox.py +68 -68
- package/ftm-inbox/backend/routes/plan.py +271 -271
- package/ftm-inbox/bin/launchagent.mjs +91 -91
- package/ftm-inbox/bin/setup.mjs +188 -188
- package/ftm-inbox/bin/start.sh +10 -10
- package/ftm-inbox/bin/status.sh +17 -17
- package/ftm-inbox/bin/stop.sh +8 -8
- package/ftm-inbox/config.example.yml +55 -55
- package/ftm-inbox/package-lock.json +2898 -2898
- package/ftm-inbox/package.json +26 -26
- package/ftm-inbox/postcss.config.js +6 -6
- package/ftm-inbox/src/app.css +199 -199
- package/ftm-inbox/src/app.html +18 -18
- package/ftm-inbox/src/lib/api.ts +166 -166
- package/ftm-inbox/src/lib/components/ExecutionLog.svelte +81 -81
- package/ftm-inbox/src/lib/components/InboxFeed.svelte +143 -143
- package/ftm-inbox/src/lib/components/PlanStep.svelte +271 -271
- package/ftm-inbox/src/lib/components/PlanView.svelte +206 -206
- package/ftm-inbox/src/lib/components/StreamPanel.svelte +99 -99
- package/ftm-inbox/src/lib/components/TaskCard.svelte +190 -190
- package/ftm-inbox/src/lib/components/ui/EmptyState.svelte +63 -63
- package/ftm-inbox/src/lib/components/ui/KawaiiCard.svelte +86 -86
- package/ftm-inbox/src/lib/components/ui/PillButton.svelte +106 -106
- package/ftm-inbox/src/lib/components/ui/StatusBadge.svelte +67 -67
- package/ftm-inbox/src/lib/components/ui/StreamDrawer.svelte +149 -149
- package/ftm-inbox/src/lib/components/ui/ThemeToggle.svelte +80 -80
- package/ftm-inbox/src/lib/theme.ts +47 -47
- package/ftm-inbox/src/routes/+layout.svelte +76 -76
- package/ftm-inbox/src/routes/+page.svelte +401 -401
- package/ftm-inbox/svelte.config.js +12 -12
- package/ftm-inbox/tailwind.config.ts +63 -63
- package/ftm-inbox/tsconfig.json +13 -13
- package/ftm-inbox/vite.config.ts +6 -6
- package/ftm-intent/SKILL.md +241 -241
- package/ftm-intent.yml +2 -2
- package/ftm-manifest.json +3794 -3794
- package/ftm-map/SKILL.md +291 -291
- package/ftm-map/scripts/db.py +712 -712
- package/ftm-map/scripts/index.py +415 -415
- package/ftm-map/scripts/parser.py +224 -224
- package/ftm-map/scripts/queries/go-tags.scm +20 -20
- package/ftm-map/scripts/queries/javascript-tags.scm +35 -35
- package/ftm-map/scripts/queries/python-tags.scm +31 -31
- package/ftm-map/scripts/queries/ruby-tags.scm +19 -19
- package/ftm-map/scripts/queries/rust-tags.scm +37 -37
- package/ftm-map/scripts/queries/typescript-tags.scm +41 -41
- package/ftm-map/scripts/query.py +301 -301
- package/ftm-map/scripts/ranker.py +377 -377
- package/ftm-map/scripts/requirements.txt +5 -5
- package/ftm-map/scripts/setup-hooks.sh +27 -27
- package/ftm-map/scripts/setup.sh +56 -56
- package/ftm-map/scripts/test_db.py +364 -364
- package/ftm-map/scripts/test_parser.py +174 -174
- package/ftm-map/scripts/test_query.py +183 -183
- package/ftm-map/scripts/test_ranker.py +199 -199
- package/ftm-map/scripts/views.py +591 -591
- package/ftm-map.yml +2 -2
- package/ftm-mind/SKILL.md +1943 -1943
- package/ftm-mind/evals/promptfoo.yaml +142 -142
- package/ftm-mind/references/blackboard-schema.md +328 -328
- package/ftm-mind/references/complexity-guide.md +110 -110
- package/ftm-mind/references/event-registry.md +319 -319
- package/ftm-mind/references/mcp-inventory.md +296 -296
- package/ftm-mind/references/protocols/COMPLEXITY-SIZING.md +72 -72
- package/ftm-mind/references/protocols/MCP-HEURISTICS.md +32 -32
- package/ftm-mind/references/protocols/PLAN-APPROVAL.md +80 -80
- package/ftm-mind/references/reflexion-protocol.md +249 -249
- package/ftm-mind/references/routing/SCENARIOS.md +22 -22
- package/ftm-mind/references/routing-scenarios.md +35 -35
- package/ftm-mind.yml +2 -2
- package/ftm-pause/SKILL.md +395 -395
- package/ftm-pause/references/protocols/SKILL-RESTORE-PROTOCOLS.md +186 -186
- package/ftm-pause/references/protocols/VALIDATION.md +80 -80
- package/ftm-pause.yml +2 -2
- package/ftm-researcher/SKILL.md +275 -275
- package/ftm-researcher/evals/agent-diversity.yaml +17 -17
- package/ftm-researcher/evals/synthesis-quality.yaml +12 -12
- package/ftm-researcher/evals/trigger-accuracy.yaml +39 -39
- package/ftm-researcher/references/adaptive-search.md +116 -116
- package/ftm-researcher/references/agent-prompts.md +193 -193
- package/ftm-researcher/references/council-integration.md +193 -193
- package/ftm-researcher/references/output-format.md +203 -203
- package/ftm-researcher/references/synthesis-pipeline.md +165 -165
- package/ftm-researcher/scripts/score_credibility.py +234 -234
- package/ftm-researcher/scripts/validate_research.py +92 -92
- package/ftm-researcher.yml +2 -2
- package/ftm-resume/SKILL.md +518 -518
- package/ftm-resume/references/protocols/VALIDATION.md +172 -172
- package/ftm-resume.yml +2 -2
- package/ftm-retro/SKILL.md +380 -380
- package/ftm-retro/references/protocols/SCORING-RUBRICS.md +89 -89
- package/ftm-retro/references/templates/REPORT-FORMAT.md +109 -109
- package/ftm-retro.yml +2 -2
- package/ftm-routine/SKILL.md +170 -170
- package/ftm-routine.yml +4 -4
- package/ftm-state/blackboard/capabilities.json +5 -5
- package/ftm-state/blackboard/capabilities.schema.json +27 -27
- package/ftm-state/blackboard/context.json +23 -23
- package/ftm-state/blackboard/experiences/index.json +9 -9
- package/ftm-state/blackboard/patterns.json +6 -6
- package/ftm-state/schemas/context.schema.json +130 -130
- package/ftm-state/schemas/experience-index.schema.json +77 -77
- package/ftm-state/schemas/experience.schema.json +78 -78
- package/ftm-state/schemas/patterns.schema.json +44 -44
- package/ftm-upgrade/SKILL.md +194 -194
- package/ftm-upgrade/scripts/check-version.sh +76 -76
- package/ftm-upgrade/scripts/upgrade.sh +143 -143
- package/ftm-upgrade.yml +2 -2
- package/ftm-verify.yml +2 -2
- package/ftm.yml +2 -2
- package/hooks/ftm-blackboard-enforcer.sh +93 -93
- package/hooks/ftm-discovery-reminder.sh +90 -90
- package/hooks/ftm-drafts-gate.sh +61 -61
- package/hooks/ftm-event-logger.mjs +107 -107
- package/hooks/ftm-map-autodetect.sh +79 -79
- package/hooks/ftm-pending-sync-check.sh +22 -22
- package/hooks/ftm-plan-gate.sh +92 -92
- package/hooks/ftm-post-commit-trigger.sh +57 -57
- package/hooks/settings-template.json +81 -81
- package/install.sh +363 -363
- package/package.json +84 -84
- package/uninstall.sh +25 -25
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ftm-browse",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Headless browser daemon for Claude Code agents using Bun + Playwright",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"start": "bun run server.ts",
|
|
8
|
-
"cli": "bun run cli.ts",
|
|
9
|
-
"build": "bun build --compile main.ts --outfile ../bin/ftm-browse --external electron --external chromium-bidi"
|
|
10
|
-
},
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"playwright": "^1.49.0"
|
|
13
|
-
},
|
|
14
|
-
"devDependencies": {
|
|
15
|
-
"@types/bun": "latest"
|
|
16
|
-
}
|
|
17
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "ftm-browse",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Headless browser daemon for Claude Code agents using Bun + Playwright",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "bun run server.ts",
|
|
8
|
+
"cli": "bun run cli.ts",
|
|
9
|
+
"build": "bun build --compile main.ts --outfile ../bin/ftm-browse --external electron --external chromium-bidi"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"playwright": "^1.49.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/bun": "latest"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,189 +1,189 @@
|
|
|
1
|
-
import * as path from "path";
|
|
2
|
-
import * as fs from "fs";
|
|
3
|
-
import * as crypto from "crypto";
|
|
4
|
-
import { browserManager } from "./browser-manager.ts";
|
|
5
|
-
import { executeCommand } from "./commands.ts";
|
|
6
|
-
|
|
7
|
-
// ─── State File ─────────────────────────────────────────────────────────────
|
|
8
|
-
|
|
9
|
-
const STATE_DIR = path.join(process.env.HOME || "~", ".ftm-browse");
|
|
10
|
-
const STATE_FILE = path.join(STATE_DIR, "state.json");
|
|
11
|
-
|
|
12
|
-
export interface DaemonState {
|
|
13
|
-
port: number;
|
|
14
|
-
token: string;
|
|
15
|
-
pid: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function ensureStateDir(): void {
|
|
19
|
-
if (!fs.existsSync(STATE_DIR)) {
|
|
20
|
-
fs.mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 });
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function writeState(state: DaemonState): void {
|
|
25
|
-
ensureStateDir();
|
|
26
|
-
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), {
|
|
27
|
-
mode: 0o600,
|
|
28
|
-
encoding: "utf-8",
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function removeState(): void {
|
|
33
|
-
try {
|
|
34
|
-
if (fs.existsSync(STATE_FILE)) {
|
|
35
|
-
fs.unlinkSync(STATE_FILE);
|
|
36
|
-
}
|
|
37
|
-
} catch {
|
|
38
|
-
// Ignore cleanup errors
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ─── Token Generation ───────────────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
function generateToken(): string {
|
|
45
|
-
return crypto.randomBytes(32).toString("hex");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ─── Port Selection ─────────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
function getRandomPort(): number {
|
|
51
|
-
return Math.floor(Math.random() * (60000 - 10000 + 1)) + 10000;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ─── Request Handling ───────────────────────────────────────────────────────
|
|
55
|
-
|
|
56
|
-
function sendJson(data: unknown, status = 200): Response {
|
|
57
|
-
return new Response(JSON.stringify(data), {
|
|
58
|
-
status,
|
|
59
|
-
headers: { "Content-Type": "application/json" },
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function handleRequest(req: Request, token: string): Promise<Response> {
|
|
64
|
-
// Verify bearer token
|
|
65
|
-
const authHeader = req.headers.get("Authorization");
|
|
66
|
-
if (!authHeader || authHeader !== `Bearer ${token}`) {
|
|
67
|
-
return sendJson({ error: "Unauthorized" }, 401);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const url = new URL(req.url);
|
|
71
|
-
const command = url.pathname.slice(1); // Remove leading /
|
|
72
|
-
|
|
73
|
-
if (req.method !== "POST") {
|
|
74
|
-
return sendJson({ error: "Method not allowed" }, 405);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Health check endpoint
|
|
78
|
-
if (command === "health") {
|
|
79
|
-
return sendJson({ status: "ok", pid: process.pid });
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Parse body
|
|
83
|
-
let body: Record<string, unknown> = {};
|
|
84
|
-
try {
|
|
85
|
-
const text = await req.text();
|
|
86
|
-
if (text.trim()) {
|
|
87
|
-
body = JSON.parse(text);
|
|
88
|
-
}
|
|
89
|
-
} catch {
|
|
90
|
-
return sendJson({ error: "Invalid JSON body" }, 400);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Execute command
|
|
94
|
-
const result = await executeCommand(command, body);
|
|
95
|
-
|
|
96
|
-
return sendJson(result, result.success ? 200 : 400);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ─── CLI Argument Parsing ────────────────────────────────────────────────────
|
|
100
|
-
|
|
101
|
-
function parseUserDataDir(): string | null {
|
|
102
|
-
const args = process.argv.slice(2);
|
|
103
|
-
const flagIndex = args.indexOf("--user-data-dir");
|
|
104
|
-
if (flagIndex === -1) return null;
|
|
105
|
-
|
|
106
|
-
// --user-data-dir [path] — optional path argument
|
|
107
|
-
const nextArg = args[flagIndex + 1];
|
|
108
|
-
if (nextArg && !nextArg.startsWith("--")) {
|
|
109
|
-
// Expand ~ to home directory
|
|
110
|
-
const expanded = nextArg.startsWith("~")
|
|
111
|
-
? path.join(process.env.HOME || "~", nextArg.slice(1))
|
|
112
|
-
: nextArg;
|
|
113
|
-
return expanded;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Flag present but no path provided — use default
|
|
117
|
-
return "default";
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ─── Server Entry Point ─────────────────────────────────────────────────────
|
|
121
|
-
|
|
122
|
-
async function startServer(): Promise<void> {
|
|
123
|
-
const port = getRandomPort();
|
|
124
|
-
const token = generateToken();
|
|
125
|
-
|
|
126
|
-
// Configure session persistence if requested
|
|
127
|
-
const userDataDir = parseUserDataDir();
|
|
128
|
-
if (userDataDir) {
|
|
129
|
-
browserManager.setUserDataDir(userDataDir);
|
|
130
|
-
const displayDir = userDataDir === "default" ? "~/.ftm-browse/user-data (default)" : userDataDir;
|
|
131
|
-
console.log(`[ftm-browse] Session persistence enabled: ${displayDir}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
console.log(`[ftm-browse] Starting daemon on port ${port}`);
|
|
135
|
-
|
|
136
|
-
// Register shutdown handler
|
|
137
|
-
browserManager.setShutdownCallback(() => {
|
|
138
|
-
console.log("[ftm-browse] Browser shut down, removing state and exiting");
|
|
139
|
-
removeState();
|
|
140
|
-
process.exit(0);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Handle process termination
|
|
144
|
-
const cleanup = () => {
|
|
145
|
-
removeState();
|
|
146
|
-
process.exit(0);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
process.on("SIGTERM", cleanup);
|
|
150
|
-
process.on("SIGINT", cleanup);
|
|
151
|
-
process.on("SIGHUP", cleanup);
|
|
152
|
-
|
|
153
|
-
// Start HTTP server
|
|
154
|
-
const server = Bun.serve({
|
|
155
|
-
port,
|
|
156
|
-
fetch(req: Request) {
|
|
157
|
-
return handleRequest(req, token);
|
|
158
|
-
},
|
|
159
|
-
error(err: Error) {
|
|
160
|
-
console.error("[ftm-browse] Server error:", err);
|
|
161
|
-
return new Response("Internal Server Error", { status: 500 });
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// Write state file after server is ready
|
|
166
|
-
writeState({
|
|
167
|
-
port,
|
|
168
|
-
token,
|
|
169
|
-
pid: process.pid,
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
console.log(`[ftm-browse] Daemon ready. PID: ${process.pid}, Port: ${port}`);
|
|
173
|
-
console.log(`[ftm-browse] State written to: ${STATE_FILE}`);
|
|
174
|
-
|
|
175
|
-
// Keep process alive
|
|
176
|
-
await new Promise<void>((resolve) => {
|
|
177
|
-
// Server runs indefinitely until browser manager shuts down or signal received
|
|
178
|
-
process.on("exit", resolve);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
server.stop();
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Run if this is the main module
|
|
185
|
-
startServer().catch((err) => {
|
|
186
|
-
console.error("[ftm-browse] Fatal error:", err);
|
|
187
|
-
removeState();
|
|
188
|
-
process.exit(1);
|
|
189
|
-
});
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as crypto from "crypto";
|
|
4
|
+
import { browserManager } from "./browser-manager.ts";
|
|
5
|
+
import { executeCommand } from "./commands.ts";
|
|
6
|
+
|
|
7
|
+
// ─── State File ─────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const STATE_DIR = path.join(process.env.HOME || "~", ".ftm-browse");
|
|
10
|
+
const STATE_FILE = path.join(STATE_DIR, "state.json");
|
|
11
|
+
|
|
12
|
+
export interface DaemonState {
|
|
13
|
+
port: number;
|
|
14
|
+
token: string;
|
|
15
|
+
pid: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function ensureStateDir(): void {
|
|
19
|
+
if (!fs.existsSync(STATE_DIR)) {
|
|
20
|
+
fs.mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function writeState(state: DaemonState): void {
|
|
25
|
+
ensureStateDir();
|
|
26
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), {
|
|
27
|
+
mode: 0o600,
|
|
28
|
+
encoding: "utf-8",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function removeState(): void {
|
|
33
|
+
try {
|
|
34
|
+
if (fs.existsSync(STATE_FILE)) {
|
|
35
|
+
fs.unlinkSync(STATE_FILE);
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// Ignore cleanup errors
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Token Generation ───────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
function generateToken(): string {
|
|
45
|
+
return crypto.randomBytes(32).toString("hex");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Port Selection ─────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
function getRandomPort(): number {
|
|
51
|
+
return Math.floor(Math.random() * (60000 - 10000 + 1)) + 10000;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Request Handling ───────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
function sendJson(data: unknown, status = 200): Response {
|
|
57
|
+
return new Response(JSON.stringify(data), {
|
|
58
|
+
status,
|
|
59
|
+
headers: { "Content-Type": "application/json" },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function handleRequest(req: Request, token: string): Promise<Response> {
|
|
64
|
+
// Verify bearer token
|
|
65
|
+
const authHeader = req.headers.get("Authorization");
|
|
66
|
+
if (!authHeader || authHeader !== `Bearer ${token}`) {
|
|
67
|
+
return sendJson({ error: "Unauthorized" }, 401);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const url = new URL(req.url);
|
|
71
|
+
const command = url.pathname.slice(1); // Remove leading /
|
|
72
|
+
|
|
73
|
+
if (req.method !== "POST") {
|
|
74
|
+
return sendJson({ error: "Method not allowed" }, 405);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Health check endpoint
|
|
78
|
+
if (command === "health") {
|
|
79
|
+
return sendJson({ status: "ok", pid: process.pid });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Parse body
|
|
83
|
+
let body: Record<string, unknown> = {};
|
|
84
|
+
try {
|
|
85
|
+
const text = await req.text();
|
|
86
|
+
if (text.trim()) {
|
|
87
|
+
body = JSON.parse(text);
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
return sendJson({ error: "Invalid JSON body" }, 400);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Execute command
|
|
94
|
+
const result = await executeCommand(command, body);
|
|
95
|
+
|
|
96
|
+
return sendJson(result, result.success ? 200 : 400);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── CLI Argument Parsing ────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
function parseUserDataDir(): string | null {
|
|
102
|
+
const args = process.argv.slice(2);
|
|
103
|
+
const flagIndex = args.indexOf("--user-data-dir");
|
|
104
|
+
if (flagIndex === -1) return null;
|
|
105
|
+
|
|
106
|
+
// --user-data-dir [path] — optional path argument
|
|
107
|
+
const nextArg = args[flagIndex + 1];
|
|
108
|
+
if (nextArg && !nextArg.startsWith("--")) {
|
|
109
|
+
// Expand ~ to home directory
|
|
110
|
+
const expanded = nextArg.startsWith("~")
|
|
111
|
+
? path.join(process.env.HOME || "~", nextArg.slice(1))
|
|
112
|
+
: nextArg;
|
|
113
|
+
return expanded;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Flag present but no path provided — use default
|
|
117
|
+
return "default";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Server Entry Point ─────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
async function startServer(): Promise<void> {
|
|
123
|
+
const port = getRandomPort();
|
|
124
|
+
const token = generateToken();
|
|
125
|
+
|
|
126
|
+
// Configure session persistence if requested
|
|
127
|
+
const userDataDir = parseUserDataDir();
|
|
128
|
+
if (userDataDir) {
|
|
129
|
+
browserManager.setUserDataDir(userDataDir);
|
|
130
|
+
const displayDir = userDataDir === "default" ? "~/.ftm-browse/user-data (default)" : userDataDir;
|
|
131
|
+
console.log(`[ftm-browse] Session persistence enabled: ${displayDir}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log(`[ftm-browse] Starting daemon on port ${port}`);
|
|
135
|
+
|
|
136
|
+
// Register shutdown handler
|
|
137
|
+
browserManager.setShutdownCallback(() => {
|
|
138
|
+
console.log("[ftm-browse] Browser shut down, removing state and exiting");
|
|
139
|
+
removeState();
|
|
140
|
+
process.exit(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Handle process termination
|
|
144
|
+
const cleanup = () => {
|
|
145
|
+
removeState();
|
|
146
|
+
process.exit(0);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
process.on("SIGTERM", cleanup);
|
|
150
|
+
process.on("SIGINT", cleanup);
|
|
151
|
+
process.on("SIGHUP", cleanup);
|
|
152
|
+
|
|
153
|
+
// Start HTTP server
|
|
154
|
+
const server = Bun.serve({
|
|
155
|
+
port,
|
|
156
|
+
fetch(req: Request) {
|
|
157
|
+
return handleRequest(req, token);
|
|
158
|
+
},
|
|
159
|
+
error(err: Error) {
|
|
160
|
+
console.error("[ftm-browse] Server error:", err);
|
|
161
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Write state file after server is ready
|
|
166
|
+
writeState({
|
|
167
|
+
port,
|
|
168
|
+
token,
|
|
169
|
+
pid: process.pid,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
console.log(`[ftm-browse] Daemon ready. PID: ${process.pid}, Port: ${port}`);
|
|
173
|
+
console.log(`[ftm-browse] State written to: ${STATE_FILE}`);
|
|
174
|
+
|
|
175
|
+
// Keep process alive
|
|
176
|
+
await new Promise<void>((resolve) => {
|
|
177
|
+
// Server runs indefinitely until browser manager shuts down or signal received
|
|
178
|
+
process.on("exit", resolve);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
server.stop();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Run if this is the main module
|
|
185
|
+
startServer().catch((err) => {
|
|
186
|
+
console.error("[ftm-browse] Fatal error:", err);
|
|
187
|
+
removeState();
|
|
188
|
+
process.exit(1);
|
|
189
|
+
});
|