コンテンツへスキップ
CADCAMFUN
ドキュメント

ドキュメント

プラグイン

拡張機能マニフェスト、ワーカーホスト API、アシスタント ツール、および個別のブラウザー プラグインのインストール契約。

英語から自動翻訳されています。 英語の原文を見る

拡張サーフェスを選択してください

@cadcamfun/plugin-sdk ワーカープラグインコントラクトを定義します plugin.json マニフェストと API バージョン 1。そのホストは機能制御されたサービスを公開し、Bun ワーカーでプラグイン機能を実行します。 Production Assistant ローダーは現在、リクエストのみを行うプラグインを受け入れます。 ai:tool.

の /api/plugins HTTP ファミリは、別のブラウザ プラグイン コントラクトを使用します。ZIP パッケージには次のようなものがあります。 manifest.json, a main エントリ、ブラウザの権限、設定、および JavaScript ブートストラップ。その API を介してパッケージをインストールしても、そのパッケージはワーカー SDK プラグインにはなりません。意図した統合に一致するマニフェストとライフサイクルを使用します。

ワーカーSDKマニフェスト

アシスタント拡張機能の plugin.json
{
  "id": "com.example.shop-helper",
  "name": "Shop helper",
  "version": "1.0.0",
  "apiVersion": "1",
  "entry": "index.js",
  "capabilities": ["ai:tool"],
  "contributes": {
    "aiTools": [{
      "id": "lookup",
      "description": "Look up the shop's documented inspection checklist.",
      "kind": "read",
      "parameters": { "type": "object", "properties": {} },
      "entry": "lookup"
    }]
  }
}

識別子は逆引き DNS 形式を使用し、バージョンはセマンティックです major.minor.patch オプションのプレリリース付き、および apiVersion でなければなりません "1"。エントリ パスはプラグイン ディレクトリに対する相対パスです。コントリビューションでは、コマンド、パネル、ポストプロセッサ、操作、およびアシスタント ツールを宣言できます。コントリビューションを宣言しても、その機能がすべてのホストに接続されるわけではありません。

スキーマではオプションで signature そして dev フィールド。現在のワーカー ホストはこれらの値を記録しますが、インストール中に暗号化署名の検証を実行しません。署名フィールドだけでは、発行者が検証済みであることを証明するものではありません。

モジュールエントリーとAI機能

definePlugin マニフェストを持つモジュールの型付きアイデンティティ ヘルパーです。 activate(ctx)。アクティブ化するとクリーンアップ関数が返される場合があり、モジュールが公開する場合があります。 deactivate。 SDK では、これらのライフサイクル フックについて説明します。それらの可用性は、以下で詳しく説明するように、具体的なワーカー ブートストラップによって異なります。

アシスタント機能は、 aiTools 記録;貢献の entry そのレコード内の関数に名前を付けます。ツールの貢献には、その説明、JSON パラメーター スキーマ、および read または write 親切。書き込みツールは、埋め込みアシスタント内の人間の承認リクエストを通じてルーティングされます。外部ブリッジは、許可された読み取りプラグイン ツールのみを受け取ります。

ワーカー エントリを自己完結型モジュールとしてバンドルします。ワーカー ホストはエントリ ソースを読み取り、BLOB モジュールを通じてインポートします。相対的なパッケージまたはファイルのインポートは、ノード スタイルのファイル システム ローダーを想定するのではなく、別の統合の問題として扱います。

機能制御された API

SDKが認識するのは、 drawing:read, drawing:write, toolpath:read, storage, ui:panel、 そして ai:tool。図面の書き込みは図面の読み取りを意味します。一般的なホストは、インストールされたマニフェストから有効な機能を取得し、RPC でそれらをチェックします。埋め込みアプリケーションは、どのマニフェストとサーフェスをロードするかを決定します。

SDK コンテキストは、図面の読み取りとエンティティ パッチの追加/更新/削除、ツールパス/プログラムの読み取り、プライベート文字列のキーと値のストレージ、および宣言的なコマンド/パネルの登録を公開できます。欠落しているサービスはコンテキストに存在しません。ストレージキーの使用 plugin:<id>: 名前空間を削除し、絶対キーまたは親トラバーサル キーを拒否します。

