diff --git a/AGENTS.md b/AGENTS.md index b1ead420..cfd44fbd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,8 +45,8 @@ Claude Codeプラグインのマーケットプレイスリポジトリ。全プ | -------------------------------- | ------------------------------------------------------------------------------------------------------------ | | **@r_masseater/cc-plugin-lib** | Common library for Claude Code plugins - logging and error handling utilities | | **@r_masseater/doc-engine** | Documentation generation and validation engine — JSDoc extraction, Markdown generation, and plugin list sync | -| **@r_masseater/ops-harbor-core** | Core library for Ops Harbor — alerts, work-item filtering, SQLite helpers, and shared type definitions | | **@r_masseater/repository-lint** | Repository linting rules — hook structure, workspace dependency constraints, and version-bump enforcement | +| **@repo/ops-harbor-core** | Core library for Ops Harbor — alerts, work-item filtering, SQLite helpers, and shared type definitions | | **@repo/ts-config** | Shared TypeScript configuration presets — base, app, plugin, and library | | **@repo/vitest-config** | Shared Vitest configuration with standardized coverage thresholds for the monorepo | @@ -56,11 +56,11 @@ Claude Codeプラグインのマーケットプレイスリポジトリ。全プ -| アプリ | 説明 | -| ----------------------------------------- | --------------------------------------------------------------------------------------------------------- | -| **@r_masseater/ops-harbor** | Ops Harbor local dashboard, daemon, and read-only MCP bridge | -| **@r_masseater/ops-harbor-control-plane** | Local GitHub App control plane — webhook ingress, tunnel management, and configuration API for Ops Harbor | -| **@r_masseater/sdd-webapp** | Web dashboard for SDD (Spec Driven Development) plugin | +| アプリ | 説明 | +| ---------------------------------- | --------------------------------------------------------------------------------------------------------- | +| **@r_masseater/ops-harbor** | Ops Harbor local dashboard, daemon, and read-only MCP bridge | +| **@r_masseater/sdd-webapp** | Web dashboard for SDD (Spec Driven Development) plugin | +| **@repo/ops-harbor-control-plane** | Local GitHub App control plane — webhook ingress, tunnel management, and configuration API for Ops Harbor | diff --git a/apps/ops-harbor-control-plane/package.json b/apps/ops-harbor-control-plane/package.json index 8bd4f4e0..a936a0c8 100644 --- a/apps/ops-harbor-control-plane/package.json +++ b/apps/ops-harbor-control-plane/package.json @@ -1,5 +1,5 @@ { - "name": "@r_masseater/ops-harbor-control-plane", + "name": "@repo/ops-harbor-control-plane", "private": true, "description": "Local GitHub App control plane — webhook ingress, tunnel management, and configuration API for Ops Harbor", "type": "module", @@ -12,7 +12,7 @@ "test": "vitest run --passWithNoTests" }, "dependencies": { - "@r_masseater/ops-harbor-core": "workspace:*", + "@repo/ops-harbor-core": "workspace:*", "better-sqlite3": "^11.9.1", "hono": "^4.10.2", "localtunnel": "^2.0.2", diff --git a/apps/ops-harbor-control-plane/src/cli.ts b/apps/ops-harbor-control-plane/src/cli.ts index 3890a80d..6a60f60c 100644 --- a/apps/ops-harbor-control-plane/src/cli.ts +++ b/apps/ops-harbor-control-plane/src/cli.ts @@ -2,7 +2,7 @@ import { ensureStoredConfigSecrets, readConfig } from "./lib/config"; import { ensureGitHubAppWebhookUrl } from "./lib/github"; -import { findAvailablePort } from "@r_masseater/ops-harbor-core"; +import { findAvailablePort } from "@repo/ops-harbor-core"; import { openTunnel } from "./lib/tunnel"; import { openControlPlaneApps, type RuntimeStatus } from "./server"; diff --git a/apps/ops-harbor-control-plane/src/lib/db.ts b/apps/ops-harbor-control-plane/src/lib/db.ts index 3dc7f36f..33abd1f2 100644 --- a/apps/ops-harbor-control-plane/src/lib/db.ts +++ b/apps/ops-harbor-control-plane/src/lib/db.ts @@ -6,7 +6,7 @@ import type { WorkItem, WorkItemAlert, WorkItemFilter, -} from "@r_masseater/ops-harbor-core"; +} from "@repo/ops-harbor-core"; import { buildActivityWhereClause, buildAutomationPrompt, @@ -14,7 +14,7 @@ import { mapWorkItemsToAlertSummaries, openSqliteDatabase, parseJson, -} from "@r_masseater/ops-harbor-core"; +} from "@repo/ops-harbor-core"; import { mkdirSync } from "node:fs"; import { dirname } from "node:path"; diff --git a/apps/ops-harbor-control-plane/src/lib/github.ts b/apps/ops-harbor-control-plane/src/lib/github.ts index 95b52edc..bf54aaad 100644 --- a/apps/ops-harbor-control-plane/src/lib/github.ts +++ b/apps/ops-harbor-control-plane/src/lib/github.ts @@ -1,5 +1,5 @@ -import type { WorkItem, WorkItemCheck, WorkItemReview } from "@r_masseater/ops-harbor-core"; -import { summarizeStatus } from "@r_masseater/ops-harbor-core"; +import type { WorkItem, WorkItemCheck, WorkItemReview } from "@repo/ops-harbor-core"; +import { summarizeStatus } from "@repo/ops-harbor-core"; import { createHmac, createSign } from "node:crypto"; import type { OpsHarborControlPlaneConfig } from "./config"; diff --git a/apps/ops-harbor-control-plane/src/lib/sync.test.ts b/apps/ops-harbor-control-plane/src/lib/sync.test.ts index 2b128f20..11f422e7 100644 --- a/apps/ops-harbor-control-plane/src/lib/sync.test.ts +++ b/apps/ops-harbor-control-plane/src/lib/sync.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "vitest"; -import type { WorkItem } from "@r_masseater/ops-harbor-core"; +import type { WorkItem } from "@repo/ops-harbor-core"; import { hasWorkItemChanged } from "./sync.js"; function makeWorkItem(overrides: Partial = {}): WorkItem { diff --git a/apps/ops-harbor-control-plane/src/lib/sync.ts b/apps/ops-harbor-control-plane/src/lib/sync.ts index 10fe4417..b4abee62 100644 --- a/apps/ops-harbor-control-plane/src/lib/sync.ts +++ b/apps/ops-harbor-control-plane/src/lib/sync.ts @@ -4,8 +4,8 @@ import type { GitHubReviewEventState, SqliteDatabase, WorkItem, -} from "@r_masseater/ops-harbor-core"; -import { deriveAlerts } from "@r_masseater/ops-harbor-core"; +} from "@repo/ops-harbor-core"; +import { deriveAlerts } from "@repo/ops-harbor-core"; import { randomUUID } from "node:crypto"; import type { OpsHarborControlPlaneConfig } from "./config"; import { diff --git a/apps/ops-harbor-control-plane/src/server.ts b/apps/ops-harbor-control-plane/src/server.ts index 2c996cb3..ac756686 100644 --- a/apps/ops-harbor-control-plane/src/server.ts +++ b/apps/ops-harbor-control-plane/src/server.ts @@ -1,5 +1,5 @@ -import type { SqliteDatabase } from "@r_masseater/ops-harbor-core"; -import { parseWorkItemFilterFromQuery } from "@r_masseater/ops-harbor-core"; +import type { SqliteDatabase } from "@repo/ops-harbor-core"; +import { parseWorkItemFilterFromQuery } from "@repo/ops-harbor-core"; import { Hono } from "hono"; import { cors } from "hono/cors"; import type { OpsHarborControlPlaneConfig, StoredOpsHarborControlPlaneConfig } from "./lib/config"; diff --git a/apps/ops-harbor/package.json b/apps/ops-harbor/package.json index 8f6d8488..5d4de96e 100644 --- a/apps/ops-harbor/package.json +++ b/apps/ops-harbor/package.json @@ -32,7 +32,7 @@ "dev:client": "vite --host 127.0.0.1 --strictPort", "build": "bun run build:client && bun run build:server", "build:client": "vite build", - "build:server": "bun build src/cli.ts --outdir dist --target bun && bun build src/mcp-server.ts --outdir dist --target bun", + "build:server": "bun build src/cli.ts --outdir dist --target bun && bun build src/mcp-server.ts --outdir dist --target bun && bun build ../ops-harbor-control-plane/src/cli.ts --outfile dist/control-plane.js --target bun --external localtunnel", "check": "oxlint", "check:fix": "oxlint --fix", "typecheck": "tsgo --noEmit", @@ -43,14 +43,16 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.25.1", - "@r_masseater/ops-harbor-core": "workspace:*", "better-sqlite3": "^11.9.1", "hono": "^4.10.2", + "localtunnel": "^2.0.2", "react": "^19.0.0", "react-dom": "^19.0.0", "valibot": "^1.2.0" }, "devDependencies": { + "@repo/ops-harbor-control-plane": "workspace:*", + "@repo/ops-harbor-core": "workspace:*", "@repo/ts-config": "workspace:*", "@repo/vitest-config": "workspace:*", "@types/better-sqlite3": "^7.6.13", diff --git a/apps/ops-harbor/src/cli.ts b/apps/ops-harbor/src/cli.ts index 89621176..f6f19622 100644 --- a/apps/ops-harbor/src/cli.ts +++ b/apps/ops-harbor/src/cli.ts @@ -1,9 +1,47 @@ #!/usr/bin/env bun +import { existsSync } from "node:fs"; +import { readFile } from "node:fs/promises"; +import { homedir } from "node:os"; +import { join } from "node:path"; import { parseArgs } from "node:util"; -import { findAvailablePort } from "@r_masseater/ops-harbor-core"; +import { findAvailablePort } from "@repo/ops-harbor-core"; import { createServedApp } from "./server"; +type ChildProcessHandle = { + kill(signal?: number): void; + exited: Promise; +}; + +const CONTROL_PLANE_CONFIG_PATH = join(homedir(), ".config", "ops-harbor", "control-plane.json"); + +async function readConfiguredControlPlanePort(): Promise { + if (!existsSync(CONTROL_PLANE_CONFIG_PATH)) return 4130; + try { + const raw = await readFile(CONTROL_PLANE_CONFIG_PATH, "utf-8"); + const parsed = JSON.parse(raw) as { port?: unknown }; + if (typeof parsed.port === "number" && Number.isInteger(parsed.port)) return parsed.port; + } catch { + // Fall through to the default. + } + return 4130; +} + +async function isControlPlaneReachable(port: number): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 700); + try { + const response = await fetch(`http://127.0.0.1:${port}/api/health`, { + signal: controller.signal, + }); + return response.ok; + } catch { + return false; + } finally { + clearTimeout(timeout); + } +} + const subcommand = Bun.argv[2]; if (subcommand === "mcp") { @@ -42,6 +80,24 @@ Options: process.exit(0); } + let managedControlPlane: ChildProcessHandle | null = null; + const controlPlanePort = await readConfiguredControlPlanePort(); + if (!(await isControlPlaneReachable(controlPlanePort))) { + const controlPlanePath = join(import.meta.dir, "control-plane.js"); + if (existsSync(controlPlanePath)) { + const child = Bun.spawn({ + cmd: ["bun", controlPlanePath], + stdout: "inherit", + stderr: "inherit", + }); + managedControlPlane = child; + child.exited.then((exitCode) => { + if (exitCode !== 0) console.error(`control-plane exited with code ${exitCode}`); + if (managedControlPlane === child) managedControlPlane = null; + }); + } + } + const port = values.port ? Number(values.port) : await findAvailablePort(4131); if (!Number.isInteger(port) || port < 1 || port > 65_535) { console.error(`Invalid port: ${values.port ?? String(port)} (expected 1-65535)`); @@ -62,4 +118,14 @@ Options: hostname: "127.0.0.1", fetch: app.fetch, }); + + const shutdown = () => { + if (managedControlPlane) { + managedControlPlane.kill(15); + managedControlPlane = null; + } + process.exit(0); + }; + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); } diff --git a/apps/ops-harbor/src/client/pages/dashboard/ui/DashboardPage.tsx b/apps/ops-harbor/src/client/pages/dashboard/ui/DashboardPage.tsx index 31513cfd..3edfd828 100644 --- a/apps/ops-harbor/src/client/pages/dashboard/ui/DashboardPage.tsx +++ b/apps/ops-harbor/src/client/pages/dashboard/ui/DashboardPage.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from "react"; -import type { ActivityEvent, WorkItem } from "@r_masseater/ops-harbor-core"; +import type { ActivityEvent, WorkItem } from "@repo/ops-harbor-core"; import { SettingsPanel } from "./SettingsPanel"; import { type ControlPlaneConfig, diff --git a/apps/ops-harbor/src/dev.ts b/apps/ops-harbor/src/dev.ts index 4dcb3889..9e427d48 100644 --- a/apps/ops-harbor/src/dev.ts +++ b/apps/ops-harbor/src/dev.ts @@ -5,7 +5,7 @@ import { mkdir, readFile } from "node:fs/promises"; import { homedir } from "node:os"; import { join } from "node:path"; import { clearDevRuntimeConfig, saveDevRuntimeConfig } from "./lib/dev-runtime-config"; -import { findAvailablePort } from "@r_masseater/ops-harbor-core"; +import { findAvailablePort } from "@repo/ops-harbor-core"; type ChildProcessHandle = { kill(signal?: number): void; diff --git a/apps/ops-harbor/src/lib/automation.ts b/apps/ops-harbor/src/lib/automation.ts index 102fe009..d2838a84 100644 --- a/apps/ops-harbor/src/lib/automation.ts +++ b/apps/ops-harbor/src/lib/automation.ts @@ -4,7 +4,7 @@ import { type PushPolicy, type SqliteDatabase, type WorkItem, -} from "@r_masseater/ops-harbor-core"; +} from "@repo/ops-harbor-core"; import { randomUUID } from "node:crypto"; import { existsSync } from "node:fs"; import { spawn } from "node:child_process"; diff --git a/apps/ops-harbor/src/lib/config.ts b/apps/ops-harbor/src/lib/config.ts index 6da0d802..f80a6e28 100644 --- a/apps/ops-harbor/src/lib/config.ts +++ b/apps/ops-harbor/src/lib/config.ts @@ -1,4 +1,4 @@ -import type { AutomationTrigger, PushPolicy } from "@r_masseater/ops-harbor-core"; +import type { AutomationTrigger, PushPolicy } from "@repo/ops-harbor-core"; import * as v from "valibot"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; diff --git a/apps/ops-harbor/src/lib/control-plane-client.ts b/apps/ops-harbor/src/lib/control-plane-client.ts index 1327d27c..75f90767 100644 --- a/apps/ops-harbor/src/lib/control-plane-client.ts +++ b/apps/ops-harbor/src/lib/control-plane-client.ts @@ -1,4 +1,4 @@ -import type { ActivityEvent, AutomationJob, WorkItem } from "@r_masseater/ops-harbor-core"; +import type { ActivityEvent, AutomationJob, WorkItem } from "@repo/ops-harbor-core"; import type { OpsHarborConfig } from "./config"; type LeaseResponse = { diff --git a/apps/ops-harbor/src/lib/local-db.ts b/apps/ops-harbor/src/lib/local-db.ts index f2d02de3..d3ddbacb 100644 --- a/apps/ops-harbor/src/lib/local-db.ts +++ b/apps/ops-harbor/src/lib/local-db.ts @@ -3,14 +3,14 @@ import type { SqliteDatabase, WorkItem, WorkItemFilter, -} from "@r_masseater/ops-harbor-core"; +} from "@repo/ops-harbor-core"; import { buildActivityWhereClause, filterWorkItems, mapWorkItemsToAlertSummaries, openSqliteDatabase, parseJson, -} from "@r_masseater/ops-harbor-core"; +} from "@repo/ops-harbor-core"; import { mkdirSync } from "node:fs"; import { homedir } from "node:os"; import { dirname, join } from "node:path"; diff --git a/apps/ops-harbor/src/lib/sync.ts b/apps/ops-harbor/src/lib/sync.ts index c283a416..c872489e 100644 --- a/apps/ops-harbor/src/lib/sync.ts +++ b/apps/ops-harbor/src/lib/sync.ts @@ -1,4 +1,4 @@ -import type { SqliteDatabase } from "@r_masseater/ops-harbor-core"; +import type { SqliteDatabase } from "@repo/ops-harbor-core"; import { ControlPlaneClient } from "./control-plane-client"; import type { OpsHarborConfig } from "./config"; import { replaceSnapshot } from "./local-db"; diff --git a/apps/ops-harbor/src/mcp-server.ts b/apps/ops-harbor/src/mcp-server.ts index a433e426..edb211e7 100644 --- a/apps/ops-harbor/src/mcp-server.ts +++ b/apps/ops-harbor/src/mcp-server.ts @@ -2,7 +2,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; -import { parseWorkItemFilterFromQuery } from "@r_masseater/ops-harbor-core"; +import { parseWorkItemFilterFromQuery } from "@repo/ops-harbor-core"; import { getWorkItem, listActivity, listAlerts, listWorkItems, openLocalDb } from "./lib/local-db"; import { mcpError, mcpText } from "./lib/mcp"; diff --git a/apps/ops-harbor/src/server.ts b/apps/ops-harbor/src/server.ts index 4492917b..c013da02 100644 --- a/apps/ops-harbor/src/server.ts +++ b/apps/ops-harbor/src/server.ts @@ -1,5 +1,5 @@ -import type { SqliteDatabase } from "@r_masseater/ops-harbor-core"; -import { parseWorkItemFilterFromQuery } from "@r_masseater/ops-harbor-core"; +import type { SqliteDatabase } from "@repo/ops-harbor-core"; +import { parseWorkItemFilterFromQuery } from "@repo/ops-harbor-core"; import { Hono } from "hono"; import { cors } from "hono/cors"; import { join, resolve } from "node:path"; diff --git a/bun.lock b/bun.lock index f00ee7b1..6be3d1d6 100644 --- a/bun.lock +++ b/bun.lock @@ -31,14 +31,16 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.25.1", - "@r_masseater/ops-harbor-core": "workspace:*", "better-sqlite3": "^11.9.1", "hono": "^4.10.2", + "localtunnel": "^2.0.2", "react": "^19.0.0", "react-dom": "^19.0.0", "valibot": "^1.2.0", }, "devDependencies": { + "@repo/ops-harbor-control-plane": "workspace:*", + "@repo/ops-harbor-core": "workspace:*", "@repo/ts-config": "workspace:*", "@repo/vitest-config": "workspace:*", "@types/better-sqlite3": "^7.6.13", @@ -55,9 +57,9 @@ }, }, "apps/ops-harbor-control-plane": { - "name": "@r_masseater/ops-harbor-control-plane", + "name": "@repo/ops-harbor-control-plane", "dependencies": { - "@r_masseater/ops-harbor-core": "workspace:*", + "@repo/ops-harbor-core": "workspace:*", "better-sqlite3": "^11.9.1", "hono": "^4.10.2", "localtunnel": "^2.0.2", @@ -136,7 +138,7 @@ }, }, "packages/ops-harbor-core": { - "name": "@r_masseater/ops-harbor-core", + "name": "@repo/ops-harbor-core", "devDependencies": { "@repo/ts-config": "workspace:*", "@repo/vitest-config": "workspace:*", @@ -821,10 +823,6 @@ "@r_masseater/ops-harbor": ["@r_masseater/ops-harbor@workspace:apps/ops-harbor"], - "@r_masseater/ops-harbor-control-plane": ["@r_masseater/ops-harbor-control-plane@workspace:apps/ops-harbor-control-plane"], - - "@r_masseater/ops-harbor-core": ["@r_masseater/ops-harbor-core@workspace:packages/ops-harbor-core"], - "@r_masseater/repository-lint": ["@r_masseater/repository-lint@workspace:packages/repository-lint"], "@r_masseater/sdd-webapp": ["@r_masseater/sdd-webapp@workspace:apps/sdd-webapp"], @@ -873,6 +871,10 @@ "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], + "@repo/ops-harbor-control-plane": ["@repo/ops-harbor-control-plane@workspace:apps/ops-harbor-control-plane"], + + "@repo/ops-harbor-core": ["@repo/ops-harbor-core@workspace:packages/ops-harbor-core"], + "@repo/ts-config": ["@repo/ts-config@workspace:packages/ts-config"], "@repo/vitest-config": ["@repo/vitest-config@workspace:packages/vitest-config"], diff --git a/knip.json b/knip.json index bbc45ee3..e15c1c7e 100644 --- a/knip.json +++ b/knip.json @@ -5,6 +5,7 @@ "type": true }, "tags": ["-lintignore"], + "ignore": [".agents/**"], "ignoreDependencies": ["@r_masseater/doc-engine"], "workspaces": { ".": { @@ -27,7 +28,12 @@ "apps/ops-harbor": { "entry": ["src/cli.ts", "src/mcp-server.ts"], "project": ["src/**/*.{ts,tsx}"], - "ignoreDependencies": ["better-sqlite3", "@types/better-sqlite3"] + "ignoreDependencies": [ + "better-sqlite3", + "@types/better-sqlite3", + "localtunnel", + "@repo/ops-harbor-control-plane" + ] }, "apps/sdd-webapp": { "entry": ["src/cli.ts", "src/mcp-server.ts", "src/client/main.tsx"], diff --git a/packages/ops-harbor-core/package.json b/packages/ops-harbor-core/package.json index 91625f04..9ffc36fd 100644 --- a/packages/ops-harbor-core/package.json +++ b/packages/ops-harbor-core/package.json @@ -1,5 +1,5 @@ { - "name": "@r_masseater/ops-harbor-core", + "name": "@repo/ops-harbor-core", "private": true, "description": "Core library for Ops Harbor — alerts, work-item filtering, SQLite helpers, and shared type definitions", "type": "module",