はじめに
OpenCode + さくら AI Engineをコーディング以外にも様々な用途に使うことができますが、Skills(スキル)を使うともっと便利になります。
スキル とは、OpenCode に特定のタスクを実行するための「専門知識」「手順書」「スクリプト」を追加する機能です。毎回細かい指示を出さなくても、あらかじめスキルを置いておくと、OpenCode が状況を判断して自律的に手順を読み込み、定型業務や開発作業を自動実行します。
スキルの使い方
スキルの使い方は、Claude Code、Kiro などと共通です。Claude Code のスキルもそのまま使うことができます。
詳しいドキュメントはこちらを参照してください。
Skillshttps://opencode.ai/docs/ja/skills/
スキルの使い方は、簡単で、SKILL.md ファイルを次の場所に配置します。
スキル名ごとにフォルダーを 1 つ作成し、その中に SKILL.md を置きます。 OpenCode は次の場所を検索します。
- プロジェクト設定: .opencode/skills//SKILL.md
- グローバル設定: ~/.config/opencode/skills//SKILL.md
- プロジェクト Claude 互換: .claude/skills//SKILL.md
- グローバル Claude 互換: ~/.claude/skills//SKILL.md
- プロジェクトエージェント互換: .agents/skills//SKILL.md
- グローバルエージェント互換: ~/.agents/skills//SKILL.md
SKILL.mdについて
公式ドキュメントの例のように SKILL.md は次のような内容になります。
先頭の1行目から9行目がフロントマターと呼ばれる部分で、name(名前)とdescription(説明)は必須です。
ここの name と、上記のスキルを配置するフォルダー は一致する必要があります。ハイフン区切りの英数字になります。(これを間違えるとスキルを検出してくれません)
内容は、簡単なプロンプトを書くイメージです。生成AIを活用している人であればそれほど違和感はないと思います。
OpenCodeは起動時に上記の検索パスを検索して、この条件に一致するスキルを候補としてリストアップします。AIエージェントは、このスキルの名前をもとに、指示の内容に合ったスキルを探して、処理を行います。そのため、名前だけで何を行うスキルか分かるような名前をつけます。
---
name: git-release
description: Create consistent releases and changelogs
license: MIT
compatibility: opencode
metadata:
audience: maintainers
workflow: github
---
## What I do
- Draft release notes from merged PRs
- Propose a version bump
- Provide a copy-pasteable `gh release create` command
## When to use me
Use this when you are preparing a tagged release.
Ask clarifying questions if the target versioning scheme is unclear.
使ってみる
次のようなスキルを使っています。明示的に「このスキルで」という使い方もありますが、基本的には文脈で勝手に判断して使ってくれます。
1. さくら AI Engine の TTS
せっかく、さくら AI Engine を使っているので、簡単に呼び出せるようにしておく
opencode の中で読み上げ音声を作成するなどで利用できる、複数ファイルの連続処理もできる
音声モデルは、VOICEVOX の音声が使える。利用前にさくら AI Engineのコンソールで利用規約に同意しておく必要がある。
使用例:ずんだもんの声で「こんにちは」を読み上げて
---
name: sakura-text-to-speech
description: さくら AI Engineの音声読み上げ
---
次の形式でリクエストして、音声読み上げ(音声合成)/Text-to-Speech(TTS)を行う
無料プランは月に50回までのリクエストができる
<Token> は今使っているアクセストークンと同じ
<string> は読み上げの文字列
\```
curl --request POST \
--url https://api.ai.sakura.ad.jp/v1/audio/speech \
--header 'accept: audio/wav' \
--header 'Authorization: Bearer <Token>' \
--header 'Content-Type: application/json' \
--data '{
"model": "zundamon",
"input": "<string>",
"voice": "normal",
"response_format": "wav"
}' --output ./audio-speech-output.wav
\```
2. さくら AI Engine の STT
せっかく、さくら AI Engine を使っているので、簡単に呼び出せるようにしておく
opencode の中で音声認識などで利用できる、複数ファイルの連続処理もできる
使用例:sample.wav を文字起こしして
---
name: sakura-speech-to-text
description: さくら AI Engineの文字起こし(音声認識)
---
次の形式でリクエストして、文字起こし(音声認識)/Speech-to-Text(STT/speech recognition)を行う
<Token> は今使っているアクセストークンと同じ、環境変数 SAKURA_AI_API_KEY で設定する
以下の例では入力ファイルが sample.mp3
音声ファイルは30分、もしくは30MBの制限があるため、送信する前にチェックしてください
\```sh
curl --request POST \
--url https://api.ai.sakura.ad.jp/v1/audio/transcriptions \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <Token>' \
--header 'Content-Type: multipart/form-data' \
--form 'file=@sample.mp3' \
--form 'model=whisper-large-v3-turbo'
\```
3. マルチモーダル
opencode から画像の認識をしたり、OCR を行うことができる。OCR結果をExcelにしたりといった操作ができる。
このスキルがない、デフォルト状態では pytesseract から Tesseract が使われるが、これを使う方が断然はやい。
使用例:sample.pdf をOCRして、Excel形式にして
---
name: sakura-multi-modal
description: さくら AI Engineのマルチモーダル(画像認識/OCR)
---
次の形式でリクエストして、画像認識やOCRを行う
<Token> は今使っているアクセストークンと同じ、環境変数 SAKURA_AI_API_KEY で設定する
入力ファイルは、URL を指定するか、画像をBASE64エンコードして "data:image/jpeg;base64,${imageBase64}" 形式でセットする
エンドポイント: https://api.ai.sakura.ad.jp/v1/chat/completions
入力テキスト: <input_text>
入力ファイル: <input_file>
POSTデータ形式:
\```
{
"model": "preview/Kimi-K2.7-Code",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "<input_text>"},
{
"type": "image_url",
"image_url": {"url": "<input_file>"}
}
]
}
]
}
\```
4. パワーポイント資料
資料作成は利用頻度が高いので、使うテンプレートを指定しておく
パワーポイントはデザインの指定、好みがあるので特に
---
name: create-powerpoint-presentation
description: テンプレートを用いて、PowerPointのプレゼンテーション資料を作成する
---
使用するテンプレート: template.pptx
指定された文書を読んで、テンプレートを用いて、PowerPointのプレゼンテーション資料を作成する。
想定読者に最適化された、論理的かつ視覚的にわかりやすいスライドを作成してください
想定読者、関心事、求めるアクション、を作成前に確認してください。
#レイアウトルール
現在のスライドマスターのレイアウトをそのまま使用し、ロゴ、タイトルの要素の位置を絶対に変更しないこと。
#デザインルール
~~お好みのデザインをここに記載する~~
#資料品質ルール
- 1スライド1メッセージを徹底し、伝えたいポイントを明確にすること
- 文章の羅列は避け、視覚要素を積極的に使うこと
5. Excel
資料作成は利用頻度が高いので使うテンプレートを指定しておく
パワーポイント同様、作成する時にテンプレートを探さなくてもいいように、あらかじめ使うテンプレートを置いておく
いまでも方眼Excelを求められることが多いので、方眼Excelのテンプレートを作成し、次のようなスキルを作成しておく
---
name: create-excel-specification
description: 指定された文書を読んで、テンプレートの「本文」シートに、Excelの仕様書/設計書を作成する
---
使用するテンプレート: template.xlsx
指定された文書を読んで、テンプレートの「本文」シートに、Excelの仕様書/設計書を作成する。
テンプレートはいわゆる「方眼Excel」なので、折り返して表示属性はつけず、列幅が変わらないようにする。
入力された文字数に合わせて、列を結合する。
1列に格納された文字数が20文字より多い場合は、例外として折り返して表示することも許可。
6. Word
資料作成は利用頻度が高いので使うテンプレートを指定しておく
パワーポイント同様、作成する時にテンプレートを探さなくてもいいように、あらかじめ使うテンプレートを置いておく
開発ではMarkdownを使っていても、提出はWordでというケースは多いので、Wordのテンプレートも用意しておく
7. Yahoo ファイナンス
特に指定しないと、デフォルトで海外のサイトを見にいくので、Yahoo! Japan を指定しておく
---
name: stock-price
description: 株価を調べる(日本国内)
---
使用するURL: https://finance.yahoo.co.jp
8. 今日は何の日?
メールやチャットで、日によって違う話題を提供するネタの1つとして用意しておく
外部サービスの呼び出しは OpenAPI 仕様で指定すると簡単かつ正確
---
name: what-is-todays-anniversary
description: 今日は何の日?
---
次の OpenAPI仕様でリクエストして、今日は何の日?か調べる
\```yaml:openapi.yaml
openapi: 3.0.3
info:
title: 'What is today API'
description: 'This is a simple API that can tell what is today.'
version: 0.0.1
externalDocs:
description: Find out more about What is today API
url: https://note.com/sooz/n/n94cc1c71ad97
servers:
- url: https://api.whatistoday.cyou/v3
paths:
/anniv/{mmdd}:
get:
tags:
- Anniversary
summary: 'What anniversary is it today?'
parameters:
-
name: mmdd
in: path
required: true
description: 'MMDD format date.'
schema:
type: string
example: 0214
responses:
200:
description: 'Anniversary'
content:
application/json:
schema:
$ref: '#/components/schemas/Anniversary'
400:
description: 'Error message.'
content:
application/json:
schema:
type: string
404:
description: 'Not found.'
content:
application/json:
schema:
type: string
/anniv1_img/{mmdd}.png:
get:
tags:
- AnniversaryImage
summary: 'Get image of what anniversary is it today?'
parameters:
-
name: mmdd
in: path
required: true
description: 'MMDD format date.'
schema:
type: string
example: '0214'
responses:
200:
description: 'AnniversaryImage'
content:
image/png:
schema:
type: string
format: binary
400:
description: 'Error message.'
content:
application/json:
schema:
type: string
404:
description: 'Not found.'
content:
application/json:
schema:
type: string
/md/{mmdd}:
get:
tags:
- Merchandising
summary: 'What merchandising day is it today?'
parameters:
-
name: mmdd
in: path
required: true
description: 'MMDD format date.'
schema:
type: string
example: '0214'
responses:
200:
description: 'Merchandising'
content:
application/json:
schema:
$ref: '#/components/schemas/Merchandising'
400:
description: 'Error message.'
content:
application/json:
schema:
type: string
404:
description: 'Not found.'
content:
application/json:
schema:
type: string
/birthflower/{mmdd}:
get:
tags:
- BirthFlower
summary: 'What birthflower is it today?'
parameters:
-
name: mmdd
in: path
required: true
description: 'MMDD format date.'
schema:
type: string
example: '0214'
responses:
200:
description: 'BirthFlower'
content:
application/json:
schema:
$ref: '#/components/schemas/BirthFlower'
400:
description: 'Error message.'
content:
application/json:
schema:
type: string
404:
description: 'Not found.'
content:
application/json:
schema:
type: string
/famousbirthday/{mmdd}:
get:
tags:
- famousbirthday
summary: 'Which great birthday is it today?'
parameters:
-
name: mmdd
in: path
required: true
description: 'MMDD format date.'
schema:
type: string
example: '0214'
responses:
200:
description: 'FamousBirthday'
content:
application/json:
schema:
$ref: '#/components/schemas/FamousBirthday'
400:
description: 'Error message.'
content:
application/json:
schema:
type: string
404:
description: 'Not found.'
content:
application/json:
schema:
type: string
components:
schemas:
Anniversary:
type: object
properties:
_count:
type: integer
description: 'Counts'
example: 1
_last:
type: string
description: 'is last'
nullable: true
_items:
type: array
items:
type: object
properties:
anniv1:
type: string
example: '世界気象デー'
anniv2:
type: string
anniv3:
type: string
anniv4:
type: string
anniv5:
type: string
key:
type: string
example: '83'
mmdd:
type: string
example: '0323'
Merchandising:
type: object
properties:
_count:
type: integer
description: 'Counts'
example: 1
_last:
type: string
description: 'is last'
nullable: true
_items:
type: array
items:
type: object
properties:
fashion1:
type: string
example: '春物(プロパー)'
fashion2:
type: string
food1:
type: string
example: '冬メニュー'
food2:
type: string
gift1:
type: string
example: 'バレンタイン'
gift2:
type: string
key:
type: string
example: '45'
life1:
type: string
example: '新生活(入園・入学・フレッシャーズ)'
life2:
type: string
mmdd:
type: string
example: '0214'
BirthFlower:
type: object
properties:
_count:
type: integer
description: 'Counts'
example: 1
_last:
type: string
description: 'is last'
nullable: true
_items:
type: array
items:
type: object
properties:
flower:
type: string
example: 'シュンラン'
key:
type: string
example: '45'
lang:
type: string
description: 'language of flower'
example: '素直なしぐさ'
mmdd:
type: string
example: '0214'
FamousBirthday:
type: object
properties:
_count:
type: integer
description: 'Counts'
example: 1
_last:
type: string
description: 'is last'
nullable: true
_items:
type: array
items:
type: object
properties:
key:
type: string
example: '7qfmexz8qu8d'
lifespan:
type: string
example: '1820-1893'
mmdd:
type: string
example: '0214'
name:
type: string
example: '清水次郎長'
profile:
type: string
example: '侠客・博徒'
\```
9. 天気予報
お好みの天気予報サイトを事前に指定しておく
外部サービスの呼び出しは OpenAPI 仕様で指定すると簡単かつ正確
---
name: jma-wheather-forecast
description: 気象庁の天気予報
---
次の OpenAPI仕様でリクエストして、天気予報を調べる
\```yaml:openapi.yaml
openapi: 3.0.3
info:
version: 1.0.0
title: JMAForecastAPI
description: JMAForecastAPI provides weather data in Japan.
servers:
- url: https://www.jma.go.jp/bosai
paths:
/forecast/data/overview_forecast/{area}.json:
get:
summary: Retrieves overview forecast for a specific area.
tags:
- overview_forecast
parameters:
- in: path
name: area
schema:
type: string
description: The location for which to retrieve overview forecast (Area code is bellow, 北海道=016000,青森県=020000,秋田県=050000,岩手県=030000,宮城県=040000,山形県=060000,福島県=070000,宮城県=080000,栃木県=090000,群馬県=100000,埼玉県=110000,東京都=130000,千葉県=120000,神奈川県=140000,長野県=200000,山梨県=190000,静岡県=220000,愛知県=230000,岐阜県=210000,三重県=240000,新潟県=150000,富山県=160000,石川県=170000,福井県=180000,滋賀県=250000,京都府=260000,大阪府=270000,兵庫県=280000,奈良県=290000,和歌山県=300000,岡山県=330000,広島県=340000,島根県=320000,鳥取県=310000,徳島県=360000,香川県=370000,愛媛県=380000,高知県=390000,山口県=350000,福岡県=400000,大分県=440000,長崎県=420000,佐賀県=410000,熊本県=430000,宮崎県=450000,鹿児島県=460100,沖縄県=471000).
example: 130000
required: true
responses:
'200':
description: Successful response.
content:
application/json:
schema:
$ref: '#/components/schemas/Overview'
'400':
description: Bad request. Invalid or missing parameters.
'404':
description: Not found. The specified location was not found.
'500':
description: Internal server error. Something went wrong on the server side.
/forecast/data/forecast/{area}.json:
get:
summary: Retrieves forecast for a specific area.
tags:
- forecast
parameters:
- in: path
name: area
schema:
type: string
description: The location for which to retrieve forecast (Area code is bellow, 北海道=016000,青森県=020000,秋田県=050000,岩手県=030000,宮城県=040000,山形県=060000,福島県=070000,宮城県=080000,栃木県=090000,群馬県=100000,埼玉県=110000,東京都=130000,千葉県=120000,神奈川県=140000,長野県=200000,山梨県=190000,静岡県=220000,愛知県=230000,岐阜県=210000,三重県=240000,新潟県=150000,富山県=160000,石川県=170000,福井県=180000,滋賀県=250000,京都府=260000,大阪府=270000,兵庫県=280000,奈良県=290000,和歌山県=300000,岡山県=330000,広島県=340000,島根県=320000,鳥取県=310000,徳島県=360000,香川県=370000,愛媛県=380000,高知県=390000,山口県=350000,福岡県=400000,大分県=440000,長崎県=420000,佐賀県=410000,熊本県=430000,宮崎県=450000,鹿児島県=460100,沖縄県=471000).
example: 130000
required: true
responses:
'200':
description: Successful response.
content:
application/json:
schema:
$ref: '#/components/schemas/Forecast'
'400':
description: Bad request. Invalid or missing parameters.
'404':
description: Not found. The specified location was not found.
'500':
description: Internal server error. Something went wrong on the server side.
components:
schemas:
Overview:
type: object
properties:
publishingOffice:
type: string
description: Publishing Office.
example: 気象庁
reportDatetime:
type: string
description: Report datetime.
example: "2024-03-27T16:37:00+09:00"
targetArea:
type: string
description: Area name.
example: "東京都"
headlineText:
type: string
description: Headline text.
text:
type: string
description: Overview forecast.
Forecast:
type: array
items:
type: object
properties:
publishingOffice:
type: string
example: "気象庁"
reportDatetime:
type: string
format: date-time
example: "2024-03-24T11:00:00+09:00"
timeSeries:
type: array
items:
type: object
properties:
timeDefines:
type: array
items:
type: string
example: "2024-03-24T11:00:00+09:00"
areas:
type: array
items:
type: object
properties:
area:
type: object
properties:
name:
type: string
example: "東京地方"
code:
type: string
example: "130010"
#
weatherCodes:
type: array
items:
type: string
example: "202"
weathers:
type: array
items:
type: string
example: "くもり 夕方 一時 雨"
winds:
type: array
items:
type: string
example: "南の風"
waves:
type: array
items:
type: string
example: "0.5メートル"
#
pops:
type: array
items:
type: string
example: "10"
#
temps:
type: array
items:
type: string
example: "10"
#
reliabilities:
type: array
items:
type: string
example: "A"
#
tempsMin:
type: array
items:
type: string
example: "11"
tempsMinUpper:
type: array
items:
type: string
example: "12"
tempsMinLower:
type: array
items:
type: string
example: "10"
tempsMax:
type: array
items:
type: string
example: "21"
tempsMaxUpper:
type: array
items:
type: string
example: "24"
tempsMaxLower:
type: array
items:
type: string
example: "18"
tempAverage:
type: object
properties:
areas:
type: array
items:
type: object
properties:
area:
type: object
properties:
name:
type: string
example: "東京"
code:
type: string
example: "44132"
min:
type: string
example: "7.0"
max:
type: string
example: "16.5"
precipAverage:
type: object
properties:
areas:
type: array
items:
type: object
properties:
area:
type: object
properties:
name:
type: string
example: "東京"
code:
type: string
example: "44132"
min:
type: string
example: "13.5"
max:
type: string
example: "37.1"
\```