pi-spotify-widget 0.1.15 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.19] - 2026-10-19
4
+
5
+ ### Changed
6
+
7
+ - Add a copyable PowerShell Client ID setup example to the packaged examples guide.
8
+
9
+ ## [0.1.18] - 2026-10-16
10
+
11
+ ### Changed
12
+
13
+ - Verify compatibility with Pi SDK 1.2.0 and pin the SDK dev/test dependencies.
14
+
15
+ ## [0.1.17] - 2026-10-12
16
+
17
+ ### Changed
18
+
19
+ - Verify compatibility with Pi SDK 1.1.0 and pin the SDK dev/test dependencies.
20
+
21
+ ### Fixed
22
+
23
+ - Keep a stable untagged version eligible for manual release after follow-up commits.
24
+ - Resume only missing release stages when an existing version tag is retried.
25
+
26
+ ## [0.1.16] - 2026-09-30
27
+
28
+ ### Changed
29
+
30
+ - Update `@earendil-works/pi-*` dependencies to `0.99.1`.
31
+
3
32
  ## [0.1.15] - 2026-09-27
4
33
 
5
34
  - chore: periodic patch bump after 7+ days without npm publish
package/README.md CHANGED
@@ -38,7 +38,7 @@ pi install npm:pi-spotify-widget
38
38
  Pin a specific version when you want reproducible installs:
39
39
 
40
40
  ```bash
41
- pi install npm:pi-spotify-widget@0.1.15
41
+ pi install npm:pi-spotify-widget@0.1.19
42
42
  ```
43
43
 
44
44
  Install into the current project instead of your user Pi settings:
@@ -155,7 +155,7 @@ npm version patch --no-git-tag-version
155
155
  git push
156
156
  ```
157
157
 
158
- On `main`, `.github/workflows/auto-release.yml` checks `package.json` version. If `v<version>` does not exist yet, it creates the tag, creates the GitHub Release, then explicitly dispatches `.github/workflows/publish.yml` for that tag.
158
+ Merging or pushing a version bump runs CI only; it does not release or publish. After separate release authorization, manually dispatch `.github/workflows/auto-release.yml` on `main` to create the tag and GitHub Release, then hand off to `.github/workflows/publish.yml`.
159
159
 
160
160
  See [`docs/release.md`](docs/release.md) for setup details.
161
161
 
package/docs/examples.md CHANGED
@@ -41,6 +41,10 @@ Redirect URI: `http://127.0.0.1:8888/callback`
41
41
 
42
42
  Optional environment variable before starting Pi:
43
43
 
44
+ ```powershell
45
+ $env:PI_SPOTIFY_CLIENT_ID = "your_client_id_here"
46
+ ```
47
+
44
48
  ```bash
45
49
  export PI_SPOTIFY_CLIENT_ID="your_client_id_here"
46
50
  ```
package/docs/release.md CHANGED
@@ -19,13 +19,15 @@ npm version patch --no-git-tag-version
19
19
  git push
