Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c38c9b2
QA fixes and full test suite across all scripts
BrutalByte May 31, 2026
40d6ab3
Add Claude Code automations: hooks, skills, agent, MCP
BrutalByte Jun 6, 2026
b8969f3
Handle non-JSON response from Amazon API gracefully
BrutalByte Jun 6, 2026
5460c12
Update Alexa To Reminders Access.js
BrutalByte Jun 6, 2026
ab403aa
Improve error handling in synchronizeReminders
BrutalByte Jun 6, 2026
723994a
Prompt user to pick reminder list when configured name not found
BrutalByte Jun 6, 2026
12f4aa6
Persist chosen reminder list name to settings file
BrutalByte Jun 6, 2026
1c343b0
Add verbose logging throughout script
BrutalByte Jun 6, 2026
23d3cad
Fix false positive auth and unexpected API response type
BrutalByte Jun 6, 2026
fb15552
Full QA pass on Alexa To Reminders script and tests
BrutalByte Jun 6, 2026
df4ad21
Point fallback WebView to API URL to refresh session cookies
BrutalByte Jun 6, 2026
3d138f7
Replace Request with shared WebView for all API calls
BrutalByte Jun 6, 2026
cecf305
Use JS fetch() inside WebView for all Amazon API calls
BrutalByte Jun 6, 2026
b233b59
Fix evaluateJavaScript unsupported type error — switch to XHR
BrutalByte Jun 6, 2026
ced1fde
Fix evaluateJavaScript by returning a Promise instead of using callback
BrutalByte Jun 6, 2026
77d3fa4
Rewrite auth: extract WebView cookies and inject into Request headers
BrutalByte Jun 6, 2026
c889c7e
Rewrite v9: loadURL+getHTML for GET, loadHTML helper for DELETE
BrutalByte Jun 6, 2026
16c4317
Update default reminderListName to SHOPPING
BrutalByte Jun 6, 2026
78166b5
Load Alexa lists page for sign-in to establish Alexa-specific session…
BrutalByte Jun 6, 2026
5d92228
Switch fetchListJSON to XHR helper; load alexa.amazon.com for sign-in
BrutalByte Jun 6, 2026
4e5fd43
Update README: document Alexa To Reminders setup and auth flow
BrutalByte Jun 6, 2026
219b033
Derive Alexa URL from baseURL instead of hard-coding alexa.amazon.com
BrutalByte Jun 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .claude/agents/scriptable-reviewer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
name: scriptable-reviewer
description: Reviews Scriptable widget scripts for size compatibility, API correctness, update-check pattern, and common iOS widget pitfalls
---

You are a Scriptable iOS widget expert. When invoked, review the provided script (or all recently edited scripts) for the following issues and report findings with line numbers and suggested fixes.

## Review Checklist

### Widget Size Compatibility
- Missing `config.widgetFamily` guard when layout differs between small/medium/large/accessory sizes
- Hardcoded pixel dimensions that will break at non-medium sizes
- Text that will overflow at small size

### Scriptable API Correctness
- Use of unavailable APIs: no `fetch()`, `require()`, `fs`, `window`, `document`, or other browser/Node APIs
- `FileManager.iCloud()` vs `FileManager.local()` — iCloud is preferred for synced scripts; flag if using local without clear reason
- `Request` objects must call `.load()` or `.loadJSON()` — flag if response is accessed without awaiting load
- `Script.complete()` must be called at the end of every execution path

### Update Check Pattern
- `updateCheck()` function should be present in scripts intended for public distribution
- Version constant should be a number, not a string

### Settings Pattern
- Settings JSON should be read with null-safety (`fm.fileExists(settingsPath)` check before `readString`)
- Settings directory should be created if it doesn't exist before writing

### Common Pitfalls
- `await` used outside async context
- Uncaught promise rejections (missing try/catch on network calls)
- Hardcoded API keys or tokens in the script body (flag as security issue)
- Missing `Script.setWidget(widget)` call when `config.runsInWidget` is true