Production Assistant ローダーがサポートするのは、 ai:tool。描画、ツールパス、ストレージ、または UI 機能を要求するマニフェストは、警告とともにスキップされます。これらのサーフェスをサイレントに許可したり、その広範な機能をアシスタント ホストにロードしたりすることはありません。

ロードとワーカーのライフサイクル

アシスタント拡張機能を有効にするには、その拡張機能を plugin.json および以下のバンドルエントリー <dataDir>/plugins/<id>/ そして識別子をリストします <dataDir>/plugins/enabled.json、JSON配列。フォルダー識別子はマニフェストと一致する必要があります。アシスタント プラグイン サービスの構築中に読み込みが発生します。プラグインが失敗すると、アプリケーションは停止せずに警告がログに記録されます。

一般的なホストは、インストール、有効化、無効化、アンインストール、コマンド呼び出し、AI ツール呼び出し、構成操作により、インストール済み、有効化、無効化、および障害の状態をサポートします。 Enable はワーカーを作成し、そのブートストラップ ハンドシェイクを完了します。無効にするとそれが破棄されます。アンインストールでは、ホストの挿入された依存関係を通じてレジストリ状態とストレージ名前空間が削除されます。

ワーカーは空の環境から開始され、ブートストラップによって選択されたグローバル API が削除されます。これはアプリケーション レベルの制限であり、オペレーティング システムのサンドボックスではありません。Bun グローバルは引き続きアクセス可能です。アプリケーション プロセス境界内での実行に適切な拡張コードのみを有効にします。

実行時間の制限

ホストは、256 MB のワーカー メモリ設定、2、000 回の 1 分あたりのコール数のレート制限、およびプラグインごとに最大 64 件の保留中のコールを定義します。コマンドのタイムアウト動作は、埋め込みホストによって提供されます。アシスタント ローダーは 10 秒を使用します。 AI ツールの呼び出しには、別途 30 秒の期限があります。タイムアウト、ツールの不在、ワーカーのクラッシュ、機能の障害、キューの枯渇により、型指定されたホストの障害または読み取り可能なツールの結果が返されます。

クラッシュ追跡では、10 分以内に 3 回クラッシュするとプラグインに障害が発生します。これらのホスト コントロールは、協調的な RPC とライフサイクルの動作を制限します。 Bun に渡されるメモリ設定は、個別に分離された実行可能ファイルの代わりにはなりません。

制限は次のように定義されています。 apps/sidecar/src/plugins/host.ts: CALL_TIMEOUT_MS (通常の呼び出しごとに 5、000 ミリ秒)、 AI_TOOL_TIMEOUT_MS (アシスタント ツール呼び出しごとに 30、000 ミリ秒)、 MEMORY_LIMIT_MB (256), CALL_RATE_LIMIT (プラグインごとに 1 分あたり 2、000 回の呼び出し)、 MAX_PENDING_CALLS (64)、および CRASH_LIMIT (3 は 10 分以内にクラッシュします CRASH_WINDOW_MS、その後プラグインは自動的に無効になります)。

ZIP インストールとブラウザ プラグイン API

POST /api/plugins/install マルチパートアーカイブを受け入れます。インストーラが読み込むのは、 manifest.json zIP ルートまたは単一の最上位ディレクトリから、ブラウザー プラグイン マニフェストを検証し、そのファイルを次の場所に保存します。 <dataDir>/storage/plugins/<id>/。ブラウザマニフェストの使用 main, engines.cadcam、SDK ではなくブラウザ許可リスト entry, apiVersion、および機能。

GET /api/plugins インストールされているエントリをリストします。詳細、有効化/無効化、アンインストール、更新、構成エンドポイントはレジストリ上で動作します。 /api/plugins/bootstrap.js?id=… javaScript を提供します。 /api/plugins/serve?id=…&file=… プラグインファイルを提供します。これらは、宣言されたコンテンツ タイプを持つテキスト/バイナリ操作です。

ローカル レジストリ ブラウザは、以下のパッケージ メタデータを読み取ります。 <dataDir>/plugins-registry。ローカルマニフェストカタログです。アーカイブのインストール、レジストリの一覧表示、ワーカー アシスタント拡張機能のロードは別の操作です。

SDKヘルパー

effectiveCapabilities, hasCapability、 そして missingCapabilities サポート能力検査。 requireCapability 要求されたコンテキスト サーフェスが存在しない場合、機能エラーが発生します。 storageNamespace そして storageKey 名前空間キーを構築します。 parseManifest jSON を検証します。 manifestKey iD/バージョンのペアをフォーマットします。

