---
url: https://docs.ogmabox.com/ja/plugins/README.md
description: >-
  バックエンドロジック、フロントエンドパネル、コマンド、権限、マーケットプレイスのメタデータを備えた Ogma
  プラグインの作成、パッケージ化、インストール、有効化、配布について説明します。
---

# Ogma プラグインシステム {#ogma-plugin-system}

Ogma プラグインは、独自のバックエンドロジック、フロントエンド UI パネル、ワークフローのステップを追加してツールを拡張します。プラグインはディスク上のディレクトリからローカルにインストールし、プロジェクトごとに有効化して、サンドボックス環境で実行します。

このドキュメントは、プラグイン開発者向けの主要なリファレンスです。

最短で始めるには、[プラグインクイックスタート](/ja/plugins/quickstart)をご覧ください。

***

## クイックスタート {#quick-start}

### 最小構成のバックエンドプラグイン {#minimal-backend-plugin}

```
my-plugin/
  manifest.json
  backend/script.js
```

`manifest.json`:

```json
{
  "id": "my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "plugins": [
    {
      "kind": "backend",
      "id": "my-plugin-backend",
      "entrypoint": "backend/script.js"
    }
  ]
}
```

`backend/script.js`（ES2020。バンドラーを使用する場合は import を利用できます）：

```js
async function init(sdk) {
  sdk.console.log("my-plugin started");

  sdk.events.onInterceptResponse(function(req, res) {
    if (res.getCode() === 403) {
      sdk.console.warn("403 on " + req.getUrl());
    }
  });
}
```

これだけでバックエンドのみのプラグインが動作します。`backend/script.js` を単一ファイルのままにして、`manifest.json` から直接参照してください。

### 一分で行うビルド手順（TypeScript ソース） {#one-minute-build-flow-typescript-source}

TypeScript で開発する場合は、次の構成を使用します。

```text
my-plugin/
  manifest.json
  backend/
    src/index.ts
```

ビルド：

```bash
pnpm add -D @ogmabox/ogma-sdk esbuild typescript
pnpm exec esbuild backend/src/index.ts --bundle --format=iife --platform=neutral --external:@ogma/sdk --external:@ogmabox/ogma-sdk --outfile=backend/script.js
```

インストール：**プラグイン > インストール**で `my-plugin/` ディレクトリを選択し、プラグインを有効にします。

***

## マニフェストリファレンス {#manifest-reference}

`manifest.json` はパッケージのルートディレクトリに配置します。すべてのフィールドで大文字と小文字が区別されます。

### トップレベルのフィールド {#top-level-fields}