20
20
  ```
21
21
 
22
- On `main`, `.github/workflows/auto-release.yml` checks `package.json` version. If `v<version>` does not exist yet, it creates the tag, creates the GitHub Release, then explicitly dispatches `.github/workflows/publish.yml` for that tag.
22
+ Code pushes and merges, including version bumps on `main`, run CI only. They do not create tags/releases or publish npm packages.
23
+
24
+ After separate release authorization, manually dispatch `.github/workflows/auto-release.yml` on `main`. It validates the stable version and inspects the matching tag, GitHub Release, and publish-workflow runs. It creates only missing stages: an existing tag is retained, an existing release is not recreated, and publish is dispatched only when no successful or active run already exists for that tag. A failed publish run can be retried without recreating the tag or release. It does not require the version to differ from the previous commit, so an untagged version remains releasable after later documentation-only commits. Other branches are rejected by the workflow gate. Do not perform this protected release action as part of routine compatibility verification.
23
25
 
24
26
  `publish.yml` also listens for `release.published` and manual `v*.*.*` tag pushes, but the explicit dispatch from `auto-release.yml` remains the primary path because `GITHUB_TOKEN` tag pushes do not reliably fan out into a second workflow on their own.
25
27
 
26
28
  ## Workflow guardrail
27
29
 
28
- Tags or releases created by `GITHUB_TOKEN` do not reliably fan out into another workflow through normal `push.tags` or `release.published` triggers. This repository keeps publishing reliable by having `auto-release.yml` explicitly dispatch `publish.yml` after creating the tag/release.
30
+ Tags or releases created by `GITHUB_TOKEN` do not reliably fan out into another workflow through normal `push.tags` or `release.published` triggers. The manually dispatched `auto-release.yml` explicitly dispatches `publish.yml` after creating the tag/release.
29
31
 
30
32
  ## GitHub Actions requirements
31
33
 
@@ -0,0 +1,284 @@
1
+ {
2
+ "schema_version": 1,
3
+ "verification_contract": "pi-sdk-behavior-v2",
4
+ "sdk_versions": {
5
+ "@earendil-works/pi-agent-core": "1.2.0",
6
+ "@earendil-works/pi-ai": "1.2.0",
7
+ "@earendil-works/pi-coding-agent": "1.2.0",
8
+ "@earendil-works/pi-tui": "1.2.0"
9
+ },
10
+ "platform": "win32",
11
+ "model_inference": "none; actual Pi host handles explicit extension commands offline",
12
+ "unhandled_errors": 0,
13
+ "extension_load_errors": 0,
14
+ "cases": [
15
+ {
16
+ "name": "extension_load",
17
+ "command": "npm run smoke:pi -- --case extension_load",
18
+ "exit_code": 0,
19
+ "passed": true,
20
+ "evidence": {
21
+ "note": "Captured actual Pi host/native-shell stdout and stderr. The Pi SDK, loader, extension lifecycle, command handler, RPC protocol, and widget implementation are not mocked; no model inference or Spotify OAuth/API request is used.",
22
+ "runs": [
23
+ {
24
+ "exit_code": 0,
25
+ "signal": null,
26
+ "error": null,
27
+ "stdout": "{\"type\":\"extension_ui_request\",\"id\":\"eef3544a-4f4a-4e2d-8f1c-e1fc18b9b178\",\"method\":\"setWidget\",\"widgetKey\":\"pi-spotify-widget\",\"widgetLines\":[\"\\u001b[39m\\u001b[2m♫ Run /spotify:login\\u001b[22;39m\"]}\n{\"type\":\"extension_ui_request\",\"id\":\"0fc4c63f-8f8f-47f5-92de-bea6ef3eea80\",\"method\":\"notify\",\"message\":\"### Spotify playback controls\",\"notifyType\":\"info\"}\n{\"type\":\"message_start\",\"message\":{\"role\":\"custom\",\"customType\":\"spotify-widget\",\"content\":\"### Spotify playback controls\\n\\nユーザーが曲操作や再生/停止を依頼したら、下表から意図に最も近い **Pi コマンド** を1つ選んで実行してください。\\n推測が曖昧なときは `/spotify:help` の一覧を参照するか、ユーザーに確認してください。\\n\\n| Command | Action | Example user phrases |\\n|---|---|---|\\n| /spotify:prev | 前の曲に戻る | 前の曲 · 前へ · 戻して · previous track |\\n| /spotify:next | 次の曲に進む | 次の曲 · 次へ · スキップ · skip |\\n| /spotify:pause | 再生を一時停止する(停止) | 止めて · 停止 · ストップ · 一時停止 |\\n| /spotify:play | 再生を再開する | 再生 · 再開 · play · resume |\\n\\nDiscovery / share:\\n| /spotify:share | Now Playing を X に投稿(API 設定時)または共有画面を開く | シェア · Xに投稿 · post to X · tweet |\\n| /spotify:play-uri | Spotify 曲 URL / URI を指定して再生 | この曲再生 · play this link · play uri |\\n\\nOther useful commands:\\n- `/spotify:refresh` — Now Playing を即時更新\\n- `/spotify:status` — 認証状態(token 値は出さない)\\n\\nNote: playback control requires `user-modify-playback-state` scope. If controls fail after an upgrade, run `/spotify:login` again.\",\"display\":true,\"details\":{\"kind\":\"help\"},\"timestamp\":1792110351657}}\n{\"type\":\"message_end\",\"message\":{\"role\":\"custom\",\"customType\":\"spotify-widget\",\"content\":\"### Spotify playback controls\\n\\nユーザーが曲操作や再生/停止を依頼したら、下表から意図に最も近い **Pi コマンド** を1つ選んで実行してください。\\n推測が曖昧なときは `/spotify:help` の一覧を参照するか、ユーザーに確認してください。\\n\\n| Command | Action | Example user phrases |\\n|---|---|---|\\n| /spotify:prev | 前の曲に戻る | 前の曲 · 前へ · 戻して · previous track |\\n| /spotify:next | 次の曲に進む | 次の曲 · 次へ · スキップ · skip |\\n| /spotify:pause | 再生を一時停止する(停止) | 止めて · 停止 · ストップ · 一時停止 |\\n| /spotify:play | 再生を再開する | 再生 · 再開 · play · resume |\\n\\nDiscovery / share:\\n| /spotify:share | Now Playing を X に投稿(API 設定時)または共有画面を開く | シェア · Xに投稿 · post to X · tweet |\\n| /spotify:play-uri | Spotify 曲 URL / URI を指定して再生 | この曲再生 · play this link · play uri |\\n\\nOther useful commands:\\n- `/spotify:refresh` — Now Playing を即時更新\\n- `/spotify:status` — 認証状態(token 値は出さない)\\n\\nNote: playback control requires `user-modify-playback-state` scope. If controls fail after an upgrade, run `/spotify:login` again.\",\"display\":true,\"details\":{\"kind\":\"help\"},\"timestamp\":1792110351657}}\n{\"type\":\"response\",\"command\":\"prompt\",\"success\":true,\"data\":{\"disposition\":\"handled\"}}\n",
28
+ "stderr": ""
29
+ }
30
+ ]
31
+ },
32
+ "assertions": [
33
+ {
34
+ "id": "extension_registered",
35
+ "expected": "The real Pi host runs session_start for pi-spotify-widget, registers /spotify:help, and returns its handled feature response.",
36
+ "observed": {
37
+ "widget": {
38
+ "type": "extension_ui_request",
39
+ "id": "eef3544a-4f4a-4e2d-8f1c-e1fc18b9b178",
40
+ "method": "setWidget",
41
+ "widgetKey": "pi-spotify-widget",
42
+ "widgetLines": [
43
+ "\u001b[39m\u001b[2m♫ Run /spotify:login\u001b[22;39m"
44
+ ]
45
+ },
46
+ "response": {
47
+ "type": "response",
48
+ "command": "prompt",
49
+ "success": true,
50
+ "data": {
51
+ "disposition": "handled"
52
+ }
53
+ },
54
+ "featureMessage": {
55
+ "type": "message_end",
56
+ "message": {
57
+ "role": "custom",
58
+ "customType": "spotify-widget",
59
+ "content": "### Spotify playback controls\n\nユーザーが曲操作や再生/停止を依頼したら、下表から意図に最も近い **Pi コマンド** を1つ選んで実行してください。\n推測が曖昧なときは `/spotify:help` の一覧を参照するか、ユーザーに確認してください。\n\n| Command | Action | Example user phrases |\n|---|---|---|\n| /spotify:prev | 前の曲に戻る | 前の曲 · 前へ · 戻して · previous track |\n| /spotify:next | 次の曲に進む | 次の曲 · 次へ · スキップ · skip |\n| /spotify:pause | 再生を一時停止する(停止) | 止めて · 停止 · ストップ · 一時停止 |\n| /spotify:play | 再生を再開する | 再生 · 再開 · play · resume |\n\nDiscovery / share:\n| /spotify:share | Now Playing を X に投稿(API 設定時)または共有画面を開く | シェア · Xに投稿 · post to X · tweet |\n| /spotify:play-uri | Spotify 曲 URL / URI を指定して再生 | この曲再生 · play this link · play uri |\n\nOther useful commands:\n- `/spotify:refresh` — Now Playing を即時更新\n- `/spotify:status` — 認証状態(token 値は出さない)\n\nNote: playback control requires `user-modify-playback-state` scope. If controls fail after an upgrade, run `/spotify:login` again.",
60
+ "display": true,
61
+ "details": {
62
+ "kind": "help"
63
+ },
64
+ "timestamp": 1792110351657
65
+ }
66
+ }
67
+ },
68
+ "passed": true
69
+ }
70
+ ]
71
+ },
72
+ {
73
+ "name": "happy_path",
74
+ "command": "npm run smoke:pi -- --case happy_path",
75
+ "exit_code": 0,
76
+ "passed": true,
77
+ "evidence": {
78
+ "note": "Captured actual Pi host/native-shell stdout and stderr. The Pi SDK, loader, extension lifecycle, command handler, RPC protocol, and widget implementation are not mocked; no model inference or Spotify OAuth/API request is used.",
79
+ "runs": [
80
+ {
81
+ "exit_code": 0,
82
+ "signal": null,
83
+ "error": null,
84
+ "stdout": "{\"type\":\"extension_ui_request\",\"id\":\"66c4d889-6384-4544-8c2c-4834eee7a3cb\",\"method\":\"setWidget\",\"widgetKey\":\"pi-spotify-widget\",\"widgetLines\":[\"\\u001b[39m\\u001b[2m♫ Run /spotify:login\\u001b[22;39m\"]}\n{\"type\":\"extension_ui_request\",\"id\":\"d2465817-cb66-4573-acbf-b95c848be979\",\"method\":\"notify\",\"message\":\"### Spotify playback controls\",\"notifyType\":\"info\"}\n{\"type\":\"message_start\",\"message\":{\"role\":\"custom\",\"customType\":\"spotify-widget\",\"content\":\"### Spotify playback controls\\n\\nユーザーが曲操作や再生/停止を依頼したら、下表から意図に最も近い **Pi コマンド** を1つ選んで実行してください。\\n推測が曖昧なときは `/spotify:help` の一覧を参照するか、ユーザーに確認してください。\\n\\n| Command | Action | Example user phrases |\\n|---|---|---|\\n| /spotify:prev | 前の曲に戻る | 前の曲 · 前へ · 戻して · previous track |\\n| /spotify:next | 次の曲に進む | 次の曲 · 次へ · スキップ · skip |\\n| /spotify:pause | 再生を一時停止する(停止) | 止めて · 停止 · ストップ · 一時停止 |\\n| /spotify:play | 再生を再開する | 再生 · 再開 · play · resume |\\n\\nDiscovery / share:\\n| /spotify:share | Now Playing を X に投稿(API 設定時)または共有画面を開く | シェア · Xに投稿 · post to X · tweet |\\n| /spotify:play-uri | Spotify 曲 URL / URI を指定して再生 | この曲再生 · play this link · play uri |\\n\\nOther useful commands:\\n- `/spotify:refresh` — Now Playing を即時更新\\n- `/spotify:status` — 認証状態(token 値は出さない)\\n\\nNote: playback control requires `user-modify-playback-state` scope. If controls fail after an upgrade, run `/spotify:login` again.\",\"display\":true,\"details\":{\"kind\":\"help\"},\"timestamp\":1792110351956}}\n{\"type\":\"message_end\",\"message\":{\"role\":\"custom\",\"customType\":\"spotify-widget\",\"content\":\"### Spotify playback controls\\n\\nユーザーが曲操作や再生/停止を依頼したら、下表から意図に最も近い **Pi コマンド** を1つ選んで実行してください。\\n推測が曖昧なときは `/spotify:help` の一覧を参照するか、ユーザーに確認してください。\\n\\n| Command | Action | Example user phrases |\\n|---|---|---|\\n| /spotify:prev | 前の曲に戻る | 前の曲 · 前へ · 戻して · previous track |\\n| /spotify:next | 次の曲に進む | 次の曲 · 次へ · スキップ · skip |\\n| /spotify:pause | 再生を一時停止する(停止) | 止めて · 停止 · ストップ · 一時停止 |\\n| /spotify:play | 再生を再開する | 再生 · 再開 · play · resume |\\n\\nDiscovery / share:\\n| /spotify:share | Now Playing を X に投稿(API 設定時)または共有画面を開く | シェア · Xに投稿 · post to X · tweet |\\n| /spotify:play-uri | Spotify 曲 URL / URI を指定して再生 | この曲再生 · play this link · play uri |\\n\\nOther useful commands:\\n- `/spotify:refresh` — Now Playing を即時更新\\n- `/spotify:status` — 認証状態(token 値は出さない)\\n\\nNote: playback control requires `user-modify-playback-state` scope. If controls fail after an upgrade, run `/spotify:login` again.\",\"display\":true,\"details\":{\"kind\":\"help\"},\"timestamp\":1792110351956}}\n{\"type\":\"response\",\"command\":\"prompt\",\"success\":true,\"data\":{\"disposition\":\"handled\"}}\n",
85
+ "stderr": ""
86
+ }
87
+ ]
88
+ },
89
+ "assertions": [
90
+ {
91
+ "id": "feature_response",
92
+ "expected": "The real /spotify:help command is handled and emits the visible widget help with playback commands.",
93
+ "observed": {
94
+ "response": {
95
+ "type": "response",
96
+ "command": "prompt",
97
+ "success": true,
98
+ "data": {
99
+ "disposition": "handled"
100
+ }
101
+ },
102
+ "featureMessage": {
103
+ "type": "message_end",
104
+ "message": {
105
+ "role": "custom",
106
+ "customType": "spotify-widget",
107
+ "content": "### Spotify playback controls\n\nユーザーが曲操作や再生/停止を依頼したら、下表から意図に最も近い **Pi コマンド** を1つ選んで実行してください。\n推測が曖昧なときは `/spotify:help` の一覧を参照するか、ユーザーに確認してください。\n\n| Command | Action | Example user phrases |\n|---|---|---|\n| /spotify:prev | 前の曲に戻る | 前の曲 · 前へ · 戻して · previous track |\n| /spotify:next | 次の曲に進む | 次の曲 · 次へ · スキップ · skip |\n| /spotify:pause | 再生を一時停止する(停止) | 止めて · 停止 · ストップ · 一時停止 |\n| /spotify:play | 再生を再開する | 再生 · 再開 · play · resume |\n\nDiscovery / share:\n| /spotify:share | Now Playing を X に投稿(API 設定時)または共有画面を開く | シェア · Xに投稿 · post to X · tweet |\n| /spotify:play-uri | Spotify 曲 URL / URI を指定して再生 | この曲再生 · play this link · play uri |\n\nOther useful commands:\n- `/spotify:refresh` — Now Playing を即時更新\n- `/spotify:status` — 認証状態(token 値は出さない)\n\nNote: playback control requires `user-modify-playback-state` scope. If controls fail after an upgrade, run `/spotify:login` again.",
108
+ "display": true,
109
+ "details": {
110
+ "kind": "help"
111
+ },
112
+ "timestamp": 1792110351956
113
+ }
114
+ }
115
+ },
116
+ "passed": true
117
+ }
118
+ ]
119
+ },
120
+ {
121
+ "name": "error_path",
122
+ "command": "npm run smoke:pi -- --case error_path",
123
+ "exit_code": 0,
124
+ "passed": true,
125
+ "evidence": {
126
+ "note": "Captured actual Pi host/native-shell stdout and stderr. The Pi SDK, loader, extension lifecycle, command handler, RPC protocol, and widget implementation are not mocked; no model inference or Spotify OAuth/API request is used.",
127
+ "runs": [
128
+ {
129
+ "exit_code": 0,
130
+ "signal": null,
131
+ "error": null,
132
+ "stdout": "{\"type\":\"extension_ui_request\",\"id\":\"4ce58e34-3f32-4e38-bbec-4e1397964d74\",\"method\":\"setWidget\",\"widgetKey\":\"pi-spotify-widget\",\"widgetLines\":[\"\\u001b[39m\\u001b[2m♫ Run /spotify:login\\u001b[22;39m\"]}\n{\"type\":\"extension_ui_request\",\"id\":\"a4112fda-3c96-427d-9ab4-35a0fd026855\",\"method\":\"notify\",\"message\":\"Could not parse Spotify track URL or URI.\",\"notifyType\":\"warning\"}\n{\"type\":\"response\",\"command\":\"prompt\",\"success\":true,\"data\":{\"disposition\":\"handled\"}}\n",
133
+ "stderr": ""
134
+ }
135
+ ]
136
+ },
137
+ "assertions": [
138
+ {
139
+ "id": "handled_error",
140
+ "expected": "A malformed Spotify track link is caught by the extension and reported as a warning without a Spotify network request.",
141
+ "observed": {
142
+ "response": {
143
+ "type": "response",
144
+ "command": "prompt",
145
+ "success": true,
146
+ "data": {
147
+ "disposition": "handled"
148
+ }
149
+ },
150
+ "handledError": {
151
+ "type": "extension_ui_request",
152
+ "id": "a4112fda-3c96-427d-9ab4-35a0fd026855",
153
+ "method": "notify",
154
+ "message": "Could not parse Spotify track URL or URI.",
155
+ "notifyType": "warning"
156
+ }
157
+ },
158
+ "passed": true
159
+ }
160
+ ]
161
+ },
162
+ {
163
+ "name": "reload_cleanup",
164
+ "command": "npm run smoke:pi -- --case reload_cleanup",
165
+ "exit_code": 0,
166
+ "passed": true,
167
+ "evidence": {
168
+ "note": "Captured actual Pi host/native-shell stdout and stderr. The Pi SDK, loader, extension lifecycle, command handler, RPC protocol, and widget implementation are not mocked; no model inference or Spotify OAuth/API request is used.",
169
+ "runs": [
170
+ {
171
+ "exit_code": 0,
172
+ "signal": null,
173
+ "error": null,
174
+ "stdout": "{\"type\":\"extension_ui_request\",\"id\":\"ef22d22f-8c68-489f-8af9-0f8a4390a369\",\"method\":\"setWidget\",\"widgetKey\":\"pi-spotify-widget\",\"widgetLines\":[\"\\u001b[39m\\u001b[2m♫ Run /spotify:login\\u001b[22;39m\"]}\n{\"type\":\"extension_ui_request\",\"id\":\"392c48c4-caa4-426e-bc97-0380ea967f04\",\"method\":\"notify\",\"message\":\"### Spotify playback controls\",\"notifyType\":\"info\"}\n{\"type\":\"message_start\",\"message\":{\"role\":\"custom\",\"customType\":\"spotify-widget\",\"content\":\"### Spotify playback controls\\n\\nユーザーが曲操作や再生/停止を依頼したら、下表から意図に最も近い **Pi コマンド** を1つ選んで実行してください。\\n推測が曖昧なときは `/spotify:help` の一覧を参照するか、ユーザーに確認してください。\\n\\n| Command | Action | Example user phrases |\\n|---|---|---|\\n| /spotify:prev | 前の曲に戻る | 前の曲 · 前へ · 戻して · previous track |\\n| /spotify:next | 次の曲に進む | 次の曲 · 次へ · スキップ · skip |\\n| /spotify:pause | 再生を一時停止する(停止) | 止めて · 停止 · ストップ · 一時停止 |\\n| /spotify:play | 再生を再開する | 再生 · 再開 · play · resume |\\n\\nDiscovery / share:\\n| /spotify:share | Now Playing を X に投稿(API 設定時)または共有画面を開く | シェア · Xに投稿 · post to X · tweet |\\n| /spotify:play-uri | Spotify 曲 URL / URI を指定して再生 | この曲再生 · play this link · play uri |\\n\\nOther useful commands:\\n- `/spotify:refresh` — Now Playing を即時更新\\n- `/spotify:status` — 認証状態(token 値は出さない)\\n\\nNote: playback control requires `user-modify-playback-state` scope. If controls fail after an upgrade, run `/spotify:login` again.\",\"display\":true,\"details\":{\"kind\":\"help\"},\"timestamp\":1792110352516}}\n{\"type\":\"message_end\",\"message\":{\"role\":\"custom\",\"customType\":\"spotify-widget\",\"content\":\"### Spotify playback controls\\n\\nユーザーが曲操作や再生/停止を依頼したら、下表から意図に最も近い **Pi コマンド** を1つ選んで実行してください。\\n推測が曖昧なときは `/spotify:help` の一覧を参照するか、ユーザーに確認してください。\\n\\n| Command | Action | Example user phrases |\\n|---|---|---|\\n| /spotify:prev | 前の曲に戻る | 前の曲 · 前へ · 戻して · previous track |\\n| /spotify:next | 次の曲に進む | 次の曲 · 次へ · スキップ · skip |\\n| /spotify:pause | 再生を一時停止する(停止) | 止めて · 停止 · ストップ · 一時停止 |\\n| /spotify:play | 再生を再開する | 再生 · 再開 · play · resume |\\n\\nDiscovery / share:\\n| /spotify:share | Now Playing を X に投稿(API 設定時)または共有画面を開く | シェア · Xに投稿 · post to X · tweet |\\n| /spotify:play-uri | Spotify 曲 URL / URI を指定して再生 | この曲再生 · play this link · play uri |\\n\\nOther useful commands:\\n- `/spotify:refresh` — Now Playing を即時更新\\n- `/spotify:status` — 認証状態(token 値は出さない)\\n\\nNote: playback control requires `user-modify-playback-state` scope. If controls fail after an upgrade, run `/spotify:login` again.\",\"display\":true,\"details\":{\"kind\":\"help\"},\"timestamp\":1792110352516}}\n{\"type\":\"response\",\"command\":\"prompt\",\"success\":true,\"data\":{\"disposition\":\"handled\"}}\n",
175
+ "stderr": ""
176
+ },
177
+ {
178
+ "exit_code": 0,
179
+ "signal": null,
180
+ "error": null,
181
+ "stdout": "{\"type\":\"extension_ui_request\",\"id\":\"e28dc25b-3354-490b-8e50-6a69d1d6d80e\",\"method\":\"setWidget\",\"widgetKey\":\"pi-spotify-widget\",\"widgetLines\":[\"\\u001b[39m\\u001b[2m♫ Run /spotify:login\\u001b[22;39m\"]}\n{\"type\":\"extension_ui_request\",\"id\":\"6b9ea355-c58b-469f-a7e5-aded479029fe\",\"method\":\"notify\",\"message\":\"### Spotify playback controls\",\"notifyType\":\"info\"}\n{\"type\":\"message_start\",\"message\":{\"role\":\"custom\",\"customType\":\"spotify-widget\",\"content\":\"### Spotify playback controls\\n\\nユーザーが曲操作や再生/停止を依頼したら、下表から意図に最も近い **Pi コマンド** を1つ選んで実行してください。\\n推測が曖昧なときは `/spotify:help` の一覧を参照するか、ユーザーに確認してください。\\n\\n| Command | Action | Example user phrases |\\n|---|---|---|\\n| /spotify:prev | 前の曲に戻る | 前の曲 · 前へ · 戻して · previous track |\\n| /spotify:next | 次の曲に進む | 次の曲 · 次へ · スキップ · skip |\\n| /spotify:pause | 再生を一時停止する(停止) | 止めて · 停止 · ストップ · 一時停止 |\\n| /spotify:play | 再生を再開する | 再生 · 再開 · play · resume |\\n\\nDiscovery / share:\\n| /spotify:share | Now Playing を X に投稿(API 設定時)または共有画面を開く | シェア · Xに投稿 · post to X · tweet |\\n| /spotify:play-uri | Spotify 曲 URL / URI を指定して再生 | この曲再生 · play this link · play uri |\\n\\nOther useful commands:\\n- `/spotify:refresh` — Now Playing を即時更新\\n- `/spotify:status` — 認証状態(token 値は出さない)\\n\\nNote: playback control requires `user-modify-playback-state` scope. If controls fail after an upgrade, run `/spotify:login` again.\",\"display\":true,\"details\":{\"kind\":\"help\"},\"timestamp\":1792110352789}}\n{\"type\":\"message_end\",\"message\":{\"role\":\"custom\",\"customType\":\"spotify-widget\",\"content\":\"### Spotify playback controls\\n\\nユーザーが曲操作や再生/停止を依頼したら、下表から意図に最も近い **Pi コマンド** を1つ選んで実行してください。\\n推測が曖昧なときは `/spotify:help` の一覧を参照するか、ユーザーに確認してください。\\n\\n| Command | Action | Example user phrases |\\n|---|---|---|\\n| /spotify:prev | 前の曲に戻る | 前の曲 · 前へ · 戻して · previous track |\\n| /spotify:next | 次の曲に進む | 次の曲 · 次へ · スキップ · skip |\\n| /spotify:pause | 再生を一時停止する(停止) | 止めて · 停止 · ストップ · 一時停止 |\\n| /spotify:play | 再生を再開する | 再生 · 再開 · play · resume |\\n\\nDiscovery / share:\\n| /spotify:share | Now Playing を X に投稿(API 設定時)または共有画面を開く | シェア · Xに投稿 · post to X · tweet |\\n| /spotify:play-uri | Spotify 曲 URL / URI を指定して再生 | この曲再生 · play this link · play uri |\\n\\nOther useful commands:\\n- `/spotify:refresh` — Now Playing を即時更新\\n- `/spotify:status` — 認証状態(token 値は出さない)\\n\\nNote: playback control requires `user-modify-playback-state` scope. If controls fail after an upgrade, run `/spotify:login` again.\",\"display\":true,\"details\":{\"kind\":\"help\"},\"timestamp\":1792110352789}}\n{\"type\":\"response\",\"command\":\"prompt\",\"success\":true,\"data\":{\"disposition\":\"handled\"}}\n",
182
+ "stderr": ""
183
+ }
184
+ ]
185
+ },
186
+ "assertions": [
187
+ {
188
+ "id": "reloaded_extension_registered",
189
+ "expected": "A second isolated Pi process reloads the extension, runs session_start, and handles /spotify:help.",
190
+ "observed": {
191
+ "widget": {
192
+ "type": "extension_ui_request",
193
+ "id": "e28dc25b-3354-490b-8e50-6a69d1d6d80e",
194
+ "method": "setWidget",
195
+ "widgetKey": "pi-spotify-widget",
196
+ "widgetLines": [
197
+ "\u001b[39m\u001b[2m♫ Run /spotify:login\u001b[22;39m"
198
+ ]
199
+ },
200
+ "response": {
201
+ "type": "response",
202
+ "command": "prompt",
203
+ "success": true,
204
+ "data": {
205
+ "disposition": "handled"
206
+ }
207
+ }
208
+ },
209
+ "passed": true
210
+ },
211
+ {
212
+ "id": "reloaded_feature_response",
213
+ "expected": "The second process returns the actual widget help message containing playback commands.",
214
+ "observed": {
215
+ "response": {
216
+ "type": "response",
217
+ "command": "prompt",
218
+ "success": true,
219
+ "data": {
220
+ "disposition": "handled"
221
+ }
222
+ },
223
+ "featureMessage": {
224
+ "type": "message_end",
225
+ "message": {
226
+ "role": "custom",
227
+ "customType": "spotify-widget",
228
+ "content": "### Spotify playback controls\n\nユーザーが曲操作や再生/停止を依頼したら、下表から意図に最も近い **Pi コマンド** を1つ選んで実行してください。\n推測が曖昧なときは `/spotify:help` の一覧を参照するか、ユーザーに確認してください。\n\n| Command | Action | Example user phrases |\n|---|---|---|\n| /spotify:prev | 前の曲に戻る | 前の曲 · 前へ · 戻して · previous track |\n| /spotify:next | 次の曲に進む | 次の曲 · 次へ · スキップ · skip |\n| /spotify:pause | 再生を一時停止する(停止) | 止めて · 停止 · ストップ · 一時停止 |\n| /spotify:play | 再生を再開する | 再生 · 再開 · play · resume |\n\nDiscovery / share:\n| /spotify:share | Now Playing を X に投稿(API 設定時)または共有画面を開く | シェア · Xに投稿 · post to X · tweet |\n| /spotify:play-uri | Spotify 曲 URL / URI を指定して再生 | この曲再生 · play this link · play uri |\n\nOther useful commands:\n- `/spotify:refresh` — Now Playing を即時更新\n- `/spotify:status` — 認証状態(token 値は出さない)\n\nNote: playback control requires `user-modify-playback-state` scope. If controls fail after an upgrade, run `/spotify:login` again.",
229
+ "display": true,
230
+ "details": {
231
+ "kind": "help"
232
+ },
233
+ "timestamp": 1792110352789
234
+ }
235
+ }
236
+ },
237
+ "passed": true
238
+ },
239
+ {
240
+ "id": "clean_shutdown",
241
+ "expected": "Both real Pi processes exit zero and produce no unhandled, extension-load, or shutdown errors.",
242
+ "observed": {
243
+ "exits": [
244
+ 0,
245
+ 0
246
+ ],
247
+ "firstFeatureResponse": true
248
+ },
249
+ "passed": true
250
+ }
251
+ ]
252
+ },
253
+ {
254
+ "name": "host_shell",
255
+ "command": "npm run smoke:pi -- --case host_shell",
256
+ "exit_code": 0,
257
+ "passed": true,
258
+ "evidence": {
259
+ "note": "Captured actual Pi host/native-shell stdout and stderr. The Pi SDK, loader, extension lifecycle, command handler, RPC protocol, and widget implementation are not mocked; no model inference or Spotify OAuth/API request is used.",
260
+ "runs": [
261
+ {
262
+ "exit_code": 0,
263
+ "signal": null,
264
+ "error": null,
265
+ "stdout": "1.2.0\n",
266
+ "stderr": ""
267
+ }
268
+ ]
269
+ },
270
+ "assertions": [
271
+ {
272
+ "id": "native_shell_boundary",
273
+ "expected": "The native host shell starts the local Pi CLI and prints exactly the frozen SDK version.",
274
+ "observed": {
275
+ "exit_code": 0,
276
+ "output": "1.2.0",
277
+ "expectedVersion": "1.2.0"
278
+ },
279
+ "passed": true
280
+ }
281
+ ]
282
+ }
283
+ ]
284
+ }
@@ -40,7 +40,12 @@ export function trackIdentity(snapshot: PlaybackSnapshot | null): string {
40
40
 
41
41
  /** True when the 1s display tick should re-render (extrapolated progress changes). */
42
42
  export function needsDisplayProgressUpdate(snapshot: PlaybackSnapshot | null): boolean {
43
- return Boolean(snapshot?.track && snapshot.isPlaying);
43
+ return Boolean(
44
+ snapshot?.track
45
+ && snapshot.isPlaying
46
+ && typeof snapshot.progressMs === "number"
47
+ && Number.isFinite(snapshot.progressMs),
48
+ );
44
49
  }
45
50
 
46
51
  export const __testing = {
package/package.json CHANGED
@@ -1,64 +1,66 @@
1
- {
2
- "name": "pi-spotify-widget",
3
- "version": "0.1.15",
4
- "description": "Lightweight Spotify Now Playing widget for Pi Coding Agent.",
5
- "type": "module",
6
- "license": "MIT",
7
- "author": "eiei114",
8
- "keywords": [
9
- "pi-package",
10
- "pi",
11
- "agent-skill",
12
- "typescript"
13
- ],
14
- "repository": {
15
- "type": "git",
16
- "url": "git+https://github.com/eiei114/pi-spotify-widget.git"
17
- },
18
- "bugs": {
19
- "url": "https://github.com/eiei114/pi-spotify-widget/issues"
20
- },
21
- "homepage": "https://github.com/eiei114/pi-spotify-widget#readme",
22
- "files": [
23
- "extensions/",
24
- "lib/",
25
- "docs/",
26
- "README.md",
27
- "SECURITY.md",
28
- "LICENSE",
29
- "CHANGELOG.md",
30
- "skills/"
31
- ],
32
- "scripts": {
33
- "typecheck": "tsc --noEmit",
34
- "test": "node --test tests/*.test.mjs",
35
- "ci": "npm run typecheck && npm test && npm run pack:check",
36
- "pack:check": "npm pack --dry-run",
37
- "version:check": "node scripts/check-version-bump.mjs"
38
- },
39
- "pi": {
40
- "extensions": [
41
- "./extensions"
42
- ],
43
- "skills": [
44
- "./skills/spotify-playback"
45
- ]
46
- },
47
- "publishConfig": {
48
- "access": "public"
49
- },
50
- "peerDependencies": {
51
- "@earendil-works/pi-ai": "*",
52
- "@earendil-works/pi-coding-agent": "*",
53
- "@earendil-works/pi-tui": "*",
54
- "typebox": "*"
55
- },
56
- "devDependencies": {
57
- "@earendil-works/pi-ai": "^0.84.1",
58
- "@earendil-works/pi-coding-agent": "^0.84.1",
59
- "@earendil-works/pi-tui": "latest",
60
- "typebox": "latest",
61
- "@types/node": "^26.0.0",
62
- "typescript": "^7.0.2"
63
- }
64
- }
1
+ {
2
+ "name": "pi-spotify-widget",
3
+ "version": "0.1.19",
4
+ "description": "Lightweight Spotify Now Playing widget for Pi Coding Agent.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "eiei114",
8
+ "keywords": [
9
+ "pi-package",
10
+ "pi",
11
+ "agent-skill",
12
+ "typescript"
13
+ ],
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/eiei114/pi-spotify-widget.git"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/eiei114/pi-spotify-widget/issues"
20
+ },
21
+ "homepage": "https://github.com/eiei114/pi-spotify-widget#readme",
22
+ "files": [
23
+ "extensions/",
24
+ "lib/",
25
+ "docs/",
26
+ "README.md",
27
+ "SECURITY.md",
28
+ "LICENSE",
29
+ "CHANGELOG.md",
30
+ "skills/"
31
+ ],
32
+ "scripts": {
33
+ "typecheck": "tsc --noEmit",
34
+ "test": "node --test tests/*.test.mjs",
35
+ "ci": "npm run typecheck && npm test && npm run smoke:pi && npm run pack:check",
36
+ "pack:check": "npm pack --dry-run",
37
+ "smoke:pi": "node scripts/pi-sdk-smoke.mjs",
38
+ "version:check": "node scripts/check-version-bump.mjs"
39
+ },
40
+ "pi": {
41
+ "extensions": [
42
+ "./extensions"
43
+ ],
44
+ "skills": [
45
+ "./skills/spotify-playback"
46
+ ]
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "peerDependencies": {
52
+ "@earendil-works/pi-ai": "*",
53
+ "@earendil-works/pi-coding-agent": "*",
54
+ "@earendil-works/pi-tui": "*",
55
+ "typebox": "*"
56
+ },
57
+ "devDependencies": {
58
+ "@earendil-works/pi-agent-core": "1.2.0",
59
+ "@earendil-works/pi-ai": "1.2.0",
60
+ "@earendil-works/pi-coding-agent": "1.2.0",
61
+ "@earendil-works/pi-tui": "1.2.0",
62
+ "typebox": "latest",
63
+ "@types/node": "^26.0.0",
64
+ "typescript": "^7.0.2"
65
+ }
66
+ }