実装のエントリポイント

packages/plugin-sdk/src 公開マニフェスト、機能、コンテキスト、プラグイン、およびテストホスト API を所有します。 apps/sidecar/src/plugins/host.ts ワーカーのライフサイクルと RPC を所有します。 services/ai-plugins.ts 同意ファイルのロードとアシスタントの登録を所有します。ブラウザのインストールは次のように実装されています。 http/plugins.ts そして repos/plugin-registry-repo.ts.

拡張機能のレビュー

貢献スキームを正確に保ち、小さな構造化された結果を返し、読み取り/書き込み動作を正直に宣言します。実際の埋め込みホストに対してテストします。SDK の可用性は、描画、ストレージ、UI、またはポスト レジストリへの運用アクセスを確立しません。の スタンドアロンの nikcli プラグイン は HTTP アクション クライアントであり、ワーカーにロードされるコードとは異なる目的があります。

サンプルアシスタントモジュールを完了する

の plugin.json 上記の名前の例 aiTools.lookup。これは対応する自己完結型です index.js。最上位のモジュール コードはインポート時に実行されます。宣言された検索関数は呼び出されると実行されます。

index.js
export const aiTools = {
  lookup: async () => ({
    ok: true,
    checklist: [
      "Compare the drawing revision with the job traveller.",
      "Record the measuring instrument and its calibration status.",
      "Record each inspected dimension and the measured value."
    ]
  })
}

これらはサンプル工場での指示であり、組み込まれた検査基準ではありません。これらを拡張機能が実際に所有するコンテンツに置き換えます。次回のサービス初期化時にこのモジュールを許可するには、有効なファイルは次のとおりです。

<dataDir>/plugins/enabled.json
["com.example.shop-helper"]

実行中のカタログは、プラグイン ID とツール ID から派生した識別子を公開します。それを発見してください GET /api/ai/actions;ブラウザの ZIP レジストリがアシスタントの有効リストであると想定しないでください。アプリケーション/サービスを再ロードすることは、運用同意ファイルを再読み込みするための信頼できる方法です。このローダーによってファイルシステム ウォッチャーが構成されていません。

ソース: apps/sidecar/src/services/ai-plugins.ts, packages/ai/src/tools/plugin-tool.ts、 そして apps/sidecar/test/ai-plugin-tools.test.ts.

引数のスキーマとライフサイクルの制限

ツールのローカル ID は小文字で始まり、小文字、数字、またはアンダースコアを最大 40 文字まで含める必要があります。説明には 10 ~ 2、000 文字が含まれています。コントリビューションでは、空ではないエントリ関数と JSON パラメーター スキーマを指定する必要があります。アプリケーション アダプターは、ワーカーを呼び出す前に引数を検証します。拒否すると構造化されたメッセージが返される場合があります ok: false プラグインを終了せずに結果が得られます。

パブリック SDK では次のように説明されています。 activate、クリーンアップ、そして deactivate エントリーポイント。現在の Bun ワーカー ブートストラップのアクティブ化/非アクティブ化 RPC ハンドラーはライフサイクル メッセージを認識しますが、エクスポートされたモジュール関数は呼び出しません。したがって、アシスタント拡張機能は、呼び出し可能な動作を aiTools また、セットアップやクリーンアップのために SDK アクティベーション コールバックに依存することは避けてください。

一般的なワーカー ブートストラップには、プレースホルダー描画/ツールパス/UI ハンドラーも含まれています。これらは、実稼働図面や UI 統合を構成するものではありません。アシスタントローダーの制限事項 ai:tool これらのサポートされていないサーフェスが、動作するアプリケーション アクセスとしてアドバタイズされるのを防ぎます。

ソース: packages/plugin-sdk/src/manifest.ts, plugin.ts, apps/sidecar/src/plugins/host.ts (PLUGIN_BOOTSTRAP)、 そして services/ai-plugins.ts.

