@codyswann/lisa 2.188.4 → 2.189.1

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.
Files changed (72) hide show
  1. package/package.json +3 -2
  2. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  3. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  4. package/plugins/lisa/rules/reference/integration-access-layer.md +2 -0
  5. package/plugins/lisa-agy/plugin.json +1 -1
  6. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  7. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  8. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  9. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  11. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  12. package/plugins/lisa-copilot/rules/reference/integration-access-layer.md +2 -0
  13. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa-cursor/rules/integration-access-layer-reference.mdc +2 -0
  15. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  17. package/plugins/lisa-expo/skills/expo-deployment/SKILL.md +3 -0
  18. package/plugins/lisa-expo/skills/expo-deployment/references/play-store.md +18 -0
  19. package/plugins/lisa-expo/skills/play-store-access/SKILL.md +226 -0
  20. package/plugins/lisa-expo/skills/play-store-access/agents/openai.yaml +4 -0
  21. package/plugins/lisa-expo-agy/plugin.json +1 -1
  22. package/plugins/lisa-expo-agy/skills/expo-deployment/SKILL.md +3 -0
  23. package/plugins/lisa-expo-agy/skills/expo-deployment/references/play-store.md +18 -0
  24. package/plugins/lisa-expo-agy/skills/play-store-access/SKILL.md +226 -0
  25. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-expo-copilot/skills/expo-deployment/SKILL.md +3 -0
  27. package/plugins/lisa-expo-copilot/skills/expo-deployment/references/play-store.md +18 -0
  28. package/plugins/lisa-expo-copilot/skills/play-store-access/SKILL.md +226 -0
  29. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-expo-cursor/skills/expo-deployment/SKILL.md +3 -0
  31. package/plugins/lisa-expo-cursor/skills/expo-deployment/references/play-store.md +18 -0
  32. package/plugins/lisa-expo-cursor/skills/play-store-access/SKILL.md +226 -0
  33. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  36. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  40. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  41. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  46. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  50. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  51. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  55. package/plugins/lisa-rails-agy/plugin.json +1 -1
  56. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  60. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  61. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  65. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  66. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  68. package/plugins/src/base/rules/reference/integration-access-layer.md +2 -0
  69. package/plugins/src/expo/skills/expo-deployment/SKILL.md +3 -0
  70. package/plugins/src/expo/skills/expo-deployment/references/play-store.md +18 -0
  71. package/plugins/src/expo/skills/play-store-access/SKILL.md +226 -0
  72. package/scripts/detect-stale-workflow-inputs.mjs +455 -0
