commit df53e2f1ed9de7319a0f0e537d63b9396f4de109 Author: Carson (Claude Assistant) Date: Sun Mar 8 22:27:43 2026 +0100 Initial commit: Bring Shopping List Skill - CLI tool for managing Bring! shopping lists - Environment variable-based configuration - Commands: add, list, remove, recent - Complete English documentation - Security: credentials via env vars, not hardcoded Co-Authored-By: Claude Sonnet 4.5 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7e1e457 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +BRING_EMAIL=your-email@example.com +BRING_PASSWORD=your-password +BRING_LIST_UUID=your-list-uuid diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b96e262 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +package-lock.json +*.log +.env diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..8e6b67d --- /dev/null +++ b/SKILL.md @@ -0,0 +1,146 @@ +# Bring Shopping List Skill + +Manage your Bring! Shopping List via CLI using the unofficial Bring! API. + +## Installation + +```bash +npm install +chmod +x bring +``` + +## Configuration + +Set the following environment variables: + +```bash +export BRING_EMAIL="your-email@example.com" +export BRING_PASSWORD="your-password" +export BRING_LIST_UUID="your-list-uuid" +``` + +### Option 1: Using a `.env` file (recommended) + +Create a `.env` file in your skill directory: + +```bash +BRING_EMAIL=your-email@example.com +BRING_PASSWORD=your-password +BRING_LIST_UUID=your-list-uuid +``` + +Then source it before running commands: +```bash +source .env +./bring list +``` + +Or use with AI agents that support .env files. + +### Option 2: System environment variables + +Add to your `~/.bashrc` or `~/.zshrc`: + +```bash +export BRING_EMAIL="your-email@example.com" +export BRING_PASSWORD="your-password" +export BRING_LIST_UUID="your-list-uuid" +``` + +### Finding your List UUID + +```javascript +const bringApi = require('bring-shopping'); +const bring = new bringApi({ + mail: 'your@email.com', + password: 'yourpassword' +}); +await bring.login(); +const lists = await bring.loadLists(); +console.log(lists.lists); // Shows all lists with their UUIDs +``` + +## Commands + +```bash +bring add [specification] # Add item to shopping list +bring list # Show current items +bring remove # Remove item (mark as purchased) +bring recent # Show recently purchased items +``` + +## Examples + +```bash +# Add items +bring add Apples +bring add Milk "1 Liter" +bring add "Coffee Beans" "Fair Trade" + +# Show list +bring list + +# Remove item (mark as bought) +bring remove Apples + +# Show recently purchased items +bring recent +``` + +## Integration with AI Agents + +### For nanoclaw/openclaw/Carson + +Add to your `CLAUDE.md`: + +```markdown +## Bring! Shopping List + +**IMPORTANT - "Shopping list" always means Bring! App:** + +When the user says "shopping list", they ALWAYS mean the Bring! Shopping List App: +- **NEVER** use a custom text file or other system +- **ALWAYS** use the Bring skill: `bring` + +**Usage:** +```bash +bring add [specification] # Add item +bring list # Show list +bring remove # Remove item +bring recent # Recently purchased +``` + +**Examples:** +- "Add milk to shopping list" → `bring add Milk` +- "What's on the shopping list?" → `bring list` +- "Remove apples from list" → `bring remove Apples` +``` + +### Setting environment variables for AI agents + +Create a `.env` file in the skill directory with your credentials, then ensure your AI agent loads it before executing commands. + +For nanoclaw/Carson, you can set the environment variables in the skill activation: + +```bash +export BRING_EMAIL="your-email@example.com" +export BRING_PASSWORD="your-password" +export BRING_LIST_UUID="your-list-uuid" +``` + +## API Reference + +This skill uses the unofficial `bring-shopping` npm package: +https://github.com/foxriver76/node-bring-api + +## Security Notes + +- **Never commit your `.env` file** - it's in `.gitignore` by default +- Environment variables keep credentials out of the code +- Each user/agent instance uses their own credentials + +## Notes + +- Items sync in real-time across all connected devices +- The specification parameter is optional (e.g., "Organic", "500g", etc.) +- Use quotes for items or specifications with spaces diff --git a/bring b/bring new file mode 100755 index 0000000..fbd7db1 --- /dev/null +++ b/bring @@ -0,0 +1,139 @@ +#!/usr/bin/env node + +const bringApi = require('bring-shopping'); + +// Load credentials from environment variables +const CREDENTIALS = { + mail: process.env.BRING_EMAIL, + password: process.env.BRING_PASSWORD +}; + +const LIST_UUID = process.env.BRING_LIST_UUID; + +// Validate required environment variables +if (!CREDENTIALS.mail || !CREDENTIALS.password || !LIST_UUID) { + console.error('Error: Missing required environment variables'); + console.error('Please set: BRING_EMAIL, BRING_PASSWORD, BRING_LIST_UUID'); + console.error('See SKILL.md for setup instructions'); + process.exit(1); +} + +async function main() { + const args = process.argv.slice(2); + const command = args[0]; + + if (!command) { + console.error('Usage: bring [args...]'); + process.exit(1); + } + + const bring = new bringApi(CREDENTIALS); + + try { + await bring.login(); + + switch (command) { + case 'add': + await addItem(bring, args.slice(1)); + break; + case 'list': + await listItems(bring); + break; + case 'remove': + await removeItem(bring, args.slice(1)); + break; + case 'recent': + await recentItems(bring); + break; + default: + console.error(`Unknown command: ${command}`); + console.error('Available commands: add, list, remove, recent'); + process.exit(1); + } + } catch (error) { + console.error('Error:', error.message); + process.exit(1); + } +} + +/** + * Add an item to the shopping list + */ +async function addItem(bring, args) { + const itemName = args[0]; + const specification = args.slice(1).join(' ') || ''; + + if (!itemName) { + console.error('Usage: bring add [specification]'); + process.exit(1); + } + + await bring.saveItem(LIST_UUID, itemName, specification); + + if (specification) { + console.log(`✓ "${itemName}" (${specification}) added to shopping list`); + } else { + console.log(`✓ "${itemName}" added to shopping list`); + } +} + +/** + * List all items currently on the shopping list + */ +async function listItems(bring) { + const items = await bring.getItems(LIST_UUID); + + if (items.purchase.length === 0) { + console.log('Shopping list is empty'); + return; + } + + console.log('Shopping list:'); + console.log(''); + items.purchase.forEach((item, index) => { + if (item.specification) { + console.log(`${index + 1}. ${item.name} (${item.specification})`); + } else { + console.log(`${index + 1}. ${item.name}`); + } + }); +} + +/** + * Remove an item from the shopping list (mark as purchased) + */ +async function removeItem(bring, args) { + const itemName = args.join(' '); + + if (!itemName) { + console.error('Usage: bring remove '); + process.exit(1); + } + + await bring.removeItem(LIST_UUID, itemName); + console.log(`✓ "${itemName}" removed from shopping list`); +} + +/** + * Show recently purchased items + */ +async function recentItems(bring) { + const items = await bring.getItems(LIST_UUID); + + if (items.recently.length === 0) { + console.log('No recently purchased items'); + return; + } + + console.log('Recently purchased:'); + console.log(''); + items.recently.slice(0, 10).forEach((item, index) => { + if (item.specification) { + console.log(`${index + 1}. ${item.name} (${item.specification})`); + } else { + console.log(`${index + 1}. ${item.name}`); + } + }); +} + +main(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..3ca8c39 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "bring-shopping-skill", + "version": "1.0.0", + "description": "Bring! Shopping List Skill for Carson", + "dependencies": { + "bring-shopping": "^2.0.1" + } +}