不足している拡張機能または失敗した拡張機能を診断する

  • カタログにツールがありません: 有効な有効な JSON、フォルダー/マニフェスト ID の正確な一致、マニフェストのデコード、および ai:tool 能力。スキップされたプラグインの起動警告を読みます。
  • マニフェストはサポートされていないサーフェスを要求します。 サポートされていない機能は、コードが本当に必要としない場合にのみ削除してください。アシスタント プラグインは、宣言しただけでは描画書き込みアクセスを取得できません。
  • いいえそのようなツール: 作る contributes.aiTools[].entry 以下のエクスポートされた関数と一致します aiTools。投稿 ID とエントリ名は異なる目的を果たします。
  • 引数の検証に失敗しました: jSON スキーマと必須フィールドを検査します。実際のワーカーとアプリケーションの統合テストで実証されているように、間違った値は実行前に拒否されます。
  • タイムアウトまたは障害状態: 作業のブロック、未解決の約束、またはクラッシュを調査します。クラッシュが繰り返されるとプラグインが故障します。原因を解決してから再度有効にしてください。
  • ZIP インストーラーがマニフェストを拒否します。 ブラウザプラグインを使用する manifest.json そのエンドポイントの契約。ワーカーの名前を変更する plugin.json フィールドは翻訳されません。

ソース: apps/sidecar/test/plugins-ai-tools.test.ts, ai-plugin-tools.test.ts, plugins-http.test.ts、 そして src/plugins/host.ts.

マニフェスト参照

すべてのプラグインには plugin.json. parseManifest(json) 1 ステップで解析および検証し、JSON 構文エラーをスキーマ エラーと同じエラー チャネルに変換するため、インストール画面は 1 つの失敗タイプのみを処理する必要があります。

分野ルール
id逆引き DNS 識別子: 少なくとも 2 つのドットで区切られたラベル内の 3 ~ 128 文字、小文字、数字、ハイフンなど com.example.gear-generator.
name空ではない表示名。
versionmajor.minor.patch、オプションで、その後に次のようなプレリリースサフィックスが続きます。 -beta.1.
apiVersion文字列でなければなりません "1";これは唯一のプラグイン API バージョンです。
entryプラグインフォルダーを基準とした ES モジュールの相対パス。
capabilities機能文字列の配列 (以下を参照)。閉じられたリストの外にあるものはすべて、明らかなエラーです。
contributesオプション commands, panels, postProcessors, operations、 そして aiTools.
signatureアーカイブ ハッシュに対する Ed25519 の署名。パッケージ化されたインストールに必要です。
devtrue ローカル開発プラグインをマークします。署名チェックはスキップされ、UI にバッジが表示されます。
貢献フィールド
commands[]id, title、オプション keybinding のような Mod+Shift+G.
panels[]id, title, entry: パネルの宣言ツリーを返すエクスポートされた関数。 DOM アクセスは決してありません。
postProcessors[]id, title, entry: エクスポートされた (program, options) => string 関数。
operations[]id, title, entry: パラメータ付きでエクスポートされたレコード schema そして純粋な generate(params, geometry, tool, stock) ツールパスを返します。
aiTools[]id マッチング ^[a-z][a-z0-9_]{0,39}$, description (10 – 2, 000 文字。モデルはそれを読み取ります)、 kind read または write, parameters (引数の JSON スキーマ)、および entry.

ヘルパー: manifestKey(m) 返品 id@version; requestedCapabilities(m) 表示用にソートされた機能リストを返します。 isDevPlugin(m) そして isSigned(m) インストール ポリシーの質問に答えます。

機能とプラグインのコンテキスト

プラグインが機能できるのは機能だけです。マニフェストはプラグインに必要なものを宣言し、ユーザーはサブセットを許可し、ホストは許可されたサーフェスを正確に含むコンテキストを構築します。各サーフェスは型内でオプションであるため、欠落している機能はコンパイル時と実行時の両方で表示され、サイレント no-op にはなりません。

能力コンテキストサーフェス許可します暗示する
drawing:readctx.drawing.read()開いている図面を読みます (または undefined どれも開いていないとき)。—
drawing:writectx.drawing.patch(patch)適用する AddEntity, UpdateEntity、 または RemoveEntity パッチ;新しいリビジョンを返します。drawing:read
toolpath:readctx.toolpath.list(), .program(id)ツールパスと G コード プログラムを読み取ります。—
storagectx.storage名前空間付きのキーと値の文字列: get, set, delete, keys.—
ui:panelctx.uiregisterCommand そして registerPanel (宣言のみ)。—
ai:toolなし (明示的な貢献)ツールをアシスタントに提供し、読み取り専用でスタンドアロンの nikcli に提供します。—
effectiveCapabilities(granted) 影響を解決するため、宣言するプラグイン drawing:write 宣言する必要はありません drawing:read。あらゆるコンテキストにも、 pluginId、解決済み capabilities、そして log と debug, info, warn、 そして error;ホストはログ行にプラグイン ID を接頭辞として付けます。使用 requireCapability(ctx, capability, surface) サーフェスを取得するか、失敗します CapabilityError 不足している付与の名前を指定します。

