フックは、Silverpoint がドキュメントの保存や書き出しを終えたときに Mac 上でコマンドを実行します。tokens.json をリポジトリにコピーする、書き出した Swift を整形する、書き出したファイルをステージする、といった使い方ができます。フックはイベントが終わったあとにメインアクターの外で実行され、きっかけになった保存や書き出しを止めたり失敗させたりすることはありません。フックが出力した内容と終了の仕方は、開いて確認できるログに残ります。
フックはあなた(またはあなたのチーム)が書いたコマンドです。ドキュメントのフックはほかの人から届いたかもしれないファイルに入っているので、あなたが許可するまで実行されません。信頼を参照してください。
| ソース | 場所 | 信頼 |
|---|---|---|
| ユーザ | ~/Library/Application Support/Silverpoint/hooks.json、または $SILVERPOINT_HOOKS_FILE が指すファイル | 信頼済み: あなたが書いたもの |
| 書類 | .silverpoint ドキュメントの hooks キー。チームのフックがファイルと一緒に持ち運ばれます | そのドキュメントのパスに対して許可するまで実行されません |
ほかの 2 つの場所も同じルールに従い、環境変数で移動できます(名前変更前の VELLUM_ の名前もフォールバックとして使えます)。
| 項目 | デフォルト | 上書き |
|---|---|---|
| ユーザのフックファイル | ~/Library/Application Support/Silverpoint/hooks.json | SILVERPOINT_HOOKS_FILE |
| 承認 | ~/Library/Application Support/Silverpoint/hook-approvals.json | SILVERPOINT_HOOK_APPROVALS_FILE |
| ログ | ~/Library/Application Support/Silverpoint/HookLogs/ | SILVERPOINT_HOOK_LOGS_DIR |
ユーザファイルの形式は {"hooks": [hook, ...]} です。ドキュメントはリストそのものを "children" の隣に "hooks": [hook, ...] として保存します。フックのないドキュメントには hooks キーそのものがないので、これまでとバイト単位で同じに保存されます。
1 つのイベントが両方のソースのフックに一致したときは、あなたのフックが先、ドキュメントのフックがあとに、書かれた順に 1 つずつ実行されます。無効にしたフック("enabled": false)はスキップされます。
{
"id": "sync-tokens",
"event": "afterExport",
"filter": ["tokens"],
"command": ["/bin/cp", "tokens.json", "../my-app/Sources/Design/tokens.json"],
"cwd": ".",
"env": { "MODE": "release" },
"timeout": 30,
"enabled": true
}
| フィールド | 意味 |
|---|---|
id | 必須。ファイル内で一意です。ログ、通知、list_hooks、環境変数でフックを示す名前になります。 |
event | 必須: afterSave、afterExport、afterExportCode のいずれか。 |
filter | 省略可能な文字列のリスト。afterExport では実行する書き出しの種類(image、pdf、code、tokens、designSystem、handoff、site)。afterExportCode では実行するコードターゲットの id(swiftui、html、compose、react、uikit など)。省略するとすべてが対象です。afterSave には指定できません。 |
command | 必須: argv、つまり空でない文字列の配列です。シェルでは実行されません。["/bin/echo", "$HOME"] は文字どおり $HOME と出力します。最初の項目はパスです。git のような名前だけの場合は PATH から探します(Dock から起動したアプリの PATH は短いので、フルパスか /usr/bin/xcrun を使うのがおすすめです)。 |
shell | true にすると、コマンドを /bin/sh -c で実行します。command[0] がスクリプトで、残りがその $0、$1... になります。デフォルトは false。 |
cwd | 作業ディレクトリ。デフォルトはドキュメントのあるフォルダです。相対パスはそのフォルダからの相対パス、絶対パス(または ~/...)はそのまま使われます。まだ保存していないドキュメントでは、あなたのフックはホームフォルダから実行されます。 |
env | 追加の環境変数(文字列)。Silverpoint の起動時の環境に追加されます。 |
timeout | 秒数で、0 より大きい値。デフォルトは 60。 |
enabled | デフォルトは true。 |
フックは 1 つずつ検証されます。検証を通らないフックは報告されてスキップされ、ほかのフックはそのまま実行されます。理由は次のとおりです。id がない、または重複している、event が不明、command が空または文字列でない、timeout が 0 より大きくない、filter の種類が存在しない(または afterSave に filter がある)、shell、cwd、env、enabled の型が違う。それぞれの問題には、ソース、フックの id または位置、理由が示されます: user hook #2 "typo": unknown event "afterSav" (use afterSave, ...)。ユーザファイルが有効な JSON でない(または {"hooks": [...]} でない)場合は 1 つの問題としてフックはなしになり、ドキュメントのフックには影響しません。問題は設定 ▸ 一般 ▸ フックと、list_hooks の errors に表示されます。
| イベント | 発生するとき | ペイロードの files |
|---|---|---|
afterSave | ファイルが書き込まれたあと。あなたが求めた保存、つまり保存、別名で保存、複製の最初の保存のとき。自動保存では発生しません(アプリはその場で絶えず自動保存するので、自動保存ごとにフックを実行すると常に動き続けることになります)。silverpoint call と mcp は、--run-hooks を付けると保存された編集ごとに発生させます。 | 保存されたパッケージ: [documentPath] |
afterExport | ファイルが書き込まれたあと。種類は filter で指定します: image(ファイル ▸ 書き出す… で PNG、JPEG、SVG として書き出したとき、フレームをPNGとして書き出す、export ツール、インスペクタの <名前>を書き出す… と、レイヤーの PNG、JPG、SVG のプリセットを書き出す export_presets。まとめて 1 回の書き出しです)、pdf(書き出す… で PDF として書き出したとき、デッキをPDFとして書き出す、PDF のプリセットを書き出す <名前>を書き出す… と export_presets)、code(コードを書き出す)、tokens(トークンの形式を選んだコードまたはトークンを書き出す、export_tokens)、designSystem(デザインシステムパッケージとして書き出す、export_design_system)、handoff(開発者向けハンドオフを書き出す、export_handoff と hand_off)、site(ウェブサイトのテーマとそのコンテンツ。ファイル ▸ ウェブサイトを公開… の公開で、ビルドの前に 1 回。mode: apply を付けた export_site。silverpoint publish はフックを実行しません) | 書き込まれたファイル |
afterExportCode | 種類が code の afterExport でも、あわせて発生します。コードターゲットを持ち、それで絞り込めます | 書き込まれたファイル |
知っておきたい点:
path を指定しない export_tokens と export_code はテキストを返すだけで何も書き込まないので、何も発生しません。render と score コマンドがフックを発生させることはありません。files に入ります。hand_off の files は、バンドルの written リストを絶対パスにしたものです(ツール自身のリストはバンドルからの相対パス)。tokensPath はバンドルの tokens.json です。export_handoff の tokensPath はその tokens.json です。どちらも handoff の書き出しです。DesignTools)が通知するので、アプリ内のエージェントや CLI も、メニューコマンドと同じイベントを発生させます。stdin には JSON オブジェクトが 1 つ渡されます(スラッシュはエスケープされず、キーはソートされます)。
{
"event": "afterExportCode",
"kind": "code",
"documentPath": "/Users/me/Design/Pulse.silverpoint",
"files": ["/Users/me/app/Design.swift"],
"codeTarget": "swiftui",
"tokensPath": null,
"hookId": "format-swift"
}
kind は書き出しの種類(保存では null)、codeTarget はコードの書き出しで、tokensPath はトークン、デザインシステム、ハンドオフの書き出しで設定されます。documentPath は、まだ保存していないドキュメントでは null です。当てはまらないフィールドは null になります。
同じ値が環境変数にも入ります。常に設定され、当てはまらないときは空です。
| 変数 | 値 |
|---|---|
SILVERPOINT_HOOK_EVENT | afterSave、afterExport、afterExportCode のいずれか |
SILVERPOINT_HOOK_KIND | 書き出しの種類 |
SILVERPOINT_HOOK_DOCUMENT | ドキュメントのパス |
SILVERPOINT_HOOK_FILES | ファイル。1 行に 1 つ |
SILVERPOINT_HOOK_CODE_TARGET | コードターゲットの id |
SILVERPOINT_HOOK_TOKENS_PATH | トークンファイル |
SILVERPOINT_HOOK_ID | フックの id |
これらは Silverpoint の起動時の環境とフックの env に追加され、SILVERPOINT_HOOK_* の値が優先されます。
timeout より長く動いたフックには SIGTERM が送られ、2 秒後に SIGKILL が送られて、timed out として記録されます。シグナルが送られるのはフック自身のプロセスだけです。shell: true の場合、スクリプトがバックグラウンドで起動したプログラムはスクリプトより長く残ることがあります。a/Pulse.silverpoint と b/Pulse.silverpoint には別々のログがあります)。実行のたびにエントリが追記されます。時刻、フックの id、ソースとイベント、argv、終了ステータス(exit 0、exit 1、signal 9、timed out、failed to launch: ...)、stdout と stderr(それぞれ先頭 256 KB)です。設定 ▸ 一般 ▸ フック ▸ ログを表示 で、最前面のドキュメントのログが開きます。フックは、あなたの権限で Mac 上で実行されるコマンドです。そのため:
shell、cwd、env、timeout、そしてどのフックがどの順にあるかです。enabled は含みません。そのため、フックをオフにしてまたオンにしても承認はそのままです。どれか 1 つのフックを編集したり、フックを追加、削除、並べ替えたりすると、そのドキュメントのすべてのフックが未承認になり、もう一度許可するまで実行されません(変更していないフックも止まります)。別のパスにあるコピーや、移動や名前変更のあとの同じフックは承認されていません(新しいパスには新しい承認が必要で、何も引き継がれません)。承認ファイルはドキュメントのパスごとに 1 つのエントリを持ちます: {"approvals": {"/path/to/Doc.silverpoint": "<sha256>"}}。silverpoint call と silverpoint mcp は、--run-hooks を付けない限りフックを実行しません。 付けた場合も、実行するのはあなたのフックと、アプリで承認したドキュメントのフックだけです。フラグがなければ、承認ファイルやログを読み書きすることはありません。list_hooks は読み取り専用です。各フックの id、event、filter、command、shell、cwd、timeout、enabled、source(user または document)、approval(trusted、approved、notApproved)と、検証を通らなかったフックを返します。エージェントはあなたが追加するフックを提案できますが、自分で追加することはできません。設定 ▸ 一般 ▸ フックには、最前面のドキュメントで有効なフックが ユーザ または 書類 のバッジ付きで一覧され、ドキュメントのフックにはそのドキュメントのフックが承認済みかどうか(1 つの状態を共有します)が表示されます。フックを追加… でフックフォームが開きます。各行には 編集(同じフォームに値が入った状態)、今すぐ実行、複製、削除 とスイッチがあります。Finderに表示 はあなたのフックファイルを、ログを表示 はドキュメントのログを、確認… は承認シートを表示します。検証を通らなかったフックは行の下に一覧されます。このグループが一般にあるのはそのカードに余裕があったからで、ドキュメントのフックはスキルのカードと同じ最前面のドキュメントを使います。
フォームにはフックのすべてのフィールドがあります。
command の 1 項目として保存され、シェルが展開します。_ で、数字で始まらないこと。それぞれ 1 回だけ設定でき、値は空でもかまいません)、タイムアウト(1 から 3600 秒、デフォルトは 60)と オン。/opt/tools/xcrun swift-format なら xcrun)、リストの中で一意になるようにします(echo、echo-2…)。フックを追加している間はコマンドに合わせて変わり、保存したあとは変わりません。各フィールドには問題がその横に表示され、問題がなくなるまで 保存 は無効のままです。フックが検証を通るまで何も書き込まれません。新しいフックは id、event、command と、デフォルトから変更したフィールドだけを書き込みます。編集は書かれているとおりのフックから始まるので、フォームが知らないキー(note や独自のキー)は残り、変更していないフックを保存しても何も書き込まれません。JSONとして編集… はフォームを閉じ、あなたの hooks.json をデフォルトのアプリで開きます(まだなければ {"hooks": []} として作成されます)。ドキュメントのフックの場合は、ドキュメントのフックを JSON 配列としてシートで開き、1 回の取り消し操作として保存します。フォームで表示できないフック(引数付きのシェルスクリプト、command の項目が 2 つ以上ある shell: true)は、その説明と JSONとして編集… だけで開きます。
コマンド行のルール。 行は、シェルの小さく予測しやすい一部と同じやり方で単語に分割され、何も展開されません。
'$HOME' は $HOME)。\"(ダブルクォート)と \\(バックスラッシュ)だけがエスケープになる部分を始めます。それ以外のバックスラッシュは次の文字と一緒に残ります("a\nb" は a\nb の 4 文字)。x\ y は 1 つの単語 x y)。a"b c"d は ab cd)、'' や "" は空の単語です。$ の展開、グロブ、~、パイプはありません。それらには シェルで実行 が必要です。フックの argv も同じやり方で表示されます。空白、引用符、バックスラッシュ、$、バッククォートを含まない単語はそのまま、それ以外はシングルクォートで囲み(中の ' は '\'' と書きます)、行が必ず同じ argv に分割し直せるようにします。
複製 は、フックのすぐあとにコピーを置きます。id 以外は書かれているとおりです(<id>-copy、続いて <id>-copy-2…)。削除 は、フックとそのコマンドを示して先に確認します。スイッチは enabled を書き換えます。
あなたのフックは hooks.json に丸ごと書き込まれます({"hooks": [...]}、整形済み、キーはソート、アトミックに置き換え。フォルダやファイルがなければ作成されます)。ほかのフックは、検証を通らないものも含めて書かれているとおりに残ります。設定が何かを失わずに書き戻せないファイルは拒否され、そのまま残ります。コメントがあるファイルや JSON でないファイル、{"hooks": [...]} でないファイル、hooks の隣に別のトップレベルのキーがあるファイル(unknown top-level key "extra" (only "hooks"))です。設定はその理由を示し、JSONとして編集… を提示します。ドキュメントのフックはドキュメントを通じて変更され、それぞれ 1 回の取り消し操作になります(フックを追加、フックを編集、フックを複製、フックを削除、フックの有効化、フックの無効化)。
信頼。 フォームで変更した(追加、編集、複製、削除、切り替えた)ドキュメントのフックリストは、変更がドキュメントに届く前、保存した時点で承認されます。ただし、変更前のリストが承認済みだった場合に限ります(空のリストも承認済みとみなします)。あなたが書いたのは変更部分であって、残りではないからです。承認されていなかったリストは未承認のままで、これまでどおり確認されます。JSONとして編集… のシートは承認せず、フォームでの変更を取り消したりやり直したりすると、確認するまでリストは未承認になります。あなた自身のフックに承認は必要ありません。
今すぐ実行 は、イベントがちょうど起きたかのようにフックを 1 回実行し、その結果を表示します。ステータス(exit 0、exit 1、timed out…)、かかった時間、stdout と stderr の最後の 40 行です。何も保存や書き出しはされません。afterSave のフックはドキュメントのパッケージを 1 つのファイルとして受け取り(ドキュメントがなければなし)、afterExport のフックは filter の最初の種類を受け取ってファイルはなし、afterExportCode のフックは kind: "code" と filter の最初のターゲットを受け取ります。あなたのフックはドキュメントなしで実行されます(ドキュメントのパスはなく、cwd で指定しない限りホームフォルダで実行)。ドキュメントのフックには、ドキュメントが保存済みで、そのフックが承認済みである必要があります(今すぐ実行は確認してから実行します)。オフにしたフックも実行できます。実行はログに残り、失敗すればほかと同じように通知が表示されます。
それぞれを ~/Library/Application Support/Silverpoint/hooks.json(またはドキュメントの hooks)に入れます。
トークンの書き出しのあとに tokens.json をリポジトリにコピーする(ファイル ▸ コードまたはトークンを書き出す… ▸ トークン: JSON)。$SILVERPOINT_HOOK_TOKENS_PATH は書き込まれたばかりのファイルなので、それを展開するにはシェルが必要です。
{
"hooks": [
{
"id": "sync-tokens",
"event": "afterExport",
"filter": ["tokens"],
"shell": true,
"command": ["cp \"$SILVERPOINT_HOOK_TOKENS_PATH\" /Users/me/code/my-app/Design/tokens.json"]
}
]
}
ドキュメントの隣にある決まったファイルなら、シェルは必要ありません。{"id": "copy-tokens", "event": "afterExport", "filter": ["tokens"], "command": ["/bin/cp", "-f", "tokens.json", "../my-app/Design/tokens.json"]}はドキュメントのフォルダで cp を実行します。
書き出したばかりの Swift に swift-format をかける(SwiftUI のコードターゲット)。ファイルは $SILVERPOINT_HOOK_FILES に改行区切りで(stdin には配列として)届くので、スペースを含むパスも tr と xargs -0 を通り抜けます。
{
"hooks": [
{
"id": "format-swift",
"event": "afterExportCode",
"filter": ["swiftui"],
"shell": true,
"command": ["printf '%s' \"$SILVERPOINT_HOOK_FILES\" | tr '\\n' '\\0' | xargs -0 /usr/bin/xcrun swift-format -i"],
"timeout": 120
}
]
}
書き出したファイルを git add する(コード、トークン、ハンドオフの書き出しを、書き込み先のリポジトリに)。
{
"hooks": [
{
"id": "stage-exports",
"event": "afterExport",
"filter": ["code", "tokens", "handoff"],
"shell": true,
"command": ["printf '%s' \"$SILVERPOINT_HOOK_FILES\" | tr '\\n' '\\0' | xargs -0 /usr/bin/git -C /Users/me/code/my-app add --"]
}
]
}
代わりにフォルダ内のすべてをステージするなら、シェルは必要ありません: {"id": "stage-all", "event": "afterExportCode", "command": ["/usr/bin/git", "add", "-A", "."], "cwd": "/Users/me/code/my-app/Sources/Design"}。
shell: true は明示的な選択です(承認シートにも表示されます)。render と score がフックを実行することはありません。