SKILL.md
embed-cli 概要
embed-cli とは
Embed サーバーと user が書いた solution コードを同期する CLI。 user は TypeScript を書き、push / pull でローカルとサーバーの間を往復させる。
所有境界
| パス | 所有者 | user が編集してよいか |
|---|---|---|
main.ts |
user | はい — solution の本体コード |
main.ts から import しているファイル |
user | はい |
generated/types.ts |
サーバー | いいえ — サーバーが自動生成 |
solution.lock |
サーバー生成 / ユーザー選択 | いいえ — GUI 設定のスナップショット。手編集しない(詳細は ## GUI で設定するもの) |
deno.json |
共有 | はい — ファイル全体(下の注を参照) |
.embed/ |
CLI | いいえ — CLI の状態(manifest、base snapshot) |
deno.json は imports / tasks / compilerOptions など全体を編集してよい。例外は compilerOptions.types の "./generated/types.ts" エントリで、これはサーバーが初回生成した型定義ファイルへのポインタ。この1エントリは消さない(消すと solution 固有の型が読み込まれない)。
Solution コードの形状
solution は Deno プロジェクトで、エントリポイントは main.ts のみ。workflow 関数を export する。
import type { TriggerOutput, WorkflowContext } from "@anyflowinc/embed-types";
export async function workflow(
triggerOutput: TriggerOutput,
context: WorkflowContext,
): Promise<void> {
// user code
}
TriggerOutput と EndUserInputs は solution ごとに generated/types.ts で TypeScript の declaration merging によって拡張される:
// generated/types.ts (auto-generated, do not edit)
declare module "@anyflowinc/embed-types" {
interface TriggerOutput {
slackChannelId: string; // solution-specific
}
}
triggerOutput や EndUserInputs の 具体的な形状(プロパティ名・型)は generated/types.ts を読む(推測でコードを書かない)。中身は GUI の solution.lock(trigger / end-user 変数定義)から派生する。サーバー内部の型生成までは追わない — user に見える単位はこの 2 interface。
引数からの値の読み方
triggerOutput(第 1 引数): トリガーの出力。型はTriggerOutput。context(第 2 引数, 型WorkflowContext): end-user 入力と実行メタを持つ。end-user がウィザードで入力した値はcontext.endUserInputsから読む(型はEndUserInputs)。contextはこのほか実行メタ(job_idなど)も持つ。
WorkflowContext の完全な定義は @anyflowinc/embed-types を参照。アクセス経路もプロパティ名も推測で書かない。
トリガーの実行粒度
workflow() は トリガーの発火 1 回につき 1 回呼ばれる。特に polling トリガーは、検知したレコード 1 件ごとに workflow() を 1 回実行する(複数レコードをまとめて 1 回ではない)。したがって workflow() は 単一レコードを処理する前提で書く — triggerOutput も 1 レコード分。複数レコードを 1 回でバッチ処理するコードを書かない。
CLI コマンド早見表
| コマンド | 用途 |
|---|---|
embed login |
ブラウザ経由の OAuth ログイン |
embed pull [solution-id] |
サーバーから solution を取得し、ローカルと 3-way merge |
embed push |
ローカルの変更をサーバーに送る |
embed libs |
利用可能な @anyflowinc/* クライアントパッケージの一覧 |
embed me |
ログイン中のユーザーを表示 |
embed logout |
認証情報をクリア |
embed version |
CLI のバージョン表示 |
embed help |
ヘルプを表示(global のみ。コマンド個別詳細は未実装) |
--help / -h をフラグとして渡しても同じヘルプが表示される。 --verbose でデバッグログを有効化する。
典型フロー: login → pull → edit main.ts → push。
制約(要点のみ)
- symlink は push/pull で未サポート。symlink を使ったファイル共有は提案しない
- バイナリファイルは 3-way merge できない。conflict は手動解決が必要
詳細は embed-constraints および embed-troubleshooting skill を参照。
実行環境の制約
- runtime 内部から外向きに観測する手段は事実上無い:
console.log/console.error等の stdout/stderr 出力、throw した例外メッセージ、戻り値、いずれも agent から直接観測できる経路には届かない - debug したい値があれば app client を介して外部に出力する 経路を取る(例:
@anyflowinc/slack-botで値を Slack に post すれば、そこから確認できる)。利用可能な app client はembed libsで確認する - 失敗は throw で表す: catch されなかった例外はその job を FAILED にする(失敗を表す正規の手段)。正常 return は SUCCEEDED 扱いなので、握りつぶして return すると失敗が成功になる。「エラー時どうするか」を user に問う前に、この既定動作を前提にする
- リランはトリガー起点の新規実行: FAILED job はウィザード / ベンダー管理画面から手動リランできるが、途中ステップからの再開ではなく常にトリガー起点でやり直す。すでに発生した副作用(外部 API への書き込み等)は再実行で二重に起きうるので、throw する位置はこれを踏まえる
用語とドメインモデル
embed の中心はドメイン概念で、その概念名は GUI ラベルでも user の言葉でもほぼそのまま、コード上の型がその投影。GUI 画面はエージェント(このスキルを読むあなた)からは見えないので、概念ごとに「コードでの現れ方」を対応づけて持っておく。user に GUI 作業を依頼するときは、コード側の名前ではなく概念名(= GUI ラベル)で言う(「EndUserInputs を足して」ではなく「エンドユーザー変数を追加して」)。GUI そのものの詳細はベンダー向けドキュメント(https://docs-embed.anyflow.jp)に委ねる。
| 概念(GUI ラベル) | コードでの現れ方 | メモ |
|---|---|---|
| トリガー | workflow() の起点 / triggerOutput |
種別: webhook / polling / schedule / click / my_event / request |
| エンドユーザー変数 | EndUserInputs のプロパティ(context.endUserInputs で読む) |
各変数に DataType がある。下記参照 |
| ウィザード | コード非対象(solution.lock 側) |
end-user 向けインストール UI。画面 + ウィジェットで構成 |
| ウィジェット | エンドユーザー変数の値の形に反映 | end-user 入力の UX。下記参照 |
| コネクション | app client が透過的に使う | コードに認証処理は書かない |
エンドユーザー変数(と DataType)
ウィザードで end-user に入力させる値。ベンダーが GUI で変数を定義し、その 変数名がそのまま EndUserInputs のプロパティ名になる。各変数には DataType(型)があり、これが値の形を決める。
- 変数名は TypeScript のプロパティとして表現できる範囲なら自由(英語必須ルールは無い。user に英語名を勧めない)。
- 実際のプロパティ名・型は
generated/types.tsを読む(推測しない)。コードからはcontext.endUserInputsで読む(## Solution コードの形状の「引数からの値の読み方」)。 - これは GUI で定義するもの。「入力項目を追加したい」はコード(
main.ts)では対応できず GUI 作業になる。
ウィジェット
ウィジェットはウィザード上の end-user 入力 UI。エンドユーザー変数の DataType が主で、その型が使えるウィジェットを制限する(ウィジェットが型を決めるのではない。例: 「マッピング」型は マッピングウィジェット専用)。どのウィジェットでも設定値は EndUserInputs に入り、コードからは context.endUserInputs で読む。user が「end-user に〜を入力させたい」と言ったら、目的に合うウィジェットを薦められる。代表例:
| ウィジェット | 用途 | 値の形(目安) |
|---|---|---|
| テキスト / パスワード | 自由入力 / 秘匿入力 | 文字列 |
| セレクト | 固定選択肢から 1 つ | 単一値 |
| アシスト | 接続先 API の候補から 1 つ選ぶ | 単一値(多くは ID。表示ラベル ≠ 保存値) |
| 複数選択アシスト | API 候補から複数選ぶ | ID の配列 |
| テーブルアシスト | API 由来の表から行を選ぶ | 識別子の配列 |
| チェックボックス | 定義済み選択肢から複数 | 値のリスト |
| マッピング | 2 アプリのフィールド対応(左 → 右) | 行の集まり(「マッピング」型専用) |
ここに無いもの(コンディション・CSV 系などニッチなもの含む)を含む全一覧・各ウィジェットの詳細は https://docs-embed.anyflow.jp/wizard-editor/widget.md。「値の形」は目安で、コードで使う実際の型は generated/types.ts が正本(推測で型を書かない)。
GUI で設定するもの (solution.lock)
solution は 処理部分(コード)と 定義部分(GUI で設定)で構成される。定義部分は solution.lock というスナップショットファイルに表現される。
GUI には 2 つのエディタがある。user に GUI 作業を依頼するときはどちらかを示す:
- ソリューションエディタ: トリガー / エンドユーザー変数 / ロジック(処理の定義)
- ウィザードエディタ: ウィザード画面 / ウィジェット(end-user 向けセットアップ UI)
GUI で設定するもの:
- trigger: どのアプリのどの trigger を起点にするか、その出力スキーマ(
triggerOutputの形) - エンドユーザー変数: solution を導入する end-user に入力させる値(
EndUserInputsの形。詳細は## 用語とドメインモデル) - wizard 画面: end-user 向けセットアップ UI(画面構成・ウィジェット・ラベル)
- OAuth 認証: 利用するアプリの認証スコープと vendor connection
solution.lock はサーバー生成。ユーザーが直接手編集するファイルではない。git 管理対象で、過去状態に git revert してから embed push するとサーバー側の定義部分もその時点に戻せる。
ユーザーが「trigger を変えたい」「入力項目を追加したい」「ウィザード文言を編集したい」と言ったら GUI 側の作業。コード(main.ts)では対応できない。
アプリクライアント
@anyflowinc/<name> クライアントは Deno の npm: specifier で扱う。
追加方法
embed libsで対象クライアントが一覧にあることを確認する。embed libsはembed login済みであることが前提(未ログインだと認証エラーで exit 3)。各クライアントをnpm:@anyflowinc/<name>@<version>形式で出力する(<version>は利用可能なバージョンの参考)。一覧に無い名前は存在しない — 推測した名前をdeno addしない(private registry への解決に失敗する)。- 表示された名前で追加する:
`` deno add npm:@anyflowinc/<name> ``
deno.json の imports に npm:@anyflowinc/<name>@... として入り(バージョンは deno が解決して固定する)、実体は node_modules/@anyflowinc/<name>/ に展開される。特定バージョンに固定したいときは embed libs の表示どおり deno add npm:@anyflowinc/<name>@<version> と渡す。
- 解決できたか確認する(
deno addの exit 0 だけで成功と見なさない)。deno.jsonのimportsにnpm:@anyflowinc/<name>@...エントリが入り、node_modules/@anyflowinc/<name>/reference.mdが存在すること。未ログインや.npmrcの欠落・破損があるとここで解決に失敗する。
npm install/npm iは使わない。 このプロジェクトにpackage.jsonは無く、依存はdeno.jsonのimportsで管理する。npm installでは import map が更新されず、main.tsの import は解決しない。node_modules/に実体が出るのは Deno の npm: specifier の挙動であって、npm で管理しているからではない。- クライアントは公開 npm ではなく GCP Artifact Registry 上の private npm registry に publish されている。解決にはプロジェクトに同梱される初期ファイル
.npmrc(@anyflowinc:registryとその認証トークンを設定)が必要。.npmrcは消さない。
API リファレンス
main.ts が @anyflowinc/<name> を import している場合、その API リファレンスは パッケージ同梱の reference.md を参照する(node_modules/@anyflowinc/<name>/reference.md)。class 名・引数なし constructor・client.<group>.<method>(params) の呼び出し方・各 method の params と戻り値 shape が記載されている。
class 名は概ね toPascalCase(<service-name>) で導出されるが、推測でコードを書かず、必ず reference.md で実名と method shape を確認すること(hallucinated な class 名・method 名を main.ts に書き込まない)。
reference.md のファイル先頭近くには 「API インデックス」 セクションが必ず置かれている(method 数に関わらず全 client で常時存在)。インデックスは namespace group ごとの 2 階層ネスト bullet で、group 見出しの下にその group の各 method が client.<group>.<method>(params) — 説明 の 1 行ずつ並ぶ。目的の method はインデックスで client.…(params) を grep して特定し、対応する method 詳細セクションだけを部分的に読む。method 数が多い client では reference.md 全体が型定義並みに大きくなるため、全文を一括で読み込む必要はない。