Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand All @@ -56,11 +56,11 @@ Claude Codeプラグインのマーケットプレイスリポジトリ。全プ

<!-- BEGIN:app-list (auto-generated, do not edit) -->

| アプリ | 説明 |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| **@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 |

<!-- END:app-list -->

Expand Down
4 changes: 2 additions & 2 deletions apps/ops-harbor-control-plane/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion apps/ops-harbor-control-plane/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
4 changes: 2 additions & 2 deletions apps/ops-harbor-control-plane/src/lib/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import type {
WorkItem,
WorkItemAlert,
WorkItemFilter,
} from "@r_masseater/ops-harbor-core";
} from "@repo/ops-harbor-core";
import {
buildActivityWhereClause,
buildAutomationPrompt,
filterWorkItems,
mapWorkItemsToAlertSummaries,
openSqliteDatabase,
parseJson,
} from "@r_masseater/ops-harbor-core";
} from "@repo/ops-harbor-core";
import { mkdirSync } from "node:fs";
import { dirname } from "node:path";

Expand Down
4 changes: 2 additions & 2 deletions apps/ops-harbor-control-plane/src/lib/github.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down
2 changes: 1 addition & 1 deletion apps/ops-harbor-control-plane/src/lib/sync.test.ts
Original file line number Diff line number Diff line change
@@ -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> = {}): WorkItem {
Expand Down
4 changes: 2 additions & 2 deletions apps/ops-harbor-control-plane/src/lib/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions apps/ops-harbor-control-plane/src/server.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
6 changes: 4 additions & 2 deletions apps/ops-harbor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
68 changes: 67 additions & 1 deletion apps/ops-harbor/src/cli.ts
Original file line number Diff line number Diff line change
@@ -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<number>;
};

const CONTROL_PLANE_CONFIG_PATH = join(homedir(), ".config", "ops-harbor", "control-plane.json");

async function readConfiguredControlPlanePort(): Promise<number> {
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<boolean> {
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") {
Expand Down Expand Up @@ -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)`);
Expand All @@ -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);
}
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
2 changes: 1 addition & 1 deletion apps/ops-harbor/src/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion apps/ops-harbor/src/lib/automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
2 changes: 1 addition & 1 deletion apps/ops-harbor/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
2 changes: 1 addition & 1 deletion apps/ops-harbor/src/lib/control-plane-client.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down
4 changes: 2 additions & 2 deletions apps/ops-harbor/src/lib/local-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
2 changes: 1 addition & 1 deletion apps/ops-harbor/src/lib/sync.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
2 changes: 1 addition & 1 deletion apps/ops-harbor/src/mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
4 changes: 2 additions & 2 deletions apps/ops-harbor/src/server.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
18 changes: 10 additions & 8 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"type": true
},
"tags": ["-lintignore"],
"ignore": [".agents/**"],
"ignoreDependencies": ["@r_masseater/doc-engine"],
"workspaces": {
".": {
Expand All @@ -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"],
Expand Down
2 changes: 1 addition & 1 deletion packages/ops-harbor-core/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Loading