@@ -0,0 +1,226 @@
1
+ ---
2
+ name: play-store-access
3
+ description: "Read-only Google Play Console access layer for Expo projects. Use when an operator or agent needs to set up Play Developer API credentials, resolve the Android package name, or check the production track release state without using the Play Console UI. Reads the androidpublisher v3 API only; track promotion, rollout changes, and submissions are out of scope."
4
+ version: 1.0.0
5
+ license: MIT
6
+ allowed-tools:
7
+ - Bash
8
+ - Read
9
+ ---
10
+
11
+ # Play Store Access: $ARGUMENTS
12
+
13
+ Read-only access layer for Google Play release state in Expo projects. Use it to
14
+ answer whether the Android app is live, halted, draft, or in staged rollout on a
15
+ Play track after EAS submission.
16
+
17
+ This skill MUST NOT promote tracks, change rollout percentages, create releases,
18
+ or submit builds. EAS remains the submission path.
19
+
20
+ ## Invocation Contract
21
+
22
+ ```text
23
+ operation: setup
24
+ operation: production-status [track:production] [package:<android.package>]
25
+ operation: track-status track:<track> [package:<android.package>]
26
+ operation: resolve-package
27
+ ```
28
+
29
+ Return a concise result with the package name, track, release `name` /
30
+ `versionCodes`, `status`, `userFraction` when present, and any `countryTargeting`
31
+ or in-app update priority fields present in the API response.
32
+
33
+ ## Configuration
34
+
35
+ Project-safe config can live in `.lisa.config.json`:
36
+
37
+ ```json
38
+ {
39
+ "playStore": {
40
+ "packageName": "com.example.app",
41
+ "track": "production"
42
+ }
43
+ }
44
+ ```
45
+
46
+ Local credential config belongs in `.lisa.config.local.json`, which must stay
47
+ gitignored:
48
+
49
+ ```json
50
+ {
51
+ "playStore": {
52
+ "serviceAccountKeyPath": "./google-play-service-account.json"
53
+ }
54
+ }
55
+ ```
56
+
57
+ Supported credential locations, in resolution order:
58
+
59
+ 1. `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` containing the raw service-account JSON.
60
+ 2. `GOOGLE_PLAY_SERVICE_ACCOUNT_KEY_BASE64` containing base64 JSON.
61
+ 3. `.lisa.config.local.json` `playStore.serviceAccountKeyPath`.
62
+ 4. Darwin keychain item `lisa-google-play` account `<packageName>`.
63
+
64
+ Fail closed if no credential is available. Never prompt indefinitely and never
65
+ fall back to Play Console UI scraping.
66
+
67
+ ## One-Time Setup Flow
68
+
69
+ For `operation: setup`:
70
+
71
+ 1. Confirm the project is an Expo app by checking for `app.json`,
72
+ `app.config.json`, `app.config.js`, or `app.config.ts`.
73
+ 2. Resolve or ask the operator to provide the Android package name. Prefer
74
+ `.lisa.config.json` `playStore.packageName`, then `expo.android.package` from
75
+ app config.
76
+ 3. In Google Cloud Console, create a service account dedicated to read-only Play
77
+ release visibility and create a JSON key for it.
78
+ 4. In Play Console, open **Setup -> API access**, link the Google Cloud project if
79
+ needed, then grant the service-account email app access with read-level
80
+ release visibility. Do not grant release-management write permissions for v1.
81
+ 5. Store the key locally using one of the credential locations above. If using a
82
+ path, add the path to `.gitignore` and record only the path in
83
+ `.lisa.config.local.json`.
84
+ 6. Record non-secret defaults in `.lisa.config.json`:
85
+
86
+ ```bash
87
+ jq '.playStore.packageName = "com.example.app" | .playStore.track = "production"' \
88
+ .lisa.config.json > /tmp/lisa-config.json && mv /tmp/lisa-config.json .lisa.config.json
89
+ ```
90
+
91
+ The key material itself must never be committed.
92
+
93
+ ## Resolve Package Name
94
+
95
+ Resolution order:
96
+
97
+ 1. Explicit `package:<value>` argument.
98
+ 2. `.lisa.config.json` `playStore.packageName`.
99
+ 3. `app.json` / `app.config.json` `expo.android.package`.
100
+ 4. `app.config.ts` / `app.config.js` by running Expo config export:
101
+
102
+ ```bash
103
+ npx expo config --json | jq -r '.android.package // empty'
104
+ ```
105
+
106
+ If the package still cannot be resolved, fail with:
107
+
108
+ ```text
109
+ Error: Google Play package name is not configured. Set playStore.packageName in .lisa.config.json or pass package:<name>.
110
+ ```
111
+
112
+ ## Read Track State
113
+
114
+ The androidpublisher API requires an edit even for track reads. Create the edit,
115
+ list the track, then delete the edit. Do not commit the edit.
116
+
117
+ ```bash
118
+ PACKAGE_NAME="${PACKAGE_NAME:-$(jq -r '.playStore.packageName // empty' .lisa.config.json)}"
119
+ TRACK="${TRACK:-$(jq -r '.playStore.track // "production"' .lisa.config.json)}"
120
+ KEY_PATH="$(jq -r '.playStore.serviceAccountKeyPath // empty' .lisa.config.local.json 2>/dev/null)"
121
+
122
+ read_service_account_json() {
123
+ if [ -n "${GOOGLE_PLAY_SERVICE_ACCOUNT_JSON:-}" ]; then
124
+ printf '%s' "$GOOGLE_PLAY_SERVICE_ACCOUNT_JSON"
125
+ return
126
+ fi
127
+ if [ -n "${GOOGLE_PLAY_SERVICE_ACCOUNT_KEY_BASE64:-}" ]; then
128
+ printf '%s' "$GOOGLE_PLAY_SERVICE_ACCOUNT_KEY_BASE64" | base64 --decode 2>/dev/null ||
129
+ printf '%s' "$GOOGLE_PLAY_SERVICE_ACCOUNT_KEY_BASE64" | base64 -D
130
+ return
131
+ fi
132
+ if [ -n "$KEY_PATH" ] && [ -f "$KEY_PATH" ]; then
133
+ cat "$KEY_PATH"
134
+ return
135
+ fi
136
+ if [ "$(uname -s)" = "Darwin" ] && [ -n "$PACKAGE_NAME" ]; then
137
+ security find-generic-password -s lisa-google-play -a "$PACKAGE_NAME" -w 2>/dev/null
138
+ return
139
+ fi
140
+ }
141
+
142
+ SERVICE_ACCOUNT_JSON="$(read_service_account_json)"
143
+ [ -n "$SERVICE_ACCOUNT_JSON" ] || {
144
+ echo "Error: no Google Play service-account key found. Run play-store-access operation: setup." >&2
145
+ exit 1
146
+ }
147
+ [ -n "$PACKAGE_NAME" ] || {
148
+ echo "Error: Google Play package name is not configured. Set playStore.packageName or pass package:<name>." >&2
149
+ exit 1
150
+ }
151
+
152
+ ACCESS_TOKEN="$(
153
+ SERVICE_ACCOUNT_JSON="$SERVICE_ACCOUNT_JSON" node <<'NODE'
154
+ const crypto = require("crypto");
155
+
156
+ const account = JSON.parse(process.env.SERVICE_ACCOUNT_JSON);
157
+ const now = Math.floor(Date.now() / 1000);
158
+ const encode = (value) =>
159
+ Buffer.from(JSON.stringify(value)).toString("base64url");
160
+ const unsigned = `${encode({ alg: "RS256", typ: "JWT" })}.${encode({
161
+ iss: account.client_email,
162
+ scope: "https://www.googleapis.com/auth/androidpublisher",
163
+ aud: "https://oauth2.googleapis.com/token",
164
+ iat: now,
165
+ exp: now + 3600,
166
+ })}`;
167
+ const signature = crypto
168
+ .createSign("RSA-SHA256")
169
+ .update(unsigned)
170
+ .sign(account.private_key, "base64url");
171
+
172
+ fetch("https://oauth2.googleapis.com/token", {
173
+ method: "POST",
174
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
175
+ body: new URLSearchParams({
176
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
177
+ assertion: `${unsigned}.${signature}`,
178
+ }),
179
+ })
180
+ .then(async (response) => {
181
+ const body = await response.json();
182
+ if (!response.ok) {
183
+ throw new Error(JSON.stringify(body));
184
+ }
185
+ process.stdout.write(body.access_token);
186
+ })
187
+ .catch((error) => {
188
+ console.error(`Error: failed to obtain Google Play access token: ${error.message}`);
189
+ process.exit(1);
190
+ });
191
+ NODE
192
+ )"
193
+
194
+ BASE="https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${PACKAGE_NAME}"
195
+ EDIT_ID="$(curl -fsS -X POST "${BASE}/edits" -H "Authorization: Bearer ${ACCESS_TOKEN}" | jq -r '.id')"
196
+ trap 'curl -fsS -X DELETE "${BASE}/edits/${EDIT_ID}" -H "Authorization: Bearer ${ACCESS_TOKEN}" >/dev/null 2>&1 || true' EXIT
197
+
198
+ curl -fsS "${BASE}/edits/${EDIT_ID}/tracks/${TRACK}" \
199
+ -H "Authorization: Bearer ${ACCESS_TOKEN}" |
200
+ jq --arg packageName "$PACKAGE_NAME" \
201
+ '{packageName: $packageName, track: .track, releases: (.releases // [])}'
202
+ ```
203
+
204
+ Interpretation:
205
+
206
+ - `status: completed` means the release is live on that track.
207
+ - `status: inProgress` means staged rollout; inspect `userFraction`.
208
+ - `status: halted` means rollout was stopped.
209
+ - `status: draft` means it is not live.
210
+ - Absence of a production release means the app has no watchable production-track
211
+ release through the API.
212
+
213
+ Known limitation: androidpublisher exposes track/release state well, but
214
+ pre-publication review state is coarse. The reliable watchable signal is whether
215
+ the target version is live on the production track.
216
+
217
+ ## Error Handling
218
+
219
+ - `401` / token exchange failure: service-account key is invalid or expired.
220
+ - `403`: service account is not invited to the Play Console app or lacks read
221
+ access to releases.
222
+ - `404`: package name is wrong, the app is not created in Play Console, or the
223
+ service account has no access to that app.
224
+
225
+ In all cases, report the package name and credential source used, but never print
226
+ private key material or the access token.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.188.4",
3
+ "version": "2.189.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -19,6 +19,9 @@ Consult these resources as needed:
19
19
  - ./references/play-store.md -- Submitting Android builds to Google Play Store
