clamper-ai 1.3.0 → 1.4.0
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/package.json +1 -1
- package/skills/anthropic-frontend-design/SKILL.md +307 -0
- package/skills/anylist/SKILL.md +295 -0
- package/skills/ask-a-human/SKILL.md +323 -0
- package/skills/ceo-advisor/SKILL.md +276 -0
- package/skills/comfy-ai/SKILL.md +378 -0
- package/skills/google-home/SKILL.md +227 -0
- package/skills/morning-routine/SKILL.md +289 -0
- package/skills/n8n-automation/SKILL.md +239 -0
- package/skills/youtube-transcript/SKILL.md +175 -0
- package/skills/youtube-video-downloader/SKILL.md +230 -0
package/package.json
CHANGED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Anthropic Frontend Design
|
|
3
|
+
description: Frontend design system and UI/UX best practices following Anthropic's design philosophy for building polished, accessible interfaces
|
|
4
|
+
author: clamper
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
tier: pro
|
|
7
|
+
category: Developer
|
|
8
|
+
tags: [frontend, design, ui, ux, css, react, tailwind, accessibility, design-system]
|
|
9
|
+
triggers: [frontend design, ui design, design system, build ui, make it look good, anthropic style, modern design]
|
|
10
|
+
api_auth: none
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Anthropic Frontend Design
|
|
14
|
+
|
|
15
|
+
A comprehensive frontend design skill that guides building polished, modern, and accessible user interfaces. Follows Anthropic's design philosophy: clean, minimal, functional, and human-centered. No API required — this is a design system and code generation skill.
|
|
16
|
+
|
|
17
|
+
## Design Principles
|
|
18
|
+
|
|
19
|
+
1. **Clarity over cleverness** — Every element should have a clear purpose
|
|
20
|
+
2. **Generous whitespace** — Let content breathe with ample padding and margins
|
|
21
|
+
3. **Subtle depth** — Use soft shadows and borders instead of harsh contrasts
|
|
22
|
+
4. **Consistent rhythm** — Maintain a spacing scale and type hierarchy
|
|
23
|
+
5. **Accessible by default** — WCAG 2.1 AA minimum, semantic HTML, keyboard navigation
|
|
24
|
+
6. **Progressive disclosure** — Show only what's needed, reveal complexity on demand
|
|
25
|
+
|
|
26
|
+
## Color System
|
|
27
|
+
|
|
28
|
+
### Light Theme
|
|
29
|
+
```css
|
|
30
|
+
:root {
|
|
31
|
+
--color-bg-primary: #FFFFFF;
|
|
32
|
+
--color-bg-secondary: #F7F7F8;
|
|
33
|
+
--color-bg-tertiary: #ECECF1;
|
|
34
|
+
--color-bg-accent: #F0EEFF;
|
|
35
|
+
|
|
36
|
+
--color-text-primary: #1A1A2E;
|
|
37
|
+
--color-text-secondary: #6B6B80;
|
|
38
|
+
--color-text-tertiary: #9B9BAF;
|
|
39
|
+
--color-text-inverse: #FFFFFF;
|
|
40
|
+
|
|
41
|
+
--color-border-default: #E5E5EA;
|
|
42
|
+
--color-border-hover: #C8C8D0;
|
|
43
|
+
|
|
44
|
+
--color-accent-primary: #6B4EFF;
|
|
45
|
+
--color-accent-hover: #5A3DE8;
|
|
46
|
+
--color-accent-subtle: #F0EEFF;
|
|
47
|
+
|
|
48
|
+
--color-success: #00A67E;
|
|
49
|
+
--color-warning: #F5A623;
|
|
50
|
+
--color-error: #EF4444;
|
|
51
|
+
--color-info: #3B82F6;
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Dark Theme
|
|
56
|
+
```css
|
|
57
|
+
[data-theme="dark"] {
|
|
58
|
+
--color-bg-primary: #1A1A2E;
|
|
59
|
+
--color-bg-secondary: #23233A;
|
|
60
|
+
--color-bg-tertiary: #2D2D48;
|
|
61
|
+
--color-bg-accent: #2A2545;
|
|
62
|
+
|
|
63
|
+
--color-text-primary: #ECECF1;
|
|
64
|
+
--color-text-secondary: #9B9BAF;
|
|
65
|
+
--color-text-tertiary: #6B6B80;
|
|
66
|
+
--color-text-inverse: #1A1A2E;
|
|
67
|
+
|
|
68
|
+
--color-border-default: #3A3A52;
|
|
69
|
+
--color-border-hover: #4A4A65;
|
|
70
|
+
|
|
71
|
+
--color-accent-primary: #8B7AFF;
|
|
72
|
+
--color-accent-hover: #9D8FFF;
|
|
73
|
+
--color-accent-subtle: #2A2545;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Typography
|
|
78
|
+
|
|
79
|
+
```css
|
|
80
|
+
:root {
|
|
81
|
+
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
82
|
+
--font-mono: 'JetBrains Mono', 'Fira Code', 'SF Mono', monospace;
|
|
83
|
+
|
|
84
|
+
--text-xs: 0.75rem; /* 12px */
|
|
85
|
+
--text-sm: 0.875rem; /* 14px */
|
|
86
|
+
--text-base: 1rem; /* 16px */
|
|
87
|
+
--text-lg: 1.125rem; /* 18px */
|
|
88
|
+
--text-xl: 1.25rem; /* 20px */
|
|
89
|
+
--text-2xl: 1.5rem; /* 24px */
|
|
90
|
+
--text-3xl: 1.875rem; /* 30px */
|
|
91
|
+
--text-4xl: 2.25rem; /* 36px */
|
|
92
|
+
|
|
93
|
+
--leading-tight: 1.25;
|
|
94
|
+
--leading-normal: 1.5;
|
|
95
|
+
--leading-relaxed: 1.75;
|
|
96
|
+
|
|
97
|
+
--tracking-tight: -0.025em;
|
|
98
|
+
--tracking-normal: 0;
|
|
99
|
+
--tracking-wide: 0.025em;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Spacing Scale
|
|
104
|
+
|
|
105
|
+
```css
|
|
106
|
+
:root {
|
|
107
|
+
--space-0: 0;
|
|
108
|
+
--space-1: 0.25rem; /* 4px */
|
|
109
|
+
--space-2: 0.5rem; /* 8px */
|
|
110
|
+
--space-3: 0.75rem; /* 12px */
|
|
111
|
+
--space-4: 1rem; /* 16px */
|
|
112
|
+
--space-5: 1.25rem; /* 20px */
|
|
113
|
+
--space-6: 1.5rem; /* 24px */
|
|
114
|
+
--space-8: 2rem; /* 32px */
|
|
115
|
+
--space-10: 2.5rem; /* 40px */
|
|
116
|
+
--space-12: 3rem; /* 48px */
|
|
117
|
+
--space-16: 4rem; /* 64px */
|
|
118
|
+
--space-20: 5rem; /* 80px */
|
|
119
|
+
--space-24: 6rem; /* 96px */
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Component Patterns
|
|
124
|
+
|
|
125
|
+
### Button
|
|
126
|
+
```jsx
|
|
127
|
+
function Button({ variant = "primary", size = "md", children, ...props }) {
|
|
128
|
+
const base = "inline-flex items-center justify-center font-medium rounded-lg transition-all duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent-primary disabled:opacity-50 disabled:cursor-not-allowed";
|
|
129
|
+
|
|
130
|
+
const variants = {
|
|
131
|
+
primary: "bg-accent-primary text-white hover:bg-accent-hover shadow-sm",
|
|
132
|
+
secondary: "bg-bg-secondary text-text-primary border border-border-default hover:border-border-hover hover:bg-bg-tertiary",
|
|
133
|
+
ghost: "text-text-secondary hover:text-text-primary hover:bg-bg-secondary",
|
|
134
|
+
danger: "bg-error text-white hover:bg-red-600",
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const sizes = {
|
|
138
|
+
sm: "px-3 py-1.5 text-sm",
|
|
139
|
+
md: "px-4 py-2 text-base",
|
|
140
|
+
lg: "px-6 py-3 text-lg",
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<button className={`${base} ${variants[variant]} ${sizes[size]}`} {...props}>
|
|
145
|
+
{children}
|
|
146
|
+
</button>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Card
|
|
152
|
+
```jsx
|
|
153
|
+
function Card({ title, description, children, className = "" }) {
|
|
154
|
+
return (
|
|
155
|
+
<div className={`bg-bg-primary border border-border-default rounded-xl p-6 shadow-sm hover:shadow-md transition-shadow ${className}`}>
|
|
156
|
+
{title && <h3 className="text-lg font-semibold text-text-primary mb-1">{title}</h3>}
|
|
157
|
+
{description && <p className="text-sm text-text-secondary mb-4">{description}</p>}
|
|
158
|
+
{children}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Input
|
|
165
|
+
```jsx
|
|
166
|
+
function Input({ label, error, helper, ...props }) {
|
|
167
|
+
return (
|
|
168
|
+
<div className="space-y-1.5">
|
|
169
|
+
{label && <label className="block text-sm font-medium text-text-primary">{label}</label>}
|
|
170
|
+
<input
|
|
171
|
+
className={`w-full px-3 py-2 rounded-lg border text-text-primary bg-bg-primary placeholder:text-text-tertiary transition-colors focus:outline-none focus:ring-2 focus:ring-accent-primary focus:border-transparent ${
|
|
172
|
+
error ? "border-error" : "border-border-default hover:border-border-hover"
|
|
173
|
+
}`}
|
|
174
|
+
{...props}
|
|
175
|
+
/>
|
|
176
|
+
{error && <p className="text-sm text-error">{error}</p>}
|
|
177
|
+
{helper && !error && <p className="text-sm text-text-tertiary">{helper}</p>}
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Modal / Dialog
|
|
184
|
+
```jsx
|
|
185
|
+
function Modal({ open, onClose, title, children }) {
|
|
186
|
+
if (!open) return null;
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
190
|
+
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm" onClick={onClose} />
|
|
191
|
+
<div className="relative bg-bg-primary rounded-2xl shadow-2xl border border-border-default max-w-lg w-full mx-4 p-6 animate-in fade-in zoom-in-95 duration-200">
|
|
192
|
+
<div className="flex items-center justify-between mb-4">
|
|
193
|
+
<h2 className="text-xl font-semibold text-text-primary">{title}</h2>
|
|
194
|
+
<button onClick={onClose} className="p-1 rounded-lg hover:bg-bg-secondary text-text-tertiary hover:text-text-primary transition-colors">
|
|
195
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="2">
|
|
196
|
+
<path d="M5 5l10 10M15 5L5 15" />
|
|
197
|
+
</svg>
|
|
198
|
+
</button>
|
|
199
|
+
</div>
|
|
200
|
+
{children}
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Toast / Notification
|
|
208
|
+
```jsx
|
|
209
|
+
function Toast({ type = "info", message, onDismiss }) {
|
|
210
|
+
const icons = { success: "check-circle", error: "x-circle", warning: "alert-triangle", info: "info" };
|
|
211
|
+
const colors = { success: "text-success", error: "text-error", warning: "text-warning", info: "text-info" };
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div className="flex items-center gap-3 px-4 py-3 bg-bg-primary border border-border-default rounded-xl shadow-lg animate-in slide-in-from-right duration-300">
|
|
215
|
+
<span className={colors[type]}>●</span>
|
|
216
|
+
<p className="text-sm text-text-primary flex-1">{message}</p>
|
|
217
|
+
<button onClick={onDismiss} className="text-text-tertiary hover:text-text-primary text-sm">Dismiss</button>
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Layout Patterns
|
|
224
|
+
|
|
225
|
+
### Page Layout
|
|
226
|
+
```jsx
|
|
227
|
+
function PageLayout({ children }) {
|
|
228
|
+
return (
|
|
229
|
+
<div className="min-h-screen bg-bg-secondary">
|
|
230
|
+
<nav className="sticky top-0 z-40 bg-bg-primary/80 backdrop-blur-lg border-b border-border-default">
|
|
231
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
|
|
232
|
+
{/* Logo + Navigation */}
|
|
233
|
+
</div>
|
|
234
|
+
</nav>
|
|
235
|
+
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
236
|
+
{children}
|
|
237
|
+
</main>
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Sidebar Layout
|
|
244
|
+
```jsx
|
|
245
|
+
function SidebarLayout({ sidebar, children }) {
|
|
246
|
+
return (
|
|
247
|
+
<div className="flex min-h-screen">
|
|
248
|
+
<aside className="w-64 bg-bg-primary border-r border-border-default p-4 flex-shrink-0">
|
|
249
|
+
{sidebar}
|
|
250
|
+
</aside>
|
|
251
|
+
<main className="flex-1 p-8 bg-bg-secondary overflow-auto">
|
|
252
|
+
{children}
|
|
253
|
+
</main>
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Accessibility Checklist
|
|
260
|
+
|
|
261
|
+
- [ ] All interactive elements are keyboard accessible
|
|
262
|
+
- [ ] Focus indicators are visible (never `outline: none` without replacement)
|
|
263
|
+
- [ ] Color contrast ratios meet WCAG AA (4.5:1 for text, 3:1 for large text)
|
|
264
|
+
- [ ] Form inputs have associated labels
|
|
265
|
+
- [ ] Images have alt text
|
|
266
|
+
- [ ] ARIA landmarks used for page regions
|
|
267
|
+
- [ ] Modals trap focus and return it on close
|
|
268
|
+
- [ ] Animations respect `prefers-reduced-motion`
|
|
269
|
+
- [ ] Text is resizable up to 200% without loss of content
|
|
270
|
+
|
|
271
|
+
```css
|
|
272
|
+
@media (prefers-reduced-motion: reduce) {
|
|
273
|
+
*, *::before, *::after {
|
|
274
|
+
animation-duration: 0.01ms !important;
|
|
275
|
+
transition-duration: 0.01ms !important;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Clamper Integration
|
|
281
|
+
|
|
282
|
+
When the user asks for frontend design help:
|
|
283
|
+
1. Identify the component or layout they need
|
|
284
|
+
2. Generate code using the design system tokens above
|
|
285
|
+
3. Default to React + Tailwind CSS unless specified otherwise
|
|
286
|
+
4. Always include accessibility features
|
|
287
|
+
5. Offer both light and dark theme support
|
|
288
|
+
6. Provide responsive variants for mobile/tablet/desktop
|
|
289
|
+
|
|
290
|
+
### Example Interaction
|
|
291
|
+
```
|
|
292
|
+
User: Build me a settings page
|
|
293
|
+
Assistant: *generates a settings page with sidebar navigation, form sections, and save/cancel buttons following the design system*
|
|
294
|
+
|
|
295
|
+
User: Make it dark mode
|
|
296
|
+
Assistant: *adds data-theme="dark" support and updates color tokens*
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Setup
|
|
300
|
+
|
|
301
|
+
No installation required. This skill provides design guidance and code generation using standard web technologies (HTML, CSS, React, Tailwind CSS).
|
|
302
|
+
|
|
303
|
+
Recommended dependencies for projects:
|
|
304
|
+
```bash
|
|
305
|
+
npm install tailwindcss @tailwindcss/forms @tailwindcss/typography
|
|
306
|
+
npm install @fontsource/inter
|
|
307
|
+
```
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: AnyList
|
|
3
|
+
description: Shopping list management, meal planning, and recipe organization via AnyList integration
|
|
4
|
+
author: clamper
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
tier: pro
|
|
7
|
+
category: Productivity
|
|
8
|
+
tags: [shopping, grocery, lists, meal-planning, recipes, anylist, organization]
|
|
9
|
+
triggers: [shopping list, grocery list, add to list, meal plan, anylist, what do i need to buy]
|
|
10
|
+
api_auth: credentials
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# AnyList
|
|
14
|
+
|
|
15
|
+
Manage shopping lists, meal plans, and recipes through AnyList. Add items, organize lists, plan meals for the week, and share lists with family. Uses the unofficial AnyList API via reverse-engineered endpoints.
|
|
16
|
+
|
|
17
|
+
## Dependencies
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install anylist
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The `anylist` Python package provides an unofficial API client.
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
### Environment Variables
|
|
28
|
+
```bash
|
|
29
|
+
export ANYLIST_EMAIL="your-email@example.com"
|
|
30
|
+
export ANYLIST_PASSWORD="your-password"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Authentication
|
|
34
|
+
```python
|
|
35
|
+
import asyncio
|
|
36
|
+
from anylist import AnyList
|
|
37
|
+
|
|
38
|
+
async def connect():
|
|
39
|
+
anylist = AnyList(email="your-email@example.com", password="your-password")
|
|
40
|
+
await anylist.login()
|
|
41
|
+
return anylist
|
|
42
|
+
|
|
43
|
+
client = asyncio.run(connect())
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
### List All Shopping Lists
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import asyncio
|
|
52
|
+
from anylist import AnyList
|
|
53
|
+
|
|
54
|
+
async def get_lists():
|
|
55
|
+
al = AnyList(email=EMAIL, password=PASSWORD)
|
|
56
|
+
await al.login()
|
|
57
|
+
|
|
58
|
+
for lst in al.lists:
|
|
59
|
+
item_count = len([i for i in lst.items if not i.checked])
|
|
60
|
+
print(f"- {lst.name}: {item_count} items remaining")
|
|
61
|
+
|
|
62
|
+
await al.teardown()
|
|
63
|
+
|
|
64
|
+
asyncio.run(get_lists())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Output:
|
|
68
|
+
```
|
|
69
|
+
- Grocery: 12 items remaining
|
|
70
|
+
- Costco: 5 items remaining
|
|
71
|
+
- Target: 3 items remaining
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Get Items from a List
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
async def get_items(list_name):
|
|
78
|
+
al = AnyList(email=EMAIL, password=PASSWORD)
|
|
79
|
+
await al.login()
|
|
80
|
+
|
|
81
|
+
lst = al.get_list(list_name)
|
|
82
|
+
if lst:
|
|
83
|
+
print(f"\n{lst.name}:")
|
|
84
|
+
for item in lst.items:
|
|
85
|
+
status = "x" if item.checked else " "
|
|
86
|
+
detail = f" ({item.detail})" if item.detail else ""
|
|
87
|
+
print(f" [{status}] {item.name}{detail}")
|
|
88
|
+
|
|
89
|
+
await al.teardown()
|
|
90
|
+
|
|
91
|
+
asyncio.run(get_items("Grocery"))
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Output:
|
|
95
|
+
```
|
|
96
|
+
Grocery:
|
|
97
|
+
[ ] Milk (whole, organic)
|
|
98
|
+
[ ] Eggs (dozen)
|
|
99
|
+
[ ] Bread
|
|
100
|
+
[x] Butter
|
|
101
|
+
[ ] Chicken breast (2 lbs)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Add Item to List
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
async def add_item(list_name, item_name, detail=None):
|
|
108
|
+
al = AnyList(email=EMAIL, password=PASSWORD)
|
|
109
|
+
await al.login()
|
|
110
|
+
|
|
111
|
+
lst = al.get_list(list_name)
|
|
112
|
+
if lst:
|
|
113
|
+
item = al.create_item(name=item_name, detail=detail)
|
|
114
|
+
lst.add_item(item)
|
|
115
|
+
await al.save()
|
|
116
|
+
print(f"Added '{item_name}' to {list_name}")
|
|
117
|
+
|
|
118
|
+
await al.teardown()
|
|
119
|
+
|
|
120
|
+
asyncio.run(add_item("Grocery", "Avocados", "4 ripe"))
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Add Multiple Items
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
async def add_multiple(list_name, items):
|
|
127
|
+
al = AnyList(email=EMAIL, password=PASSWORD)
|
|
128
|
+
await al.login()
|
|
129
|
+
|
|
130
|
+
lst = al.get_list(list_name)
|
|
131
|
+
if lst:
|
|
132
|
+
for name, detail in items:
|
|
133
|
+
item = al.create_item(name=name, detail=detail)
|
|
134
|
+
lst.add_item(item)
|
|
135
|
+
await al.save()
|
|
136
|
+
print(f"Added {len(items)} items to {list_name}")
|
|
137
|
+
|
|
138
|
+
await al.teardown()
|
|
139
|
+
|
|
140
|
+
items = [
|
|
141
|
+
("Bananas", "1 bunch"),
|
|
142
|
+
("Greek Yogurt", "plain, large"),
|
|
143
|
+
("Spinach", "baby, 1 bag"),
|
|
144
|
+
("Salmon", "2 fillets"),
|
|
145
|
+
]
|
|
146
|
+
asyncio.run(add_multiple("Grocery", items))
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Check Off / Remove Items
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
async def check_item(list_name, item_name):
|
|
153
|
+
al = AnyList(email=EMAIL, password=PASSWORD)
|
|
154
|
+
await al.login()
|
|
155
|
+
|
|
156
|
+
lst = al.get_list(list_name)
|
|
157
|
+
if lst:
|
|
158
|
+
for item in lst.items:
|
|
159
|
+
if item.name.lower() == item_name.lower():
|
|
160
|
+
item.checked = True
|
|
161
|
+
await al.save()
|
|
162
|
+
print(f"Checked off '{item_name}'")
|
|
163
|
+
break
|
|
164
|
+
|
|
165
|
+
await al.teardown()
|
|
166
|
+
|
|
167
|
+
asyncio.run(check_item("Grocery", "Milk"))
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Create a New List
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
async def create_list(name):
|
|
174
|
+
al = AnyList(email=EMAIL, password=PASSWORD)
|
|
175
|
+
await al.login()
|
|
176
|
+
|
|
177
|
+
new_list = al.create_list(name=name)
|
|
178
|
+
await al.save()
|
|
179
|
+
print(f"Created list: {name}")
|
|
180
|
+
|
|
181
|
+
await al.teardown()
|
|
182
|
+
|
|
183
|
+
asyncio.run(create_list("Party Supplies"))
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Get Recipes
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
async def get_recipes():
|
|
190
|
+
al = AnyList(email=EMAIL, password=PASSWORD)
|
|
191
|
+
await al.login()
|
|
192
|
+
|
|
193
|
+
for recipe in al.recipes[:10]:
|
|
194
|
+
print(f"- {recipe.name}")
|
|
195
|
+
if recipe.note:
|
|
196
|
+
print(f" Note: {recipe.note}")
|
|
197
|
+
|
|
198
|
+
await al.teardown()
|
|
199
|
+
|
|
200
|
+
asyncio.run(get_recipes())
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Add Recipe Ingredients to Shopping List
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
async def recipe_to_list(recipe_name, list_name):
|
|
207
|
+
al = AnyList(email=EMAIL, password=PASSWORD)
|
|
208
|
+
await al.login()
|
|
209
|
+
|
|
210
|
+
recipe = None
|
|
211
|
+
for r in al.recipes:
|
|
212
|
+
if r.name.lower() == recipe_name.lower():
|
|
213
|
+
recipe = r
|
|
214
|
+
break
|
|
215
|
+
|
|
216
|
+
if recipe:
|
|
217
|
+
lst = al.get_list(list_name)
|
|
218
|
+
for ingredient in recipe.ingredients:
|
|
219
|
+
item = al.create_item(name=ingredient.name, detail=ingredient.detail)
|
|
220
|
+
lst.add_item(item)
|
|
221
|
+
await al.save()
|
|
222
|
+
print(f"Added {len(recipe.ingredients)} ingredients from '{recipe_name}' to '{list_name}'")
|
|
223
|
+
|
|
224
|
+
await al.teardown()
|
|
225
|
+
|
|
226
|
+
asyncio.run(recipe_to_list("Chicken Parmesan", "Grocery"))
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Meal Planning
|
|
230
|
+
|
|
231
|
+
### View Meal Plan
|
|
232
|
+
```python
|
|
233
|
+
async def get_meal_plan():
|
|
234
|
+
al = AnyList(email=EMAIL, password=PASSWORD)
|
|
235
|
+
await al.login()
|
|
236
|
+
|
|
237
|
+
for day in al.meal_plan:
|
|
238
|
+
print(f"\n{day.date}:")
|
|
239
|
+
for meal in day.meals:
|
|
240
|
+
print(f" {meal.type}: {meal.recipe_name or meal.name}")
|
|
241
|
+
|
|
242
|
+
await al.teardown()
|
|
243
|
+
|
|
244
|
+
asyncio.run(get_meal_plan())
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Output:
|
|
248
|
+
```
|
|
249
|
+
Monday:
|
|
250
|
+
Breakfast: Overnight Oats
|
|
251
|
+
Lunch: Chicken Caesar Salad
|
|
252
|
+
Dinner: Pasta Bolognese
|
|
253
|
+
|
|
254
|
+
Tuesday:
|
|
255
|
+
Breakfast: Smoothie Bowl
|
|
256
|
+
Dinner: Grilled Salmon
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Clamper Integration
|
|
260
|
+
|
|
261
|
+
When the user wants to manage shopping/grocery lists:
|
|
262
|
+
1. Authenticate with stored AnyList credentials
|
|
263
|
+
2. List available shopping lists when asked
|
|
264
|
+
3. Add items naturally: "add milk and eggs to grocery list"
|
|
265
|
+
4. Parse quantities and details from natural language
|
|
266
|
+
5. Support meal planning queries: "what's for dinner this week?"
|
|
267
|
+
6. Add recipe ingredients to shopping list on request
|
|
268
|
+
|
|
269
|
+
### Example Interaction
|
|
270
|
+
```
|
|
271
|
+
User: Add these to my grocery list: 2 lbs chicken breast, a dozen eggs, whole milk, and bread
|
|
272
|
+
Assistant: *connects to AnyList, adds 4 items*
|
|
273
|
+
Added to Grocery list:
|
|
274
|
+
- Chicken breast (2 lbs)
|
|
275
|
+
- Eggs (dozen)
|
|
276
|
+
- Whole milk
|
|
277
|
+
- Bread
|
|
278
|
+
|
|
279
|
+
User: What's on my Costco list?
|
|
280
|
+
Assistant: *fetches Costco list*
|
|
281
|
+
Your Costco list has 5 items:
|
|
282
|
+
- [ ] Paper towels (bulk pack)
|
|
283
|
+
- [ ] Olive oil (2L)
|
|
284
|
+
- [ ] Rotisserie chicken
|
|
285
|
+
- [x] Laundry detergent
|
|
286
|
+
- [ ] Trash bags
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Setup
|
|
290
|
+
|
|
291
|
+
1. Install the AnyList Python package: `pip install anylist`
|
|
292
|
+
2. Set `ANYLIST_EMAIL` and `ANYLIST_PASSWORD` environment variables
|
|
293
|
+
3. AnyList account required (free tier works, AnyList+ unlocks extra features)
|
|
294
|
+
|
|
295
|
+
**Note:** This uses an unofficial API. Functionality may change if AnyList updates their backend.
|