SDK テスト ホストを使用してプラグインをテストする

createTestHost サイドカーと同じ機能ルールを持つメモリ内ホストを構築します。許可された機能のみがコンテキストに表示され、図面、ツールパス、およびプログラムを指定できます。以下の例は完成しており、次のように実行されます。 bun ワークスペースで。
frame-plugin.test.ts
import { cad } from "@cadcamfun/core"
import { createTestHost, definePlugin, requireCapability, testManifest } from "@cadcamfun/plugin-sdk"

const manifest = testManifest({
  id: "com.example.frame",
  capabilities: ["drawing:write", "storage", "ui:panel"],
})

const plugin = definePlugin({
  manifest,
  activate(ctx) {
    const drawing = requireCapability(ctx, "drawing:write", "drawing")
    ctx.ui?.registerCommand({ id: "frame.add", title: "Add frame line" })
    ctx.storage?.set("runs", "1")
    drawing.patch({
      _tag: "AddEntity",
      entity: { ...cad.baseEntity("frame-1"), _tag: "Line",
                start: { x: 0, y: 0 }, end: { x: 100, y: 0 } },
    })                                   // returns revision 1
    return () => ctx.log.info("teardown") // runs on deactivation
  },
})

const host = createTestHost({ manifest, drawing: cad.emptyDrawing("d1", "Demo") })
const stop = await host.activate(plugin)
host.drawing()?.entities.length     // 1
host.context.storage?.get("runs")   // "1"
host.ui.commands                    // [{ id: "frame.add", title: "Add frame line" }]
await stop()

// Without the grant, requireCapability throws:
const denied = createTestHost({ manifest, granted: ["storage"] })
await denied.activate(plugin)
// CapabilityError: Plugin com.example.frame may not use "drawing:write":
// it was not granted at install time
  • testManifest(overrides) 最小限の有効なマニフェストから始まります (com.example.test-plugin、バージョン 1.0.0、API 1、エントリ index.js、機能はありません)。
  • granted デフォルトはマニフェストが要求するすべてのものです。拒否されたパスをテストするには、より小さいリストを渡します。
  • host.storage ホストの作成時に作成されたスナップショットです。ライブ値を読み取る host.context.storage.
  • host.on(commandId, handler) そして host.invoke(commandId, args) 登録されたコマンドを実行します。
  • runPostProcessor 投稿された投稿をプログラムに対して実行し、 keyOf(pluginId, key) は、テストで分離をアサートできるようにする名前空間ストレージ キーを示しています。

アプリケーションがアシスタント プラグインをロードする方法

  1. 起動時にサイドカーは次のように読み取ります <data dir>/plugins/enabled.json、プラグイン ID の JSON 配列。そこにリストされていない限り、何もロードされません。
  2. 読み取られるIDごとに <data dir>/plugins/<id>/plugin.json そしてそれを解析します。フォルダー名はマニフェストと同じである必要があります id.
  3. のみ ai:tool このホストに接続されています。描画、ツールパス、ストレージ、または UI へのアクセスを要求するマニフェストは、何も行わないサーフェスをロードするのではなく、警告とともにスキップされます。
  4. プラグインはワーカー ホストにインストールされ、有効になります。ログのロードに失敗するプラグイン Plugin <id> was not loaded: ... アプリケーションの起動を決して停止しません。
  5. 提供された各ツールは次のようにアシスタントに提供されます。 plugin_<slug of plugin id>_<tool id>、最大 80 文字。スラッグは ID を小文字にし、他の文字をアンダースコアに置き換えます。 com.example.bolt-circle 工具付き bolt_circle になる plugin_com_example_bolt_circle_bolt_circle.
  6. 引数は、ワーカーに表示される前に、ツールの JSON スキーマに対して検証されます。スキーマが 20、000 文字を超えている場合、オブジェクト スキーマではない場合、または変換できない場合、ホストは任意のオブジェクトを受け入れ、プラグインは検証する必要があります。
  7. プラグインがスロー、タイムアウト、またはプラグインがなくなっていることが判明した呼び出しは返されます。 { ok: false, error } ターンをクラッシュさせるのではなく、アシスタントに(最大 500 文字のエラー テキスト)を送信します。あ write ツールは、その引数が何であれ、電話をかける前に必ず担当者に確認されます。