20
20
  - ./references/ios-app-store.md -- iOS App Store submission and review process
21
21
 
22
+ For post-submit Android release visibility, use the `play-store-access` skill to
23
+ query Google Play production-track state without opening Play Console.
24
+
22
25
  ## Quick Start
23
26
 
24
27
  ### Install EAS CLI
@@ -237,6 +237,24 @@ eas submit:list -p android
237
237
  eas submit:view SUBMISSION_ID
238
238
  ```
239
239
 
240
+ ## Monitoring Production Track State
241
+
242
+ EAS can submit Android builds, but Play Console owns review, production release,
243
+ and staged-rollout state. For non-interactive visibility into whether a version
244
+ is live or rolling out on Google Play, use the `play-store-access` skill.
245
+
246
+ The skill configures a read-only Google Play Developer API service account and
247
+ queries `androidpublisher` v3:
248
+
249
+ 1. Resolve the package name from `.lisa.config.json` or Expo app config.
250
+ 2. Create a short-lived read-only edit.
251
+ 3. Read the target track, usually `production`.
252
+ 4. Delete the edit without making changes.
253
+
254
+ It reports release `versionCodes`, `status`, and `userFraction` when a staged
255
+ rollout is active. It does not promote tracks, adjust rollout percentages, or
256
+ submit builds.
257
+
240
258
  ## Tips
241
259
 
242
260
  - Start with `internal` track for testing before production
@@ -0,0 +1,226 @@
1
+ ---
2
+ name: play-store-access
3
+ description: "Read-only Google Play Console access layer for Expo projects. Use when an operator or agent needs to set up Play Developer API credentials, resolve the Android package name, or check the production track release state without using the Play Console UI. Reads the androidpublisher v3 API only; track promotion, rollout changes, and submissions are out of scope."
4
+ version: 1.0.0
5
+ license: MIT
6
+ allowed-tools:
7
+ - Bash
8
+ - Read
9
+ ---
10
+
11
+ # Play Store Access: $ARGUMENTS
12
+
13
+ Read-only access layer for Google Play release state in Expo projects. Use it to
14
+ answer whether the Android app is live, halted, draft, or in staged rollout on a
15
+ Play track after EAS submission.
16
+
17
+ This skill MUST NOT promote tracks, change rollout percentages, create releases,
18
+ or submit builds. EAS remains the submission path.
19
+
20
+ ## Invocation Contract
21
+
22
+ ```text
23
+ operation: setup
24
+ operation: production-status [track:production] [package:<android.package>]
25
+ operation: track-status track:<track> [package:<android.package>]
26
+ operation: resolve-package
27
+ ```
28
+
29
+ Return a concise result with the package name, track, release `name` /
30
+ `versionCodes`, `status`, `userFraction` when present, and any `countryTargeting`
31
+ or in-app update priority fields present in the API response.
32
+
33
+ ## Configuration
34
+
35
+ Project-safe config can live in `.lisa.config.json`:
36
+
37
+ ```json
38
+ {
39
+ "playStore": {
40
+ "packageName": "com.example.app",
41
+ "track": "production"
42
+ }
43
+ }
44
+ ```
45
+
46
+ Local credential config belongs in `.lisa.config.local.json`, which must stay
47
+ gitignored:
48
+
49
+ ```json
50
+ {
51
+ "playStore": {
52
+ "serviceAccountKeyPath": "./google-play-service-account.json"
53
+ }
54
+ }
55
+ ```
56
+
57
+ Supported credential locations, in resolution order:
58
+
59
+ 1. `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` containing the raw service-account JSON.
60
+ 2. `GOOGLE_PLAY_SERVICE_ACCOUNT_KEY_BASE64` containing base64 JSON.
61
+ 3. `.lisa.config.local.json` `playStore.serviceAccountKeyPath`.
62
+ 4. Darwin keychain item `lisa-google-play` account `<packageName>`.
63
+
64
+ Fail closed if no credential is available. Never prompt indefinitely and never
65
+ fall back to Play Console UI scraping.
66
+
67
+ ## One-Time Setup Flow
68
+
69
+ For `operation: setup`:
70
+
71
+ 1. Confirm the project is an Expo app by checking for `app.json`,
72
+ `app.config.json`, `app.config.js`, or `app.config.ts`.
73
+ 2. Resolve or ask the operator to provide the Android package name. Prefer
74
+ `.lisa.config.json` `playStore.packageName`, then `expo.android.package` from
75
+ app config.
76
+ 3. In Google Cloud Console, create a service account dedicated to read-only Play
77
+ release visibility and create a JSON key for it.
78
+ 4. In Play Console, open **Setup -> API access**, link the Google Cloud project if
79
+ needed, then grant the service-account email app access with read-level
80
+ release visibility. Do not grant release-management write permissions for v1.
81
+ 5. Store the key locally using one of the credential locations above. If using a
82
+ path, add the path to `.gitignore` and record only the path in
83
+ `.lisa.config.local.json`.
84
+ 6. Record non-secret defaults in `.lisa.config.json`:
85
+
86
+ ```bash
87
+ jq '.playStore.packageName = "com.example.app" | .playStore.track = "production"' \
88
+ .lisa.config.json > /tmp/lisa-config.json && mv /tmp/lisa-config.json .lisa.config.json
89
+ ```
90
+
91
+ The key material itself must never be committed.
92
+
93
+ ## Resolve Package Name
94
+
95
+ Resolution order:
96
+
97
+ 1. Explicit `package:<value>` argument.
98
+ 2. `.lisa.config.json` `playStore.packageName`.
99
+ 3. `app.json` / `app.config.json` `expo.android.package`.
100
+ 4. `app.config.ts` / `app.config.js` by running Expo config export:
101
+
102
+ ```bash
103
+ npx expo config --json | jq -r '.android.package // empty'
104
+ ```
105
+
106
+ If the package still cannot be resolved, fail with:
107
+
108
+ ```text
109
+ Error: Google Play package name is not configured. Set playStore.packageName in .lisa.config.json or pass package:<name>.
110
+ ```
111
+
112
+ ## Read Track State
113
+
114
+ The androidpublisher API requires an edit even for track reads. Create the edit,
115
+ list the track, then delete the edit. Do not commit the edit.
116
+
117
+ ```bash
118
+ PACKAGE_NAME="${PACKAGE_NAME:-$(jq -r '.playStore.packageName // empty' .lisa.config.json)}"
119
+ TRACK="${TRACK:-$(jq -r '.playStore.track // "production"' .lisa.config.json)}"
120
+ KEY_PATH="$(jq -r '.playStore.serviceAccountKeyPath // empty' .lisa.config.local.json 2>/dev/null)"
121
+
122
+ read_service_account_json() {
123
+ if [ -n "${GOOGLE_PLAY_SERVICE_ACCOUNT_JSON:-}" ]; then
124
+ printf '%s' "$GOOGLE_PLAY_SERVICE_ACCOUNT_JSON"
125
+ return
126
+ fi
127
+ if [ -n "${GOOGLE_PLAY_SERVICE_ACCOUNT_KEY_BASE64:-}" ]; then
128
+ printf '%s' "$GOOGLE_PLAY_SERVICE_ACCOUNT_KEY_BASE64" | base64 --decode 2>/dev/null ||
129
+ printf '%s' "$GOOGLE_PLAY_SERVICE_ACCOUNT_KEY_BASE64" | base64 -D
130
+ return
131
+ fi
132
+ if [ -n "$KEY_PATH" ] && [ -f "$KEY_PATH" ]; then
133
+ cat "$KEY_PATH"
134
+ return
135
+ fi
136
+ if [ "$(uname -s)" = "Darwin" ] && [ -n "$PACKAGE_NAME" ]; then
137
+ security find-generic-password -s lisa-google-play -a "$PACKAGE_NAME" -w 2>/dev/null
138
+ return
139
+ fi
140
+ }
141
+
142
+ SERVICE_ACCOUNT_JSON="$(read_service_account_json)"
143
+ [ -n "$SERVICE_ACCOUNT_JSON" ] || {
144
+ echo "Error: no Google Play service-account key found. Run play-store-access operation: setup." >&2
145
+ exit 1
146
+ }
147
+ [ -n "$PACKAGE_NAME" ] || {
148
+ echo "Error: Google Play package name is not configured. Set playStore.packageName or pass package:<name>." >&2
149
+ exit 1
150
+ }
151
+
152
+ ACCESS_TOKEN="$(
153
+ SERVICE_ACCOUNT_JSON="$SERVICE_ACCOUNT_JSON" node <<'NODE'
154
+ const crypto = require("crypto");
155
+
156
+ const account = JSON.parse(process.env.SERVICE_ACCOUNT_JSON);
157
+ const now = Math.floor(Date.now() / 1000);
158
+ const encode = (value) =>
159
+ Buffer.from(JSON.stringify(value)).toString("base64url");
160
+ const unsigned = `${encode({ alg: "RS256", typ: "JWT" })}.${encode({
161
+ iss: account.client_email,
162
+ scope: "https://www.googleapis.com/auth/androidpublisher",
163
+ aud: "https://oauth2.googleapis.com/token",
164
+ iat: now,
165
+ exp: now + 3600,
166
+ })}`;
167
+ const signature = crypto
168
+ .createSign("RSA-SHA256")
169
+ .update(unsigned)
170
+ .sign(account.private_key, "base64url");
171
+
172
+ fetch("https://oauth2.googleapis.com/token", {
173
+ method: "POST",
174
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
175
+ body: new URLSearchParams({
176
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
177
+ assertion: `${unsigned}.${signature}`,
178
+ }),
179
+ })
180
+ .then(async (response) => {
181
+ const body = await response.json();
182
+ if (!response.ok) {
183
+ throw new Error(JSON.stringify(body));
184
+ }
185
+ process.stdout.write(body.access_token);
186
+ })
187
+ .catch((error) => {
188
+ console.error(`Error: failed to obtain Google Play access token: ${error.message}`);
189
+ process.exit(1);
190
+ });
191
+ NODE
192
+ )"
193
+
194
+ BASE="https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${PACKAGE_NAME}"
195
+ EDIT_ID="$(curl -fsS -X POST "${BASE}/edits" -H "Authorization: Bearer ${ACCESS_TOKEN}" | jq -r '.id')"
196
+ trap 'curl -fsS -X DELETE "${BASE}/edits/${EDIT_ID}" -H "Authorization: Bearer ${ACCESS_TOKEN}" >/dev/null 2>&1 || true' EXIT
197
+
198
+ curl -fsS "${BASE}/edits/${EDIT_ID}/tracks/${TRACK}" \
199
+ -H "Authorization: Bearer ${ACCESS_TOKEN}" |
200
+ jq --arg packageName "$PACKAGE_NAME" \
201
+ '{packageName: $packageName, track: .track, releases: (.releases // [])}'
202
+ ```
203
+
204
+ Interpretation:
205
+
206
+ - `status: completed` means the release is live on that track.
207
+ - `status: inProgress` means staged rollout; inspect `userFraction`.
208
+ - `status: halted` means rollout was stopped.
209
+ - `status: draft` means it is not live.
210
+ - Absence of a production release means the app has no watchable production-track
211
+ release through the API.
212
+
213
+ Known limitation: androidpublisher exposes track/release state well, but
214
+ pre-publication review state is coarse. The reliable watchable signal is whether
215
+ the target version is live on the production track.
216
+
217
+ ## Error Handling
218
+
219
+ - `401` / token exchange failure: service-account key is invalid or expired.
220
+ - `403`: service account is not invited to the Play Console app or lacks read
221
+ access to releases.
222
+ - `404`: package name is wrong, the app is not created in Play Console, or the
223
+ service account has no access to that app.
224
+
225
+ In all cases, report the package name and credential source used, but never print
226
+ private key material or the access token.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.188.4",
3
+ "version": "2.189.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -19,6 +19,9 @@ Consult these resources as needed:
19
19
  - ./references/play-store.md -- Submitting Android builds to Google Play Store
20
20
  - ./references/ios-app-store.md -- iOS App Store submission and review process
21
21
 
22
+ For post-submit Android release visibility, use the `play-store-access` skill to
23
+ query Google Play production-track state without opening Play Console.
24
+
22
25
  ## Quick Start
23
26
 
24
27
  ### Install EAS CLI
@@ -237,6 +237,24 @@ eas submit:list -p android
237
237
  eas submit:view SUBMISSION_ID
238
238
  ```
239
239
 
240
+ ## Monitoring Production Track State
241
+
242
+ EAS can submit Android builds, but Play Console owns review, production release,
243
+ and staged-rollout state. For non-interactive visibility into whether a version
244
+ is live or rolling out on Google Play, use the `play-store-access` skill.
245
+
246
+ The skill configures a read-only Google Play Developer API service account and
247
+ queries `androidpublisher` v3:
248
+
249
+ 1. Resolve the package name from `.lisa.config.json` or Expo app config.
250
+ 2. Create a short-lived read-only edit.
251
+ 3. Read the target track, usually `production`.
252
+ 4. Delete the edit without making changes.
253
+
254
+ It reports release `versionCodes`, `status`, and `userFraction` when a staged
255
+ rollout is active. It does not promote tracks, adjust rollout percentages, or
256
+ submit builds.
257
+
240
258
  ## Tips
241
259
 
242
260
  - Start with `internal` track for testing before production