Report all findings grouped by severity: **Error** (will crash), **Warning** (may misbehave), **Info** (style/best practice).
26 changes: 26 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "node tests/runner.js 2>&1 | tail -20"
}
]
}
],
"PreToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "echo \"$CLAUDE_TOOL_INPUT\" | grep -q 'scriptable-mocks' && echo 'BLOCK: scriptable-mocks.js is shared by all tests — confirm this change is intentional before proceeding.' || true"
}
]
}
]
}
}
48 changes: 48 additions & 0 deletions .claude/skills/gen-test/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
name: gen-test
description: Generate a Node.js test file for a Scriptable widget script, following the project's existing test conventions in tests/
disable-model-invocation: true
---

Generate a test file for the Scriptable script named by the user (e.g. `/gen-test Twitter Widget.js`).

## Steps

1. Read the target `.js` script from the project root
2. Read `tests/scriptable-mocks.js` to understand available mock factories
3. Identify pure/testable functions in the script (logic that doesn't require a live Scriptable runtime)
4. Create `tests/<kebab-name>.test.js` following the exact pattern used in existing test files:

```javascript
// Tests for <ScriptName>.js
'use strict'

const {
makeRequest, makeWebView, makeAlert, /* etc. — import only what's needed */
} = require('./scriptable-mocks')

// ─── Pure logic extracted from the script ────────────────────────────────────

// Copy or re-express pure functions from the script here so they can be tested
// without requiring the Scriptable runtime.

// ─── Tests ───────────────────────────────────────────────────────────────────

module.exports = async ({ test, describe, assert }) => {
describe('<ScriptName>', () => {
test('example: <function> returns expected value', () => {
// arrange
// act
// assert
})
})
}
```

5. Add the new test file to the `files` array in `tests/runner.js` so it runs automatically.

## Rules
- Only test logic that can run in plain Node.js — no Scriptable runtime APIs in test bodies
- Use injected `assert` from Node's built-in `assert` module (strict mode)
- Follow the existing naming: `<kebab-case-script-name>.test.js`
- Aim for at least 3 meaningful tests covering happy path, edge case, and error case
65 changes: 65 additions & 0 deletions .claude/skills/new-script/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
name: new-script
description: Scaffold a new Scriptable widget script with required headers, FileManager setup, settings JSON pattern, and updateCheck boilerplate
disable-model-invocation: true
---

Create a new Scriptable widget script based on the name and description provided by the user.

The script MUST start with exactly these 3 lines (no blank line before them):
```
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: <color>; icon-glyph: <glyph>;
```

Choose an appropriate `icon-color` from: red, deep-red, orange, yellow, green, teal, blue, deep-blue, indigo, purple, pink, white, gray, light-gray, dark-gray, black.
Choose an appropriate `icon-glyph` from Font Awesome glyph names (e.g. calendar-alt, cloud-sun, dove, chart-bar, bell, star).

After the header, scaffold the following structure:

```javascript
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: blue; icon-glyph: star;

'use strict'

const SCRIPT_VERSION = 1.0

let fm = FileManager.iCloud()
let scriptDir = fm.documentsDirectory() + '/<ScriptName>/'
let settingsPath = scriptDir + 'settings.json'

if (!fm.fileExists(scriptDir)) fm.createDirectory(scriptDir, false)

let needUpdated = await updateCheck(SCRIPT_VERSION)

let settings = {}
if (fm.fileExists(settingsPath)) {
settings = JSON.parse(fm.readString(settingsPath))
}

// ─── Main widget ────────────────────────────────────────────────────────────

let widget = new ListWidget()
widget.backgroundColor = Color.dynamic(Color.white(), Color.black())

// TODO: build widget UI here

if (config.runsInWidget) {
Script.setWidget(widget)
} else {
await widget.presentMedium()
}
Script.complete()

// ─── Helpers ────────────────────────────────────────────────────────────────

async function updateCheck(currentVersion) {
// Placeholder — replace with actual update URL for published scripts
return false
}
```

Save the file as `<ScriptName>.js` in the project root. Ask the user for the script name, purpose, icon color, and icon glyph if not provided.
Loading