オペレーティング システムのサンドボックスではありません

ワーカーには独自のプロセス、フェッチ、またはファイル ハンドルはありませんが、内部の Bun グローバルには引き続きアクセス可能です。信頼できるコードを持つプラグインのみを有効にし、書き込みツールは最小限に抑えます。

識別子とバージョンのルール、テスト済み

価値有効?ルール
com.example.gear-generatorはい逆引き DNS: 文字、数字、および小文字のセグメント -、少なくとも 2 つの 3 ~ 128 文字
a.bはい1 文字のセグメントが 2 つあれば十分です
gearいいえ少なくとも 1 つのドットが必要です
Com.Example.xいいえ大文字は使用できません
com.example_x.yいいえアンダースコアは使用できません
バージョン有効?
1.0.0はい
1.2.3-beta.1はい
1.0いいえ: 3 つの数字が必要です
v1.0.0no: プレフィックスなし
apiVersion 文字列でなければなりません "1"、存在する唯一のプラグイン API メジャー。 AI ツール ID は一致する必要があります ^[a-z][a-z0-9_]{0,39}$ アシスタントには次のように表示されます plugin_<plugin>_<id>;説明はモデルが読み取るため、10–2、000 文字である必要があります。

寄付の種類

鍵フィールド目的
commandsid, title、オプション keybinding (e.g. Mod+Shift+G)パレット内のコマンド
panelsid, title, entry指定された関数が返す宣言ツリーからレンダリングされたパネル。 DOM アクセスなし
postProcessorsid, title, entryA (program, options) => string 実行時に登録される投稿
operationsid, title, entry輸出された { schema, generate } 記録
aiToolsid, description, kind 読み取り|書き込み、 parameters (JSONスキーマ)、 entryアシスタントに提供されるツール。呼び出しの前に書き込みツールが確認される

その他のマニフェストフィールド: signature (アーカイブ ハッシュに対する Ed25519、パッケージ化されたインストールに必要)、および dev (ローカル開発プラグイン: 署名チェックをスキップし、バッジを表示します)。

機能の解像度、テスト済み

影響、付与の不足、ストレージ キー
import { effectiveCapabilities, missingCapabilities, storageKey, requireCapability } from "@cadcamfun/plugin-sdk"

effectiveCapabilities(["drawing:write", "storage"])
// ["drawing:read", "drawing:write", "storage"]   (write implies read; sorted)

missingCapabilities(["drawing:read", "toolpath:read", "ai:tool"], ["drawing:write"])
// ["toolpath:read", "ai:tool"]

storageKey("com.example.gear", "presets/m2")   // "plugin:com.example.gear:presets/m2"
storageKey("com.example.gear", "../x")
// RangeError: storage key "../x" may not escape the plugin namespace   (same for "/abs")

requireCapability(ctxWithoutDrawing, "drawing:read", "drawing")
// CapabilityError (_tag "CapabilityDenied"):
// Plugin com.example.gear may not use "drawing:read": it was not granted at install time
コンテキスト上の表面付与時に提示するメソッド
drawingdrawing:read または drawing:writeread() →絵を描いたり、 undefined; patch(p) → 新しいリビジョン、 AddEntity, UpdateEntity, RemoveEntity パッチ
toolpathtoolpath:readlist(), program(toolpathId)
storagestorageget, set, delete, keys;文字列値
uiui:panelregisterCommand, registerPanel
logいつもdebug, info, warn, error

3Dで

ワークスペース内のプラグイン パッケージ ドラッグして周回します · スクロールしてズーム · 右ドラッグしてパンします
All 13 workspace packages from their package.json files; each line is a workspace:* dependency (17 in total). Rows are dependency depth: packages with no workspace dependencies at the front, the sidecar, which depends on 8 of them, at the back. Orange boxes are apps. Highlighted: plugin-sdk (depends on core) and the nikcli plugin, both loaded by the sidecar.