Compare commits
5 Commits
0f6a2caa41
...
be5fd5a75b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be5fd5a75b | ||
|
|
9dec4b3ace | ||
|
|
d129777bf1 | ||
|
|
5a0a668a8a | ||
|
|
9c67910f3d |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.sh text eol=lf
|
||||
@@ -42,9 +42,10 @@
|
||||
1. ローカルリポジトリは、サーバー側Gitをリモートにしない
|
||||
2. Windmillの実体変更は **Windmill REST API 経由のみ**
|
||||
3. 作業開始時にAPIでサーバー状態を取り込み、サーバーが新しければローカルへ同期
|
||||
4. ローカル変更はAPIでサーバーへ反映
|
||||
5. サーバー側の Git Sync Workflow は定期記録用途として継続
|
||||
6. ローカルGitコミットは手動(またはAI)で実施し、監査/履歴用途として扱う
|
||||
4. **運用単位は「workflow package(flow + schedules)」を基本**とする
|
||||
5. ローカル変更はAPIでサーバーへ反映
|
||||
6. サーバー側の Git Sync Workflow は定期記録用途として継続
|
||||
7. ローカルGitコミットは手動(またはAI)で実施し、監査/履歴用途として扱う
|
||||
|
||||
---
|
||||
|
||||
@@ -78,6 +79,13 @@
|
||||
- ローカルファイルは「編集用ワークツリー + 監査ログ」
|
||||
- ローカルGitはサーバー同期の必須経路ではない
|
||||
|
||||
### 管理単位(重要)
|
||||
|
||||
- 単体オブジェクト運用(scriptだけ、flowだけ)は補助用途
|
||||
- 標準運用は **workflow package 単位** とする
|
||||
- `flow` 本体
|
||||
- その `flow path` に紐づく `schedules`(`is_flow=true` かつ `script_path == flow.path`)
|
||||
|
||||
### 既存の主要オブジェクト(例)
|
||||
|
||||
- script: `u/admin/alexa_speak`
|
||||
@@ -98,11 +106,18 @@
|
||||
- サーバー `updated_at` / `hash` とローカル管理メタ情報を比較
|
||||
- サーバーが新しければローカルを更新
|
||||
|
||||
### 対象API
|
||||
### 対象API(オブジェクト単体)
|
||||
|
||||
- `GET /api/w/{workspace}/scripts/get/p/{path}`
|
||||
- `GET /api/w/{workspace}/flows/get/{path}`
|
||||
- `GET /api/w/{workspace}/schedules/get/{path}`(必要時)
|
||||
- `GET /api/w/{workspace}/schedules/get/{path}`
|
||||
|
||||
### workflow package Pull(標準)
|
||||
|
||||
1. `GET /flows/get/{flow_path}` で flow 本体取得
|
||||
2. `GET /schedules/list` で schedule 一覧取得
|
||||
3. `script_path == flow_path` の schedule 群を抽出
|
||||
4. ローカルへ一括保存(flow + schedules)
|
||||
|
||||
## 5.2 Push(ローカル -> サーバー)
|
||||
|
||||
@@ -132,6 +147,14 @@
|
||||
1. `DELETE /flows/delete/{path}`
|
||||
2. `POST /flows/create`
|
||||
|
||||
### schedule反映API(workflow package の一部)
|
||||
|
||||
- workflow package Push時は、対象 flow に紐づく schedule も同時同期する
|
||||
- 原則:
|
||||
1. サーバー現行 schedule(`script_path == flow_path`)一覧取得
|
||||
2. ローカル定義との差分計算(追加・更新・削除)
|
||||
3. `DELETE /schedules/delete/{path}` と `POST /schedules/create` で収束
|
||||
|
||||
---
|
||||
|
||||
## 6. 競合時の動作仕様
|
||||
@@ -148,6 +171,15 @@
|
||||
|
||||
- 削除再作成前に最新を必ずPullしてローカル保存
|
||||
- 競合が疑われる場合は自動実行せず手動確認にフォールバック
|
||||
- **Preflight必須**: Push直前に `remote_index` の既知hashとサーバー現在hashを比較し、不一致ならPush中断(fail closed)
|
||||
- Push後は `post-verify`(再取得して期待JSON一致確認)を必須化
|
||||
|
||||
### schedulePush競合
|
||||
|
||||
- `schedule.path` 重複や同名上書きに注意
|
||||
- workflow package Push時は、対象 flow の schedule 群をまとめて同期し、中途半端な状態を残さない
|
||||
- 失敗時は flow と schedules の両方を再取得して整合を確認
|
||||
- schedule 同期も Preflight/`post-verify` の対象に含める
|
||||
|
||||
---
|
||||
|
||||
@@ -157,7 +189,7 @@
|
||||
|
||||
- 本ドキュメント作成
|
||||
|
||||
## Phase 1: API運用コマンド整備(次タスク)
|
||||
## Phase 1: API運用コマンド整備(完了)
|
||||
|
||||
`wm-api.sh` へ追加:
|
||||
|
||||
@@ -168,17 +200,26 @@
|
||||
5. `pull-all`(scripts/flowsの一覧取得 + 一括保存)
|
||||
6. `status-remote`(ローカルとサーバーのhash比較)
|
||||
|
||||
## Phase 2: メタ情報管理
|
||||
## Phase 2: workflow package 対応(次タスク)
|
||||
|
||||
- `state/remote_index.json` を導入し、`path -> hash/updated_at` を保持
|
||||
- Pull前後で差分判定可能にする
|
||||
`wm-api.sh` へ追加:
|
||||
|
||||
## Phase 3: 標準化
|
||||
1. `pull-workflow <flow_path>`(flow + schedules 一括取得)
|
||||
2. `push-workflow <workflow-dir>`(flow反映 + schedules差分同期)
|
||||
3. `status-workflow [flow_path]`(workflow単位の差分表示)
|
||||
4. `pull-all-workflows`(flow全件を workflow package で取得)
|
||||
|
||||
## Phase 3: メタ情報管理
|
||||
|
||||
- `state/remote_index.json` を拡張し、`scripts/flows/schedules` を保持
|
||||
- `state/workflow_index.json` を導入し、`workflow -> flow_hash + schedule_hashes` を保持
|
||||
|
||||
## Phase 4: 標準化
|
||||
|
||||
- `docs/flow-manage` に操作例を固定
|
||||
- 「作業開始時は必ずpull」を運用ルール化
|
||||
|
||||
## Phase 4: 半自動化(任意)
|
||||
## Phase 5: 半自動化(任意)
|
||||
|
||||
- AI/スクリプトで
|
||||
- 変更検知
|
||||
@@ -193,14 +234,16 @@
|
||||
|
||||
1. `pull-all` でサーバー最新を取得
|
||||
2. ローカル編集
|
||||
3. `push-script` / `push-flow` で反映
|
||||
4. 必要に応じてローカルGitにコミット
|
||||
3. workflow修正時は `push-workflow` で反映(flow + schedules)
|
||||
4. script単体修正時のみ `push-script` を使う
|
||||
5. 必要に応じてローカルGitにコミット
|
||||
|
||||
### 8.2 サーバーで変更されたものを取り込む
|
||||
|
||||
1. `status-remote` 実行
|
||||
2. サーバー新規/更新分を `pull-*` で取得
|
||||
3. ローカル履歴としてコミット(任意)
|
||||
2. workflow単位の変更は `status-workflow` / `pull-workflow` で取得
|
||||
3. 単体変更は `pull-*` で取得
|
||||
4. ローカル履歴としてコミット(任意)
|
||||
|
||||
### 8.3 `alexa_speak` 更新例(現在の具体例)
|
||||
|
||||
@@ -233,7 +276,8 @@
|
||||
|
||||
1. Push前に対象オブジェクトをバックアップ保存(必須)
|
||||
2. Push後に `post-verify`(再取得して期待値比較)を必須化
|
||||
3. 失敗時は「再実行」ではなく「現状確認 -> 復旧」の順で実施
|
||||
3. Push直前に `preflight hash check` を必須化し、不一致時はPush停止(fail closed)
|
||||
4. 失敗時は「再実行」ではなく「現状確認 -> 復旧」の順で実施
|
||||
|
||||
### 10.2 標準復旧手順
|
||||
|
||||
@@ -251,6 +295,13 @@
|
||||
2. 依存スケジュールの有効性を確認
|
||||
3. 関連ジョブの手動実行で動作確認
|
||||
|
||||
### 10.4 delete -> create 運用ガード(必須)
|
||||
|
||||
1. `push-flow` / `push-workflow` 前に対象flowのバックアップJSONを必ず保存
|
||||
2. Preflightで hash 不一致なら自動Pushしない(手動レビューへフォールバック)
|
||||
3. Push後に flow と schedules を再取得し、ローカル期待値と一致確認
|
||||
4. 不一致時は即時にバックアップから復元し、復旧ログを残す
|
||||
|
||||
---
|
||||
|
||||
## 11. Windmill依存を薄くする方針(必須)
|
||||
@@ -261,7 +312,7 @@ Windmill API依存は避けられないため、依存点を最小化する。
|
||||
|
||||
- `scripts`: `list/get/create`
|
||||
- `flows`: `list/get/create/delete`
|
||||
- `schedules`: `list/get/create/delete`(必要時)
|
||||
- `schedules`: `list/get/create/delete`(workflow package 同期で常用)
|
||||
|
||||
上記以外のAPIは原則使わない。
|
||||
|
||||
@@ -283,18 +334,20 @@ Windmill API依存は避けられないため、依存点を最小化する。
|
||||
|
||||
以下を満たせば「このプロジェクトはサーバーのワークフローを管理するためのもの」と言える状態:
|
||||
|
||||
1. ローカルから scripts/flows の Pull/Push がAPIで完結
|
||||
2. サーバー更新がローカルで検知できる(hash比較)
|
||||
3. 競合時の復旧手順が定義済み
|
||||
1. ローカルから workflow package(flow + schedules)の Pull/Push がAPIで完結
|
||||
2. サーバー更新が workflow 単位でローカル検知できる
|
||||
3. 競合時の復旧手順が flow/schedule 両方で定義済み
|
||||
4. 運用手順がこの文書だけで再現可能
|
||||
|
||||
---
|
||||
|
||||
## 13. 既知の注意点
|
||||
|
||||
1. `wm-api.sh` は現状 `bash` 前提だが、Windowsで改行がCRLFだと shebang 実行失敗する場合がある
|
||||
1. `wm-api.sh` は `bash` 前提。WindowsのCRLF混入で shebang 実行失敗し得るため、`.gitattributes` で `*.sh text eol=lf` を固定し、実行は `bash wm-api.sh ...` を標準とする
|
||||
2. フロー更新は環境により `PUT` できないため削除再作成を標準とする
|
||||
3. API応答仕様はWindmillバージョン差で微差が出るため、初回導入時は `get` のレスポンス形を確認する
|
||||
3. 削除再作成は `version_id/hash/edited_at` を更新するため、Preflight hash check がないと競合上書きを見落とす可能性がある
|
||||
4. 1つのflowに複数scheduleが紐づくことがあるため、`script_path` ベースで束ねて管理する
|
||||
5. API応答仕様はWindmillバージョン差で微差が出るため、初回導入時は `get` のレスポンス形を確認する
|
||||
|
||||
---
|
||||
|
||||
@@ -304,4 +357,6 @@ Windmill API依存は避けられないため、依存点を最小化する。
|
||||
|------|----------|
|
||||
| 2026-03-03 | 初版作成(API一本化方針、同期仕様、実装計画、Runbookを定義) |
|
||||
| 2026-03-03 | 障害前提の復旧設計、Windmill依存を薄くする方針を必須要件として追記 |
|
||||
| 2026-03-03 | 運用単位を workflow package(flow + schedules)へ変更し、実装計画とRunbookを更新 |
|
||||
| 2026-03-03 | `delete -> create` と hash 管理の運用ガード(preflight / fail closed / post-verify)およびCRLF対策を追記 |
|
||||
| 2026-03-04 | `u/admin/alexa_speak` のAPI反映後にUIドロップダウンが即時反映されない事象と運用回避策(`Edit -> Deploy`)を追記 |
|
||||
|
||||
24
flows/git_sync.flow.json
Normal file
24
flows/git_sync.flow.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"path": "u/antigravity/git_sync",
|
||||
"summary": "Git Sync Workflow",
|
||||
"description": "Automatically sync Windmill workflows to Git repository (sync branch)",
|
||||
"value": {
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"value": {
|
||||
"lock": "",
|
||||
"type": "rawscript",
|
||||
"content": "#!/bin/bash\nset -e\nexport PATH=/usr/bin:/usr/local/bin:/usr/sbin:/sbin:/bin:$PATH\n\nGREEN=\"\\033[0;32m\"\nYELLOW=\"\\033[1;33m\"\nRED=\"\\033[0;31m\"\nNC=\"\\033[0m\"\n\necho -e \"${GREEN}=== Windmill Workflow Git Sync ===${NC}\"\n\nREPO_ROOT=\"/workspace\"\nWMILL_DIR=\"${REPO_ROOT}/workflows\"\n\nif ! command -v wmill &> /dev/null; then\n echo -e \"${YELLOW}Installing windmill-cli...${NC}\"\n npm install -g windmill-cli\n export PATH=$(npm prefix -g)/bin:$PATH\nfi\n\ngit config --global --add safe.directory \"$REPO_ROOT\"\ngit config --global user.email \"bot@keinafarm.net\"\ngit config --global user.name \"Windmill Bot\"\n\n# sync ブランチを使用\nCURRENT_BRANCH=$(git -C \"$REPO_ROOT\" rev-parse --abbrev-ref HEAD)\nif [ \"$CURRENT_BRANCH\" != \"sync\" ]; then\n echo -e \"${YELLOW}Switching to sync branch...${NC}\"\n git -C \"$REPO_ROOT\" fetch origin sync\n git -C \"$REPO_ROOT\" checkout sync\nfi\n\necho -e \"${YELLOW}Pulling from origin/sync...${NC}\"\ngit -C \"$REPO_ROOT\" pull --rebase origin sync || {\n echo -e \"${RED}Failed to pull from remote. Continuing...${NC}\"\n}\n\necho -e \"${YELLOW}Pulling from Windmill...${NC}\"\ncd \"$WMILL_DIR\"\nwmill sync pull --config-dir /workspace/wmill_config --skip-variables --skip-secrets --skip-resources --yes || exit 1\n\ncd \"$REPO_ROOT\"\nif [[ -n $(git status --porcelain) ]]; then\n echo -e \"${YELLOW}Changes detected, committing to Git...${NC}\"\n git add -A\n TIMESTAMP=$(date \"+%Y-%m-%d %H:%M:%S\")\n git commit -m \"Auto-sync: ${TIMESTAMP}\"\n echo -e \"${YELLOW}Pushing to Gitea (sync branch)...${NC}\"\n git push origin sync || {\n echo -e \"${RED}Failed to push.${NC}\"\n exit 1\n }\n echo -e \"${GREEN}Changes pushed to Gitea (sync branch)${NC}\"\nelse\n echo -e \"${GREEN}No changes detected${NC}\"\nfi\n\necho -e \"${GREEN}=== Sync Complete ===${NC}\"\n",
|
||||
"language": "bash",
|
||||
"input_transforms": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
38
flows/hourly_chime.flow.json
Normal file
38
flows/hourly_chime.flow.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"path": "u/akiracraftwork/hourly_chime",
|
||||
"summary": "鳩時計機能",
|
||||
"description": "",
|
||||
"value": {
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"value": {
|
||||
"lock": "{\n \"dependencies\": {}\n}\n//bun.lock\n<empty>",
|
||||
"type": "rawscript",
|
||||
"content": "export async function main(\n device: string = \"オフィスの右エコー\",\n prefix: string = \"現在時刻は\",\n suffix: string = \"です\"\n) {\n const now = new Date();\n const hhmm = new Intl.DateTimeFormat(\"ja-JP\", {\n timeZone: \"Asia/Tokyo\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n }).format(now); // 例: 09:30\n\n const [h, m] = hhmm.split(\":\");\n const text = `${prefix}${Number(h)}時${Number(m)}分${suffix}`;\n\n const res = await fetch(\"http://alexa_api:3500/speak\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ device, text }),\n });\n\n if (!res.ok) {\n const body = await res.text();\n throw new Error(`alexa-api error ${res.status}: ${body}`);\n }\n\n return { ok: true, device, text };\n}\n",
|
||||
"language": "bun",
|
||||
"input_transforms": {
|
||||
"device": {
|
||||
"type": "static",
|
||||
"value": "オフィスの右エコー"
|
||||
},
|
||||
"prefix": {
|
||||
"type": "static",
|
||||
"value": "現在時刻は"
|
||||
},
|
||||
"suffix": {
|
||||
"type": "static",
|
||||
"value": "です"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
24
flows/konnnichiha.flow.json
Normal file
24
flows/konnnichiha.flow.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"path": "f/dev/konnnichiha",
|
||||
"summary": "Print greeting",
|
||||
"description": "",
|
||||
"value": {
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\n",
|
||||
"type": "rawscript",
|
||||
"content": "def main():\n print('こんにちは、世界')",
|
||||
"language": "python3",
|
||||
"input_transforms": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -6,14 +6,14 @@
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"summary": "変更確認・LINE通知",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\nanyio==4.12.1\ncertifi==2026.1.4\nh11==0.16.0\nhttpcore==1.0.9\nhttpx==0.28.1\nidna==3.11\ntyping-extensions==4.15.0\nwmill==1.640.0",
|
||||
"type": "rawscript",
|
||||
"language": "python3",
|
||||
"content": "import urllib.request\nimport urllib.parse\nimport json\nimport ssl\nfrom datetime import datetime, timezone, timedelta\nimport wmill\n\nJST = timezone(timedelta(hours=9))\n\n\ndef main():\n # シークレット取得\n api_key = wmill.get_variable(\"u/admin/NOTIFICATION_API_KEY\")\n line_token = wmill.get_variable(\"u/admin/LINE_CHANNEL_ACCESS_TOKEN\")\n line_to = wmill.get_variable(\"u/admin/LINE_TO\")\n\n # 前回実行時刻を取得(初回は現在時刻 - 10分)\n try:\n last_checked = wmill.get_variable(\"u/admin/SHIRAOU_LAST_CHECKED_AT\")\n if not last_checked:\n last_checked = None\n except Exception:\n last_checked = None\n\n if last_checked:\n since = last_checked\n else:\n since = (datetime.now(JST) - timedelta(minutes=10)).isoformat()\n\n print(f\"[通知] 変更確認: since={since}\")\n\n # API呼び出し\n ssl_ctx = ssl.create_default_context()\n ssl_ctx.check_hostname = False\n ssl_ctx.verify_mode = ssl.CERT_NONE\n\n params = urllib.parse.urlencode({\"since\": since})\n url = f\"https://shiraou.keinafarm.net/reservations/api/changes/?{params}\"\n\n req = urllib.request.Request(url, headers={\"X-API-Key\": api_key})\n with urllib.request.urlopen(req, context=ssl_ctx, timeout=30) as resp:\n data = json.loads(resp.read().decode(\"utf-8\"))\n\n checked_at = data[\"checked_at\"]\n reservations = data.get(\"reservations\", [])\n usages = data.get(\"usages\", [])\n\n print(f\"[通知] checked_at={checked_at}, 予約={len(reservations)}件, 実績={len(usages)}件\")\n\n # 変更があればLINE通知(エラー時は状態を更新しない)\n if reservations or usages:\n message = _format_message(reservations, usages)\n _send_line(line_token, line_to, message)\n print(\"[通知] LINE送信完了\")\n else:\n print(\"[通知] 変更なし、通知スキップ\")\n\n # 正常完了時のみ状態更新\n wmill.set_variable(\"u/admin/SHIRAOU_LAST_CHECKED_AT\", checked_at)\n print(f\"[通知] last_checked_at更新: {checked_at}\")\n\n return {\n \"since\": since,\n \"checked_at\": checked_at,\n \"reservations_count\": len(reservations),\n \"usages_count\": len(usages),\n \"notified\": bool(reservations or usages),\n }\n\n\ndef _format_message(reservations, usages):\n lines = [\"\\U0001f4cb 営農システム 変更通知\\n\"]\n\n OP_R = {\n \"create\": (\"\\U0001f7e2\", \"予約作成\"),\n \"update\": (\"\\U0001f535\", \"予約変更\"),\n \"cancel\": (\"\\U0001f534\", \"予約キャンセル\"),\n }\n OP_U = {\n \"create\": (\"\\U0001f7e2\", \"実績登録\"),\n \"update\": (\"\\U0001f535\", \"実績修正\"),\n \"delete\": (\"\\U0001f534\", \"実績削除\"),\n }\n\n for r in reservations:\n start = r[\"start_at\"][:16].replace(\"T\", \" \")\n end = r[\"end_at\"][:16].replace(\"T\", \" \")\n icon, label = OP_R.get(r[\"operation\"], (\"\\u26aa\", r[\"operation\"]))\n lines += [\n f\"{icon} {label}\",\n f\" 機械: {r['machine_name']}\",\n f\" 利用者: {r['user_name']}\",\n f\" 日時: {start} \\uff5e {end}\",\n ]\n if r.get(\"reason\"):\n lines.append(f\" 理由: {r['reason']}\")\n lines.append(\"\")\n\n for u in usages:\n start = u[\"start_at\"][:16].replace(\"T\", \" \")\n icon, label = OP_U.get(u[\"operation\"], (\"\\u26aa\", u[\"operation\"]))\n lines += [\n f\"{icon} {label}\",\n f\" 機械: {u['machine_name']}\",\n f\" 利用者: {u['user_name']}\",\n f\" 利用量: {u['amount']}{u['unit']}\",\n f\" 日: {start[:10]}\",\n ]\n if u.get(\"reason\"):\n lines.append(f\" 理由: {u['reason']}\")\n lines.append(\"\")\n\n return \"\\n\".join(lines).strip()\n\n\ndef _send_line(token, to, message):\n payload = json.dumps({\n \"to\": to,\n \"messages\": [{\"type\": \"text\", \"text\": message}],\n }).encode(\"utf-8\")\n\n req = urllib.request.Request(\n \"https://api.line.me/v2/bot/message/push\",\n data=payload,\n headers={\n \"Authorization\": f\"Bearer {token}\",\n \"Content-Type\": \"application/json\",\n },\n method=\"POST\",\n )\n with urllib.request.urlopen(req, timeout=30) as resp:\n return resp.read().decode(\"utf-8\")\n",
|
||||
"input_transforms": {},
|
||||
"lock": ""
|
||||
}
|
||||
"language": "python3",
|
||||
"input_transforms": {}
|
||||
},
|
||||
"summary": "変更確認・LINE通知"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -6,70 +6,70 @@
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"summary": "Step1: 診断データ生成",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\n",
|
||||
"type": "rawscript",
|
||||
"language": "python3",
|
||||
"content": "import uuid\nfrom datetime import datetime, timezone\n\ndef main():\n \"\"\"診断データを生成する\"\"\"\n now = datetime.now(timezone.utc)\n run_id = str(uuid.uuid4())\n check_value = 2 + 2\n \n result = {\n \"timestamp\": now.isoformat(),\n \"run_id\": run_id,\n \"check\": check_value,\n \"python_version\": __import__('sys').version\n }\n print(f\"[Step1] 診断データ生成完了\")\n print(f\" run_id: {run_id}\")\n print(f\" timestamp: {now.isoformat()}\")\n print(f\" check: {check_value}\")\n return result\n",
|
||||
"input_transforms": {},
|
||||
"lock": ""
|
||||
}
|
||||
"language": "python3",
|
||||
"input_transforms": {}
|
||||
},
|
||||
"summary": "Step1: 診断データ生成"
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"summary": "Step2: データ検証",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\n",
|
||||
"type": "rawscript",
|
||||
"language": "python3",
|
||||
"content": "from datetime import datetime, timezone\n\ndef main(step1_result: dict):\n \"\"\"Step1の結果を検証する\"\"\"\n errors = []\n \n # 計算チェック\n if step1_result.get(\"check\") != 4:\n errors.append(f\"計算エラー: expected 4, got {step1_result.get('check')}\")\n \n # run_idの存在チェック\n if not step1_result.get(\"run_id\"):\n errors.append(\"run_idが存在しない\")\n \n # timestampの存在チェック\n if not step1_result.get(\"timestamp\"):\n errors.append(\"timestampが存在しない\")\n \n if errors:\n error_msg = \"; \".join(errors)\n print(f\"[Step2] 検証失敗: {error_msg}\")\n raise Exception(f\"検証失敗: {error_msg}\")\n \n print(f\"[Step2] データ検証OK\")\n print(f\" 計算チェック: 2+2={step1_result['check']} ✓\")\n print(f\" run_id: {step1_result['run_id']} ✓\")\n print(f\" timestamp: {step1_result['timestamp']} ✓\")\n \n return {\n \"verification\": \"PASS\",\n \"step1_data\": step1_result\n }\n",
|
||||
"language": "python3",
|
||||
"input_transforms": {
|
||||
"step1_result": {
|
||||
"type": "javascript",
|
||||
"expr": "results.a"
|
||||
"expr": "results.a",
|
||||
"type": "javascript"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lock": ""
|
||||
}
|
||||
"summary": "Step2: データ検証"
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"summary": "Step3: HTTPヘルスチェック",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\n",
|
||||
"type": "rawscript",
|
||||
"language": "python3",
|
||||
"content": "import urllib.request\nimport ssl\n\ndef main(verification_result: dict):\n \"\"\"Windmillサーバー自身へのHTTPチェック\"\"\"\n url = \"https://windmill.keinafarm.net/api/version\"\n \n # SSL検証をスキップ(自己署名証明書対応)\n ctx = ssl.create_default_context()\n ctx.check_hostname = False\n ctx.verify_mode = ssl.CERT_NONE\n \n try:\n req = urllib.request.Request(url)\n with urllib.request.urlopen(req, context=ctx, timeout=10) as response:\n status_code = response.status\n body = response.read().decode('utf-8')\n except Exception as e:\n print(f\"[Step3] HTTPチェック失敗: {e}\")\n raise Exception(f\"HTTPヘルスチェック失敗: {e}\")\n \n print(f\"[Step3] HTTPヘルスチェックOK\")\n print(f\" URL: {url}\")\n print(f\" Status: {status_code}\")\n print(f\" Version: {body}\")\n \n return {\n \"http_check\": \"PASS\",\n \"status_code\": status_code,\n \"server_version\": body\n }\n",
|
||||
"language": "python3",
|
||||
"input_transforms": {
|
||||
"verification_result": {
|
||||
"type": "javascript",
|
||||
"expr": "results.b"
|
||||
"expr": "results.b",
|
||||
"type": "javascript"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lock": ""
|
||||
}
|
||||
"summary": "Step3: HTTPヘルスチェック"
|
||||
},
|
||||
{
|
||||
"id": "d",
|
||||
"summary": "Step4: 年度判定 & 最終レポート",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\n",
|
||||
"type": "rawscript",
|
||||
"language": "python3",
|
||||
"content": "from datetime import datetime, timezone\n\ndef main(step1_data: dict, verification: dict, http_check: dict):\n \"\"\"年度判定と最終診断レポートを生成\"\"\"\n now = datetime.now(timezone.utc)\n \n # 日本の年度判定(4月始まり)\n fiscal_year = now.year if now.month >= 4 else now.year - 1\n \n report = {\n \"status\": \"ALL OK\",\n \"fiscal_year\": fiscal_year,\n \"diagnostics\": {\n \"data_generation\": \"PASS\",\n \"data_verification\": verification.get(\"verification\", \"UNKNOWN\"),\n \"http_health\": http_check.get(\"http_check\", \"UNKNOWN\"),\n \"server_version\": http_check.get(\"server_version\", \"UNKNOWN\")\n },\n \"run_id\": step1_data.get(\"run_id\"),\n \"started_at\": step1_data.get(\"timestamp\"),\n \"completed_at\": now.isoformat()\n }\n \n print(\"\")\n print(\"========================================\")\n print(\" Windmill Heartbeat - 診断レポート\")\n print(\"========================================\")\n print(f\" Status: {report['status']}\")\n print(f\" 年度: {fiscal_year}年度\")\n print(f\" Run ID: {report['run_id']}\")\n print(f\" Server: {report['diagnostics']['server_version']}\")\n print(f\" 開始: {report['started_at']}\")\n print(f\" 完了: {report['completed_at']}\")\n print(\" ────────────────────────────────────\")\n print(f\" データ生成: PASS ✓\")\n print(f\" データ検証: {report['diagnostics']['data_verification']} ✓\")\n print(f\" HTTP確認: {report['diagnostics']['http_health']} ✓\")\n print(\"========================================\")\n print(\"\")\n \n return report\n",
|
||||
"language": "python3",
|
||||
"input_transforms": {
|
||||
"http_check": {
|
||||
"expr": "results.c",
|
||||
"type": "javascript"
|
||||
},
|
||||
"step1_data": {
|
||||
"type": "javascript",
|
||||
"expr": "results.a"
|
||||
"expr": "results.a",
|
||||
"type": "javascript"
|
||||
},
|
||||
"verification": {
|
||||
"type": "javascript",
|
||||
"expr": "results.b"
|
||||
},
|
||||
"http_check": {
|
||||
"type": "javascript",
|
||||
"expr": "results.c"
|
||||
"expr": "results.b",
|
||||
"type": "javascript"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lock": ""
|
||||
}
|
||||
"summary": "Step4: 年度判定 & 最終レポート"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
24
flows/textout.flow.json
Normal file
24
flows/textout.flow.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"path": "f/dev/textout",
|
||||
"summary": "Display current time on startup",
|
||||
"description": "",
|
||||
"value": {
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\n",
|
||||
"type": "rawscript",
|
||||
"content": "def main():\n from datetime import datetime\n print(datetime.now().strftime('%H:%M:%S'))",
|
||||
"language": "python3",
|
||||
"input_transforms": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
27
flows/weather_sync.flow.json
Normal file
27
flows/weather_sync.flow.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"path": "f/weather/weather_sync",
|
||||
"summary": "Weather Sync - 気象データ日次同期",
|
||||
"description": "Open-Meteo から昨日の気象データを取得し、Keinasystem DB に保存する。毎朝6時実行。",
|
||||
"value": {
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\nanyio==4.12.1\ncertifi==2026.2.25\ncharset-normalizer==3.4.4\nh11==0.16.0\nhttpcore==1.0.9\nhttpx==0.28.1\nidna==3.11\nrequests==2.32.5\ntyping-extensions==4.15.0\nurllib3==2.6.3\nwmill==1.646.0",
|
||||
"type": "rawscript",
|
||||
"content": "import wmill\nimport requests\nimport datetime\n\nLATITUDE = 33.213\nLONGITUDE = 133.133\nTIMEZONE = \"Asia/Tokyo\"\n\nOPEN_METEO_URL = \"https://archive-api.open-meteo.com/v1/archive\"\nDAILY_VARS = [\n \"temperature_2m_mean\",\n \"temperature_2m_max\",\n \"temperature_2m_min\",\n \"sunshine_duration\",\n \"precipitation_sum\",\n \"wind_speed_10m_max\",\n \"surface_pressure_min\",\n]\n\n\ndef main():\n api_key = wmill.get_variable(\"u/admin/KEINASYSTEM_API_KEY\")\n base_url = wmill.get_variable(\"u/admin/KEINASYSTEM_API_URL\").rstrip(\"/\")\n sync_url = f\"{base_url}/api/weather/sync/\"\n\n yesterday = (datetime.date.today() - datetime.timedelta(days=1)).isoformat()\n print(f\"Fetching weather data for {yesterday} ...\")\n\n params = {\n \"latitude\": LATITUDE,\n \"longitude\": LONGITUDE,\n \"start_date\": yesterday,\n \"end_date\": yesterday,\n \"daily\": DAILY_VARS,\n \"timezone\": TIMEZONE,\n }\n resp = requests.get(OPEN_METEO_URL, params=params, timeout=30)\n if resp.status_code != 200:\n raise Exception(f\"Open-Meteo API error: {resp.status_code} {resp.text[:300]}\")\n\n daily = resp.json().get(\"daily\", {})\n dates = daily.get(\"time\", [])\n if not dates:\n print(\"No data returned from Open-Meteo.\")\n return {\"status\": \"no_data\"}\n\n sunshine_raw = daily.get(\"sunshine_duration\", [])\n records = []\n for i, d in enumerate(dates):\n sun_sec = sunshine_raw[i]\n records.append({\n \"date\": d,\n \"temp_mean\": daily[\"temperature_2m_mean\"][i],\n \"temp_max\": daily[\"temperature_2m_max\"][i],\n \"temp_min\": daily[\"temperature_2m_min\"][i],\n \"sunshine_h\": round(sun_sec / 3600, 2) if sun_sec is not None else None,\n \"precip_mm\": daily[\"precipitation_sum\"][i],\n \"wind_max\": daily[\"wind_speed_10m_max\"][i],\n \"pressure_min\": daily[\"surface_pressure_min\"][i],\n })\n\n headers = {\n \"X-API-Key\": api_key,\n \"Content-Type\": \"application/json\",\n }\n post_resp = requests.post(sync_url, json=records, headers=headers, timeout=30)\n if post_resp.status_code not in (200, 201):\n raise Exception(f\"Keinasystem sync error: {post_resp.status_code} {post_resp.text[:300]}\")\n\n result = post_resp.json()\n print(f\"Sync complete: {result}\")\n return result\n",
|
||||
"language": "python3",
|
||||
"input_transforms": {}
|
||||
},
|
||||
"summary": "気象データ取得・同期"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"order": [],
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,20 @@
|
||||
/**
|
||||
* alexa_speak.ts
|
||||
* 指定した Echo デバイスにテキストを読み上げさせる Windmill スクリプト
|
||||
*
|
||||
* パラメータ:
|
||||
* device - ドロップダウンから選択するデバイス(内部的にはシリアル番号)
|
||||
* text - 読み上げるテキスト
|
||||
*/
|
||||
|
||||
const ALEXA_API_URL = "http://alexa_api:3500";
|
||||
|
||||
type DeviceOption = { value: string; label: string };
|
||||
|
||||
const FALLBACK_DEVICE_OPTIONS: DeviceOption[] = [
|
||||
{ value: "G0922H085165007R", label: "プレハブ (G0922H085165007R)" },
|
||||
{ value: "G8M2DB08522600RL", label: "リビングエコー1 (G8M2DB08522600RL)" },
|
||||
{ value: "G8M2DB08522503WF", label: "リビングエコー2 (G8M2DB08522503WF)" },
|
||||
{ value: "G0922H08525302K5", label: "オフィスの右エコー (G0922H08525302K5)" },
|
||||
{ value: "G0922H08525302J9", label: "オフィスの左エコー (G0922H08525302J9)" },
|
||||
{ value: "G8M2HN08534302XH", label: "寝室のエコー (G8M2HN08534302XH)" },
|
||||
];
|
||||
|
||||
// Windmill Dynamic Select: 引数名 `device` に対応する `DynSelect_device` と `device()` を定義
|
||||
export type DynSelect_device = string;
|
||||
|
||||
export async function device(): Promise<DeviceOption[]> {
|
||||
try {
|
||||
const res = await fetch(`${ALEXA_API_URL}/devices`);
|
||||
if (!res.ok) return FALLBACK_DEVICE_OPTIONS;
|
||||
|
||||
const devices = (await res.json()) as Array<{
|
||||
name?: string;
|
||||
serial?: string;
|
||||
family?: string;
|
||||
}>;
|
||||
|
||||
const options = devices
|
||||
.filter((d) => d.family === "ECHO" && d.serial)
|
||||
.map((d) => ({
|
||||
value: d.serial as string,
|
||||
label: `${d.name ?? d.serial} (${d.serial})`,
|
||||
}))
|
||||
.sort((a, b) => a.label.localeCompare(b.label, "ja"));
|
||||
|
||||
return options.length > 0 ? options : FALLBACK_DEVICE_OPTIONS;
|
||||
} catch {
|
||||
return FALLBACK_DEVICE_OPTIONS;
|
||||
}
|
||||
}
|
||||
|
||||
export async function main(
|
||||
device: DynSelect_device,
|
||||
device: string,
|
||||
text: string,
|
||||
): Promise<{ ok: boolean; device: string; text: string }> {
|
||||
const ALEXA_API_URL = "http://alexa_api:3500";
|
||||
|
||||
const res = await fetch(`${ALEXA_API_URL}/speak`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ device, text }),
|
||||
body: JSON.stringify({ device, text }), // ← SSMLなし、素のテキスト
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
`alexa-api error ${res.status}: ${JSON.stringify(body)}`
|
||||
);
|
||||
throw new Error(`alexa-api error ${res.status}: ${JSON.stringify(body)}`);
|
||||
}
|
||||
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
|
||||
106
state/flows.list.json
Normal file
106
state/flows.list.json
Normal file
@@ -0,0 +1,106 @@
|
||||
[
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "u/akiracraftwork/hourly_chime",
|
||||
"summary": "鳩時計機能",
|
||||
"description": "",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-03-03T05:37:39.969305Z",
|
||||
"archived": false,
|
||||
"extra_perms": {},
|
||||
"starred": false,
|
||||
"has_draft": false,
|
||||
"ws_error_handler_muted": false
|
||||
},
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "f/dev/textout",
|
||||
"summary": "Display current time on startup",
|
||||
"description": "",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-03-02T05:05:05.215985Z",
|
||||
"archived": false,
|
||||
"extra_perms": {},
|
||||
"starred": false,
|
||||
"has_draft": false,
|
||||
"ws_error_handler_muted": false
|
||||
},
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "f/dev/konnnichiha",
|
||||
"summary": "Print greeting",
|
||||
"description": "",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-03-02T04:53:56.968574Z",
|
||||
"archived": false,
|
||||
"extra_perms": {},
|
||||
"starred": false,
|
||||
"has_draft": false,
|
||||
"ws_error_handler_muted": false
|
||||
},
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "u/antigravity/git_sync",
|
||||
"summary": "Git Sync Workflow",
|
||||
"description": "Automatically sync Windmill workflows to Git repository (sync branch)",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-03-01T17:28:14.331046Z",
|
||||
"archived": false,
|
||||
"extra_perms": {},
|
||||
"starred": false,
|
||||
"has_draft": false,
|
||||
"ws_error_handler_muted": false
|
||||
},
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "f/weather/weather_sync",
|
||||
"summary": "Weather Sync - 気象データ日次同期",
|
||||
"description": "Open-Meteo から昨日の気象データを取得し、Keinasystem DB に保存する。毎朝6時実行。",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-02-28T04:31:27.835748Z",
|
||||
"archived": false,
|
||||
"extra_perms": {},
|
||||
"starred": false,
|
||||
"has_draft": false,
|
||||
"ws_error_handler_muted": false
|
||||
},
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "f/mail/mail_filter",
|
||||
"summary": "メールフィルタリング",
|
||||
"description": "IMAPで新着メールを受信し、送信者ルール確認→LLM判定→LINE通知を行う。Keinasystemと連携。Gmail→Hotmail→Xserverの順で段階的に有効化する。",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-02-24T06:41:54.748865Z",
|
||||
"archived": false,
|
||||
"extra_perms": {},
|
||||
"starred": false,
|
||||
"has_draft": false,
|
||||
"ws_error_handler_muted": false
|
||||
},
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "f/shiraou/shiraou_notification",
|
||||
"summary": "白皇集落営農 変更通知",
|
||||
"description": "shiraou.keinafarm.net の予約・実績変更をポーリングし、変更があればLINEで管理者に通知する。5分毎に実行。",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-02-21T06:33:11.078673Z",
|
||||
"archived": false,
|
||||
"extra_perms": {},
|
||||
"starred": false,
|
||||
"has_draft": false,
|
||||
"ws_error_handler_muted": false
|
||||
},
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "f/app_custom/system_heartbeat",
|
||||
"summary": "Windmill Heartbeat - システム自己診断",
|
||||
"description": "Windmillの動作確認用ワークフロー。UUID生成、時刻取得、計算チェック、HTTPヘルスチェック、年度判定を行い、全ステップの正常性を検証する。",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-02-21T03:43:55.495111Z",
|
||||
"archived": false,
|
||||
"extra_perms": {},
|
||||
"starred": false,
|
||||
"has_draft": false,
|
||||
"ws_error_handler_muted": false
|
||||
}
|
||||
]
|
||||
78
state/remote_index.current.json
Normal file
78
state/remote_index.current.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"synced_at": "2026-03-03T07:09:55Z",
|
||||
"workspace": "admins",
|
||||
"scripts": {
|
||||
"u/admin/alexa_speak": {
|
||||
"hash": "3783872112d1a24c",
|
||||
"updated_at": "2026-03-03T02:57:13.068287Z"
|
||||
}
|
||||
},
|
||||
"flows": {
|
||||
"u/akiracraftwork/hourly_chime": {
|
||||
"updated_at": "2026-03-03T05:37:39.969305Z"
|
||||
},
|
||||
"f/dev/textout": {
|
||||
"updated_at": "2026-03-02T05:05:05.215985Z"
|
||||
},
|
||||
"f/dev/konnnichiha": {
|
||||
"updated_at": "2026-03-02T04:53:56.968574Z"
|
||||
},
|
||||
"u/antigravity/git_sync": {
|
||||
"updated_at": "2026-03-01T17:28:14.331046Z"
|
||||
},
|
||||
"f/weather/weather_sync": {
|
||||
"updated_at": "2026-02-28T04:31:27.835748Z"
|
||||
},
|
||||
"f/mail/mail_filter": {
|
||||
"updated_at": "2026-02-24T06:41:54.748865Z"
|
||||
},
|
||||
"f/shiraou/shiraou_notification": {
|
||||
"updated_at": "2026-02-21T06:33:11.078673Z"
|
||||
},
|
||||
"f/app_custom/system_heartbeat": {
|
||||
"updated_at": "2026-02-21T03:43:55.495111Z"
|
||||
}
|
||||
},
|
||||
"schedules": {
|
||||
"u/akiracraftwork/hourly_chime": {
|
||||
"schedule": "0 0 * * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "u/akiracraftwork/hourly_chime",
|
||||
"is_flow": true,
|
||||
"updated_at": "2026-03-03T04:44:03.309346Z"
|
||||
},
|
||||
"f/weather/weather_sync": {
|
||||
"schedule": "0 0 6 * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "f/weather/weather_sync",
|
||||
"is_flow": true,
|
||||
"updated_at": "2026-02-28T04:31:41.375049Z"
|
||||
},
|
||||
"f/mail/mail_filter_schedule": {
|
||||
"schedule": "0 */10 * * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "f/mail/mail_filter",
|
||||
"is_flow": true,
|
||||
"updated_at": "2026-02-24T06:42:06.977249Z"
|
||||
},
|
||||
"f/shiraou/shiraou_notification_every_5min": {
|
||||
"schedule": "0 */5 * * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "f/shiraou/shiraou_notification",
|
||||
"is_flow": true,
|
||||
"updated_at": "2026-02-21T06:18:34.967961Z"
|
||||
},
|
||||
"u/antigravity/git_sync": {
|
||||
"schedule": "0 */30 * * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "u/antigravity/git_sync",
|
||||
"is_flow": true,
|
||||
"updated_at": "2026-02-19T06:38:19.867037Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
78
state/remote_index.json
Normal file
78
state/remote_index.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"synced_at": "2026-03-03T07:12:56Z",
|
||||
"workspace": "admins",
|
||||
"scripts": {
|
||||
"u/admin/alexa_speak": {
|
||||
"hash": "3783872112d1a24c",
|
||||
"updated_at": "2026-03-03T02:57:13.068287Z"
|
||||
}
|
||||
},
|
||||
"flows": {
|
||||
"u/akiracraftwork/hourly_chime": {
|
||||
"updated_at": "2026-03-03T05:37:39.969305Z"
|
||||
},
|
||||
"f/dev/textout": {
|
||||
"updated_at": "2026-03-02T05:05:05.215985Z"
|
||||
},
|
||||
"f/dev/konnnichiha": {
|
||||
"updated_at": "2026-03-02T04:53:56.968574Z"
|
||||
},
|
||||
"u/antigravity/git_sync": {
|
||||
"updated_at": "2026-03-01T17:28:14.331046Z"
|
||||
},
|
||||
"f/weather/weather_sync": {
|
||||
"updated_at": "2026-02-28T04:31:27.835748Z"
|
||||
},
|
||||
"f/mail/mail_filter": {
|
||||
"updated_at": "2026-02-24T06:41:54.748865Z"
|
||||
},
|
||||
"f/shiraou/shiraou_notification": {
|
||||
"updated_at": "2026-02-21T06:33:11.078673Z"
|
||||
},
|
||||
"f/app_custom/system_heartbeat": {
|
||||
"updated_at": "2026-02-21T03:43:55.495111Z"
|
||||
}
|
||||
},
|
||||
"schedules": {
|
||||
"u/akiracraftwork/hourly_chime": {
|
||||
"schedule": "0 0 * * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "u/akiracraftwork/hourly_chime",
|
||||
"is_flow": true,
|
||||
"updated_at": "2026-03-03T04:44:03.309346Z"
|
||||
},
|
||||
"f/weather/weather_sync": {
|
||||
"schedule": "0 0 6 * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "f/weather/weather_sync",
|
||||
"is_flow": true,
|
||||
"updated_at": "2026-02-28T04:31:41.375049Z"
|
||||
},
|
||||
"f/mail/mail_filter_schedule": {
|
||||
"schedule": "0 */10 * * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "f/mail/mail_filter",
|
||||
"is_flow": true,
|
||||
"updated_at": "2026-02-24T06:42:06.977249Z"
|
||||
},
|
||||
"f/shiraou/shiraou_notification_every_5min": {
|
||||
"schedule": "0 */5 * * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "f/shiraou/shiraou_notification",
|
||||
"is_flow": true,
|
||||
"updated_at": "2026-02-21T06:18:34.967961Z"
|
||||
},
|
||||
"u/antigravity/git_sync": {
|
||||
"schedule": "0 */30 * * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "u/antigravity/git_sync",
|
||||
"is_flow": true,
|
||||
"updated_at": "2026-02-19T06:38:19.867037Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
67
state/schedules.list.json
Normal file
67
state/schedules.list.json
Normal file
@@ -0,0 +1,67 @@
|
||||
[
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "u/akiracraftwork/hourly_chime",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-03-03T04:44:03.309346Z",
|
||||
"schedule": "0 0 * * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "u/akiracraftwork/hourly_chime",
|
||||
"is_flow": true,
|
||||
"summary": null,
|
||||
"extra_perms": {}
|
||||
},
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "f/weather/weather_sync",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-02-28T04:31:41.375049Z",
|
||||
"schedule": "0 0 6 * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "f/weather/weather_sync",
|
||||
"is_flow": true,
|
||||
"summary": null,
|
||||
"extra_perms": {}
|
||||
},
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "f/mail/mail_filter_schedule",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-02-24T06:42:06.977249Z",
|
||||
"schedule": "0 */10 * * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "f/mail/mail_filter",
|
||||
"is_flow": true,
|
||||
"summary": null,
|
||||
"extra_perms": {}
|
||||
},
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "f/shiraou/shiraou_notification_every_5min",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-02-21T06:18:34.967961Z",
|
||||
"schedule": "0 */5 * * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "f/shiraou/shiraou_notification",
|
||||
"is_flow": true,
|
||||
"summary": null,
|
||||
"extra_perms": {}
|
||||
},
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"path": "u/antigravity/git_sync",
|
||||
"edited_by": "akiracraftwork@gmail.com",
|
||||
"edited_at": "2026-02-19T06:38:19.867037Z",
|
||||
"schedule": "0 */30 * * * *",
|
||||
"timezone": "Asia/Tokyo",
|
||||
"enabled": true,
|
||||
"script_path": "u/antigravity/git_sync",
|
||||
"is_flow": true,
|
||||
"summary": null,
|
||||
"extra_perms": {}
|
||||
}
|
||||
]
|
||||
18
state/scripts.list.json
Normal file
18
state/scripts.list.json
Normal file
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"hash": "3783872112d1a24c",
|
||||
"path": "u/admin/alexa_speak",
|
||||
"summary": "Echo デバイスに TTS で読み上げ",
|
||||
"created_at": "2026-03-03T02:57:13.068287Z",
|
||||
"archived": false,
|
||||
"extra_perms": {},
|
||||
"language": "bun",
|
||||
"starred": false,
|
||||
"tag": null,
|
||||
"description": "指定した Echo デバイスにテキストを読み上げさせる",
|
||||
"has_draft": false,
|
||||
"has_deploy_errors": false,
|
||||
"ws_error_handler_muted": false,
|
||||
"kind": "script"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user