| フィールド | 必須 | 型 | 説明 |
|-------|----------|------|-------|
| `id` | はい | string | 小文字の英字、数字、ハイフンのみ。最大 64 文字。インストール済みプラグインの間で一意である必要があります。 |
| `version` | はい | string | セマンティックバージョニング：`MAJOR.MINOR.PATCH` |
| `name` | いいえ | string | UI に表示する名前。既定値は `id` です。 |
| `description` | いいえ | string | 一行の概要。 |
| `author` | いいえ | object | `{ "name": "...", "email": "...", "url": "..." }` |
| `homepage` | いいえ | string | ソースリポジトリまたはドキュメントの URL。 |
| `plugins` | はい | array | 一つ以上のプラグインコンポーネントのエントリ（下記参照）。 |
| `permissions` | いいえ | array | 必要な権限名の一覧（[権限](#permissions)参照）。 |

### プラグインコンポーネントのエントリ {#plugin-component-entry}

`plugins` 配列の各オブジェクトは、一つのコンポーネントを定義します。

**バックエンドコンポーネント：**

```json
{
  "kind": "backend",
  "id": "my-plugin-backend",
  "entrypoint": "backend/script.js",
  "runtime": "javascript",
  "assets": "backend/assets"
}
```

**フロントエンドコンポーネント：**

```json
{
  "kind": "frontend",
  "id": "my-plugin-frontend",
  "entrypoint": "frontend/script.js",
  "style": "frontend/style.css",
  "assets": "frontend/assets",
  "backend": { "id": "my-plugin-backend" }
}
```

| フィールド | 必須 | 説明 |
|-------|----------|-------|
| `kind` | はい | `"backend"` または `"frontend"` |
| `id` | はい | マニフェスト内で一意である必要があります。小文字とハイフンを使用します。 |
| `entrypoint` | はい | JS エントリファイルへの相対パス。 |
| `style` | いいえ | プラグインの iframe に読み込む CSS ファイル。 |
| `assets` | いいえ | `/plugins/{id}/assets/` 以下で配信する静的アセットのディレクトリ。 |
| `backend.id` | いいえ | `sdk.backend.*` RPC を使用するため、フロントエンドコンポーネントをバックエンドコンポーネントに関連付けます。 |
| `runtime` | いいえ（バックエンドのみ） | `"javascript"`（既定値であり、唯一サポートされる値）。 |

***

## バックエンドプラグイン API（sdk） {#backend-plugin-api-sdk}

バックエンドの `sdk` オブジェクトは `init(sdk)` 関数に渡されます。`async` と明記されているものを除き、すべてのメソッドは同期的に動作します。

### `sdk.console` {#sdk-console}

```js
sdk.console.log("message")
sdk.console.warn("message")
sdk.console.error("message")
```

プラグインのログバッファーに書き込みます（ログタブで確認できます）。最大 500 件を保持し、各メッセージは 1 KB で切り詰められます。

### `sdk.meta` {#sdk-meta}

```js
sdk.meta.id()          // > string: plugin id (e.g. "my-plugin")
sdk.meta.packageId()   // > string: same as id
sdk.meta.version()     // > string: semver (e.g. "1.0.0")
sdk.meta.path()        // > string: writable data directory for this plugin
```

`sdk.meta.path()` は、プラグイン専用の書き込み可能なデータディレクトリを示します。例：`~/.local/share/ogma/plugins/my-plugin/data`。ディレクトリは自動作成され、再起動をまたいでプラグインのデータを保存できます。

状態管理やファイル操作の補助には、`sdk.storage`、`sdk.path`、`sdk.fs` も利用できます。

### `sdk.storage` {#sdk-storage}

```js
sdk.storage.get("key")      // > string | null
sdk.storage.set("key", "value")
sdk.storage.delete("key")
sdk.storage.clear()
sdk.storage.keys()          // > string[]
```

`sdk.storage` のスコープはプラグイン ID ごとに分離され、データはプラグインの再起動後も保持されます。

### `sdk.fs` {#sdk-fs}

```js
sdk.fs.readFile("relative/file.txt")      // > string
sdk.fs.writeFile("relative/file.txt", "text")
sdk.fs.appendFile("relative/file.txt", "more")
sdk.fs.exists("relative/file.txt")        // > boolean
sdk.fs.existsSync("relative/file.txt")    // > boolean
sdk.fs.list("relative/dir")              // > string[]
sdk.fs.mkdir("relative/dir")
```

`sdk.fs` が扱えるのは `sdk.meta.path()` 以下のファイルのみです。

`read` と `write` は引き続き、それぞれ `readFile` と `writeFile` の互換エイリアスとして利用できます。上書きしたくないファイルを作成する前に、`exists` または `existsSync` で確認してください。これらの API はプラグインランタイムで同期的に動作し、Node.js の完全な `fs` モジュールではありません。プラグインからのファイルアクセスには `plugin_storage` 権限が必要で、プラグイン専用のデータディレクトリ内に制限されます。ワークフローの JavaScript ではファイルシステムのコンテキストが異なります。[ワークフローのファイルアクセス](../app/workflows.md#javascript-and-files)をご覧ください。

### `sdk.path` {#sdk-path}

```js
sdk.path.join("a", "b", "c")
sdk.path.basename("/tmp/file.txt")
sdk.path.dirname("/tmp/file.txt")
sdk.path.extname("file.txt")
sdk.path.resolve("/a", "b")
sdk.path.isAbsolute("/tmp/file.txt")
sdk.path.sep
```

### `sdk.events` {#sdk-events}

Ogma のイベントに対するコールバックを登録します。すべてのコールバックは QuickJS サンドボックス内で同期的に呼び出されます。

```js
sdk.events.onInterceptRequest(function(req) {
  // req: RequestSpecRaw
  // Return a modified RequestSpecRaw to mutate the request.
  // Return null/undefined to pass through unchanged.
});

sdk.events.onInterceptResponse(function(req, res) {
  // req: Request (read-only), res: Response (read-only)
  // Return value is ignored.
});

sdk.events.onProjectChange(function() {
  // no callback args
});

sdk.events.onFindingCreated(function(finding) {
  // finding: { id, title, reporter }
});
```

### `sdk.requests` {#sdk-requests}

```js
// Get a single HTTP entry by id
var entry = sdk.requests.get("entry-id");
// entry: { id, method, host, path, query, tls, ... } or null

// Search HTTP history
var results = sdk.requests.search({ limit: 20, offset: 0 });
// results: { entries: [...], total: N }

// Send an HTTP request (requires send_requests permission)
var response = await sdk.requests.send(spec);
// spec: RequestSpecRaw (see below)
// response: Response
```

`sdk.requests.send` を使用するには、マニフェストで `send_requests` 権限を宣言し、ユーザーから許可を得る必要があります。[権限](#permissions)をご覧ください。

### `sdk.findings` {#sdk-findings}

```js
// Create a finding (requires write_findings permission)
sdk.findings.create({
  title: "SSRF via redirect",
  reporter: "my-plugin",
  dedupeKey: "ssrf-" + request.getId(),
  request: { id: request.getId() }
});

// Check if a finding already exists (dedup check)
var exists = sdk.findings.exists({ dedupeKey: "ssrf-abc" });

// List findings
var page = sdk.findings.list({ limit: 20, offset: 0 });

// Get a single finding
var finding = sdk.findings.get("finding-id");
```

`sdk.findings.create` の回数制限：毎分 10 回、プラグインセッションあたり 500 回、イベントコールバックあたり 3 回。

### `sdk.api` {#sdk-api}

フロントエンドから `sdk.backend.*` 経由で呼び出せるバックエンド RPC 関数を登録します。

```js
// In backend init:
sdk.api.register("getScans", function(scanId) {
  return { scans: [] };
});

// Emit an event to connected frontends:
sdk.api.send("scan:complete", { scanId: 1, status: "ok" });
```

ハンドラーはフロントエンドから渡された引数を受け取ります（追加の `sdk` 引数は注入されません）。戻り値は JSON にシリアライズされ、呼び出し元に返されます。

フロントエンドは `sdk.backend.getScans(scanId)` でこれらを呼び出します。[フロントエンドプラグイン API](#frontend-plugin-api-sdk)をご覧ください。

`sdk.api.send` はプラグインごとのキューにイベントを追加します（最大 200 件）。フロントエンドは `sdk.backend.onEvent` でこのキューをポーリングします。

### `sdk.replay`, `sdk.projects`, `sdk.scope`, `sdk.workflows`, `sdk.matchReplace` {#sdk-replay-sdk-projects-sdk-scope-sdk-workflows-sdk-matchreplace}

これらは読み取り専用のクエリ名前空間です。メソッドの完全なシグネチャは[バックエンド SDK リファレンス](./backend-sdk.md)をご覧ください。

### リクエストクラス {#request-classes}

**`RequestSpecRaw`**：インターセプトされたリクエストを表します。`onInterceptRequest` でこのオブジェクトを受け取ります。

```js
spec.getMethod()          // > string
spec.setMethod("POST")
spec.getHost()            // > string
spec.setHost("example.com")
spec.getPort()            // > number
spec.getPath()            // > string
spec.setPath("/new/path")
spec.getQuery()           // > string
spec.getTls()             // > boolean
spec.getHeaders()         // > Record<string, string[]>
spec.setHeader("X-Foo", "bar")
spec.getBody()            // > Body | null
spec.setBody("new body")
spec.getRaw()             // > Uint8Array (raw bytes) or []
spec.setRaw(bytes)        // set raw bytes

// Create a new spec from a URL string:
var spec = new RequestSpecRaw("https://example.com/path?q=1");
```

**`Request`**：キャプチャされた読み取り専用のリクエスト（`sdk.requests.get` から取得）。

```js
req.getId()
req.getMethod()
req.getHost()
req.getPort()
req.getTls()
req.getPath()
req.getQuery()
req.getUrl()           // > full URL string
req.getHeaders()       // > Record<string, string>
req.getHeader("name")
req.getBody()          // > Body | null
req.getCreatedAt()     // > Date
req.toSpec()           // > RequestSpec (mutable copy)
```

**`Response`**：キャプチャされた読み取り専用のレスポンス。

```js
res.getCode()            // > number (HTTP status)
res.getHeaders()         // > Record<string, string>
res.getHeader("name")
res.getBody()            // > Body | null
res.getRoundtripTime()   // > number (ms)
res.getCreatedAt()       // > Date
```

**`Body`**:

```js
body.toText()    // > string
body.toJson()    // > parsed object or null
body.toRaw()     // > Uint8Array
body.length      // > number (original size, may differ from toText() if truncated)
```

***

## フロントエンドプラグイン API（sdk） {#frontend-plugin-api-sdk}

フロントエンドプラグインのコードは、`/plugins/{id}/ui` から読み込まれるサンドボックス化された iframe で実行されます。iframe は `postMessage` で Ogma ホストと通信し、ホストがバックエンドへの呼び出しを仲介します。

SDK は `window.ogmaSDK` から利用できます。`ogmaSDK.ready(cb)` を呼び出すと、ホストブリッジが確立された後に、使用可能な SDK を受け取れます。

```js
ogmaSDK.ready(function(sdk) {
  // sdk is the live SDK - safe to call any method here
  sdk.log.info("frontend ready");
});
```

すべての SDK メソッドは Promise を返します。

### `sdk.log` {#sdk-log}

```js
sdk.log.info("message")
sdk.log.warn("message")
sdk.log.error("message")
```

### `sdk.meta` {#sdk-meta-1}

```js
var meta = await sdk.meta.get();
// { pluginId, packageId, name, version, ogmaVersion }
```

### `sdk.requests` {#sdk-requests-1}

```js
var entry = await sdk.requests.get({ id: "entry-id" });
var result = await sdk.requests.search({ limit: 20, offset: 0, query: "host:example.com" });
```

`read_http_history` 権限が必要です（自動的に付与され、ユーザーの承認は不要です）。

### `sdk.findings` {#sdk-findings-1}

```js
var page = await sdk.findings.list({ limit: 20, offset: 0 });
```

`read_findings` 権限が必要です（自動付与）。

### `sdk.scope` {#sdk-scope}

```js
var scope = await sdk.scope.getActive();
```

### `sdk.projects` {#sdk-projects}

```js
var project = await sdk.projects.getCurrent();
```

### `sdk.backend`：バックエンド RPC {#sdk-backend-backend-rpc}

バックエンドで `sdk.api.register` により登録した関数を呼び出します。

```js
// Call a named backend function
var result = await sdk.backend.call("getScans", [scanId]);

// Poll for backend-emitted events (sdk.api.send on the backend side)
var { events, next_since } = await sdk.backend.poll(since);
// events: [{ event: "scan:complete", args: [...] }]

// Register an event listener (uses polling internally)
sdk.backend.onEvent("scan:complete", function(data) {
  console.log("scan done", data);
});
```

`sdk.backend.onEvent` は内部で 2 秒間隔のポーリングループを使用します。返された購読解除関数を呼び出すと、監視を停止できます。

```js
var unsub = sdk.backend.onEvent("scan:complete", handler);
// later:
unsub();
```

### `sdk.navigation` {#sdk-navigation}

```js
await sdk.navigation.addPage("/my-plugin", { title: "My Plugin" });
```

ナビゲーションページを登録します。現在はホストが操作を受け付ける段階です。ルーターへの完全な統合は開発中です。

### `sdk.sidebar` {#sdk-sidebar}

```js
await sdk.sidebar.registerItem("My Plugin", "/my-plugin", { icon: "puzzle" });
```

サイドバー項目を登録します。現在はプラグイン UI パネル内に限定されています。グローバルサイドバーのスロットとの接続は開発中です。

### `sdk.commands` {#sdk-commands}

```js
await sdk.commands.register("my-plugin:scan", {
  name: "Scan with My Plugin",
  handler: function(context) { /* ... */ }
});
```

ホストが操作を受け付けます。コマンドパレットへの統合は開発中です。

### `sdk.menu` {#sdk-menu}

```js
await sdk.menu.registerItem({
  type: "Request",
  commandId: "my-plugin:scan",
  leadingIcon: "shield"
});
```

ホストが操作を受け付けます。コンテキストメニューへの追加は開発中です。

### `sdk.window` {#sdk-window}

```js
sdk.window.showToast("Scan complete", { variant: "success", duration: 3000 });
```

プラグインパネルにトースト通知を表示します。種類：`info`、`success`、`warning`、`error`。

### `sdk.ui` {#sdk-ui}

```js
sdk.ui.resize(600);                              // request iframe height change
sdk.ui.sidebar.registerItem("name", "/path");    // alias for sdk.sidebar.registerItem
```

***

## 権限 {#permissions}

権限は `manifest.json` で宣言します。

```json
{
  "permissions": ["send_requests", "write_findings"]
}
```

### 自動付与される権限（ユーザーの承認不要） {#auto-granted-permissions-no-user-approval-needed}

次の権限は、インストールされたすべてのプラグインに常に付与されます。

| 権限 | 許可される操作 |
|------------|----------------|
| `read_http_history` | `sdk.requests.get`, `sdk.requests.search` |
| `read_findings` | `sdk.findings.get`, `sdk.findings.list` |
| `read_scope` | `sdk.scope.getActive` |
| `read_projects` | `sdk.projects.getCurrent`, `sdk.projects.list` |
| `plugin_storage` | `sdk.storage`, `sdk.fs`, `sdk.path` |

### 保護された権限（ユーザーの承認が必要） {#protected-permissions-require-user-approval}

これらはマニフェストで宣言し、権限タブからユーザーが明示的に許可する必要があります。

| 権限 | 許可される操作 |
|------------|----------------|
| `send_requests` | `sdk.requests.send`：外部への HTTP リクエスト送信 |
| `write_findings` | `sdk.findings.create`, `sdk.findings.update` |

保護された権限を宣言するプラグインを有効にすると、ユーザーに確認が表示されます。また、権限タブからいつでも権限を付与または取り消せます。

***

## プラグインパッケージの構成 {#plugin-package-structure}

プラグインパッケージはローカルディレクトリからインストールできます。ブラウザーでの操作では、エクスポートされた `.zip` からもインストールできます。

```
my-plugin/
  manifest.json           - required
  backend/
    script.js             - bundled backend JS (ES2020)
  frontend/
    script.js             - bundled frontend JS
    style.css             - optional CSS
    assets/               - static assets (images, fonts, etc.)
```

### バックエンドスクリプトの要件 {#backend-script-requirements}

* 単一の自己完結した JS ファイルである必要があります。
* `require()` と動的な `import()` はサポートされません。
* `init(sdk)` 関数をエクスポートする必要があります（またはグローバル関数として定義します）。
* QuickJS がサポートする ES2020 のサブセット：`async/await`、`Promise`、`Map`、`Set`、`Symbol`、`Proxy`、`Date`、`RegExp`、`JSON`。`fetch` と `Buffer` は利用できません。
* サポートされる静的 import はプラグインの前処理で解決されます：`@ogma/sdk`、`crypto`、`fs`、`path`。
* ファイルサイズの上限：256 KB。

### フロントエンドスクリプトの要件 {#frontend-script-requirements}

* サンドボックス化された iframe 内で実行されます。`connect-src: 'self'` が許可されているため、プラグインは `/plugins/{id}/api/*` に POST し、`/plugins/{id}/events/poll` をポーリングできます。
* CSP：`default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'`。
* iframe のサンドボックスには `allow-same-origin` がないため、プラグインは Ogma の親 DOM や Cookie にアクセスできません。
* SDK にアクセスするには `ogmaSDK.ready(cb)` を使用してください。コールバックが呼ばれる前に SDK メソッドを呼び出さないでください。

***

## Ogma 向けプラグインのビルド {#building-a-plugin-for-ogma}

バックエンドは単一のバンドル済み JS ファイルである必要があるため、インストール前に TypeScript/ES モジュールのソースをバンドルしてください。

推奨ツールチェーン：

```bash
# Install dependencies
pnpm install

# Bundle backend (outputs a single CJS/IIFE file):
esbuild packages/backend/src/index.ts \
  --bundle \
  --platform=neutral \
  --format=iife \
  --global-name=_plugin \
  --outfile=dist/backend/script.js \
  --external:@ogma/sdk --external:@ogmabox/ogma-sdk

# Bundle frontend:
vite build packages/frontend --outDir ../../dist/frontend
```

Caido の開発ツールチェーン（`@caido-community/dev`）を使用する場合は、`caido-dev build` を実行し、ルートに `manifest.json` を配置した Ogma 互換の構成に出力をコピーしてください。

***

## プラグインのインストール {#installing-a-plugin}

1. 左サイドバーの**プラグイン**を開きます。
2. **インストール**をクリックします（インストール済みタブの上部）。
3. デスクトップアプリでは、**参照**をクリックして OS 標準のフォルダー選択画面を開きます。ブラウザーでは、サーバー側のプラグインディレクトリへのフルパスを入力します。
4. **検証**をクリックし、マニフェストとファイル一覧を確認します。
5. 検証に合格したら、**インストール**をクリックします。
6. 一覧でプラグインを選択し、**有効にする**をクリックします。
7. プラグインが保護された権限を宣言している場合は、有効化する前に**権限**タブで内容を確認して許可してください。

***

## トラブルシューティング {#troubleshooting}

**プラグインの初期化が通知なく失敗する：** **ログ**タブを確認してください。よくある原因は次のとおりです。

* `sdk.meta.path()` を呼び出したが、データディレクトリを作成できなかった。
* `init()` 内の未処理の例外。
* `sdk.*` 呼び出しの欠落またはスペルミス。

**フロントエンドが空白になる：** ブラウザーコンソールで CSP 違反を確認してください。フロントエンドスクリプトが SDK メソッドにアクセスする前に `ogmaSDK.ready(cb)` を呼び出していることを確認します。

**`sdk.requests.send` が `Permission denied` をスローする：** マニフェストで `send_requests` 権限を宣言し、かつユーザーが権限タブで許可する必要があります。

**`sdk.api.register` の関数をフロントエンドから呼び出せない：** バックエンドはインストールだけでなく、有効化されている必要があります。関数名はフロントエンドが `sdk.backend.call` に渡す名前と完全に一致する必要があります（大文字と小文字を区別）。

**互換性の警告がエラーとして表示される：** 動作を妨げるものではありませんが、API の対応範囲に不足があることを示しています。対応状況は上記の SDK 対応表をご覧ください。
