feat(aiPlugin): first draft

This commit is contained in:
Johannes Millan 2025-08-29 15:50:19 +02:00
parent 41448e543b
commit 27171607fa
21 changed files with 4162 additions and 0 deletions

View file

@ -0,0 +1,103 @@
# Procrastination Buster Plugin
A Super Productivity plugin that helps identify procrastination blockers and provides tailored strategies to overcome them.
## Features
- 🎯 Identify 8 different procrastination types
- 💡 Get tailored strategies for each type
- ⏱️ Start Pomodoro timer directly from strategies
- Add strategies as tasks
- 🌓 Dark mode support using CSS variables
## Installation
### Development
```bash
# Install dependencies
npm install
# Start development server
npm run dev
# Build for production
npm run build
# Create plugin ZIP
npm run package
```
### Use in Super Productivity
1. Run `npm run build`
2. Upload the generated `dist/plugin.zip` in Super Productivity
3. Or copy the `dist` folder to `src/assets/procrastination-buster/`
## Usage
1. **Shortcut**: Use keyboard shortcut for quick access
2. **Side Panel**: Open the plugin via the side panel
3. **Automatic**: After 15 minutes of inactivity on a task
## Procrastination Types
1. **Overwhelm** - "Too much at once"
2. **Perfectionism** - "It's not perfect enough"
3. **Unclear** - "I don't know what to do"
4. **Boredom** - "It's boring"
5. **Fear** - "I might fail"
6. **Low Energy** - "I'm too tired"
7. **Distraction** - "Other things are more interesting"
8. **Resistance** - "I don't want to do this"
## Technology
- **SolidJS** for reactive UI
- **Vite** for fast development and builds
- **TypeScript** for type safety
- **Super Productivity Plugin API**
- **CSS Variables** for theme integration
## Development
The plugin consists of two parts:
1. **plugin.ts** - Backend logic that communicates with Super Productivity
2. **SolidJS App** - Frontend UI in iframe
### Project Structure
```
procrastination-buster/
├── src/
│ ├── plugin.ts # Plugin backend
│ ├── App.tsx # Main component
│ ├── types.ts # TypeScript definitions
│ ├── BlockerSelector.tsx
│ └── StrategyList.tsx
├── manifest.json # Plugin metadata
├── index.html # HTML entry
└── vite.config.ts # Build configuration
```
## Customization
### Add New Strategies
Edit `src/types.ts` and add new strategies to the appropriate types.
### Styling Customization
Edit `src/App.css` for visual adjustments. The plugin uses CSS variables for seamless theme integration:
- `--primary-color` - Main theme color
- `--text-color` - Primary text
- `--background-color` - Background
- `--card-background` - Card backgrounds
- `--border-radius` - Standard radius
- And many more...
## License
MIT

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,28 @@
{
"name": "ai-productivity-prompts-plugin",
"version": "1.0.0",
"description": "AI Productivity Prompts plugin for Super Productivity",
"type": "module",
"scripts": {
"dev": "vite",
"dev:watch": "node scripts/watch-and-build.js",
"build": "vite build && node scripts/inline-assets.js",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"deploy": "npm run build",
"clean": "rm -rf dist node_modules *.zip",
"lint": "tsc --noEmit"
},
"devDependencies": {
"@solidjs/router": "^0.14.10",
"@types/node": "^22.15.33",
"solid-js": "^1.9.7",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"vite-plugin-solid": "^2.11.7",
"archiver": "^7.0.1"
},
"dependencies": {
"@super-productivity/plugin-api": "file:../../plugin-api"
}
}

View file

@ -0,0 +1,81 @@
# Plugin Message Communication in Super Productivity
## How iframe plugins receive messages
### 1. Plugin registers a message handler in its iframe (index.html):
```javascript
// In the plugin's index.html
PluginAPI.onMessage(async (message) => {
console.log('Plugin received message:', message);
// Handle different message types
if (message.type === 'updateBlockedSites') {
// Update the plugin's state
return { success: true, sites: message.sites };
}
return { error: 'Unknown message type' };
});
```
### 2. Host app sends a message to the plugin:
```typescript
// From anywhere in the Super Productivity app
const pluginBridge = inject(PluginBridgeService);
const response = await pluginBridge.sendMessageToPlugin('procrastination-buster', {
type: 'updateBlockedSites',
sites: ['reddit.com', 'twitter.com'],
});
```
### 3. Message flow:
1. `PluginBridgeService.sendMessageToPlugin()` is called
2. It delegates to `PluginRunner.sendMessageToPlugin()`
3. PluginRunner finds the PluginAPI instance and calls its `__sendMessage()` method
4. For iframe plugins, this triggers a postMessage to the iframe with type `PLUGIN_MESSAGE`
5. The iframe's message listener (set up by `onMessage`) handles the message
6. The response is sent back via postMessage with type `PLUGIN_MESSAGE_RESPONSE`
7. The promise resolves with the response
### 4. Implementation details:
The iframe message handling is set up in `plugin-iframe.util.ts`:
```javascript
// When onMessage is called in the iframe:
onMessage: (handler) => {
window.__pluginMessageHandler = handler;
window.addEventListener('message', async (event) => {
if (event.data?.type === 'PLUGIN_MESSAGE' && window.__pluginMessageHandler) {
try {
const result = await window.__pluginMessageHandler(event.data.message);
event.source?.postMessage(
{
type: 'PLUGIN_MESSAGE_RESPONSE',
messageId: event.data.messageId,
result,
},
'*',
);
} catch (error) {
event.source?.postMessage(
{
type: 'PLUGIN_MESSAGE_ERROR',
messageId: event.data.messageId,
error: error.message,
},
'*',
);
}
}
});
};
```
However, I notice that the actual sending of `PLUGIN_MESSAGE` to the iframe is not implemented in the current code. The `__sendMessage` method on PluginAPI calls the handler directly for non-iframe plugins, but there's no code to post the message to the iframe.
This appears to be a missing piece in the implementation that would need to be added to complete the message communication system for iframe plugins.

View file

@ -0,0 +1,61 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const distDir = path.join(__dirname, '../dist');
const targetDir = path.join(
__dirname,
'../../../../src/assets/bundled-plugins/ai-productivity-prompts',
);
// Read the HTML file
const htmlPath = path.join(distDir, 'index.html');
let html = fs.readFileSync(htmlPath, 'utf8');
// Read and inline JavaScript
const jsPath = path.join(distDir, 'index.js');
if (fs.existsSync(jsPath)) {
const js = fs.readFileSync(jsPath, 'utf8');
// Find script tag and replace with inline version, preserving type="module"
html = html.replace(
/<script([^>]*)src="[^"]*index\.js"[^>]*><\/script>/g,
(match, attrs) => {
// Check if it has type="module"
if (attrs.includes('type="module"') || match.includes('type="module"')) {
return `<script type="module">${js}</script>`;
}
return `<script>${js}</script>`;
},
);
}
// Read and inline CSS
const cssPath = path.join(distDir, 'index.css');
if (fs.existsSync(cssPath)) {
const css = fs.readFileSync(cssPath, 'utf8');
html = html.replace(/<link[^>]*href="[^"]*index\.css"[^>]*>/g, `<style>${css}</style>`);
}
// Create target directory if it doesn't exist
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
// Write the inlined HTML to the target directory
const targetHtmlPath = path.join(targetDir, 'index.html');
fs.writeFileSync(targetHtmlPath, html);
// Copy other required files
const filesToCopy = ['manifest.json', 'plugin.js', 'icon.svg'];
filesToCopy.forEach((file) => {
const srcPath = path.join(distDir, file);
const destPath = path.join(targetDir, file);
if (fs.existsSync(srcPath)) {
fs.copyFileSync(srcPath, destPath);
}
});
console.log('Assets inlined and deployed successfully!');

View file

@ -0,0 +1,64 @@
#!/usr/bin/env node
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import path from 'path';
import fs from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.dirname(__dirname);
console.log('🚀 Starting development server with auto-packaging...\n');
// Start Vite dev server
const viteProcess = spawn('npm', ['run', 'dev'], {
cwd: projectRoot,
stdio: 'inherit',
shell: true,
});
// Watch for changes and rebuild package
const srcPath = path.join(projectRoot, 'src');
let buildTimeout;
const rebuildPackage = () => {
if (buildTimeout) clearTimeout(buildTimeout);
buildTimeout = setTimeout(async () => {
console.log('\n📦 Building plugin package...');
const buildProcess = spawn('npm', ['run', 'package'], {
cwd: projectRoot,
stdio: 'inherit',
shell: true,
});
buildProcess.on('close', (code) => {
if (code === 0) {
console.log('✅ Plugin package built successfully!\n');
} else {
console.error('❌ Failed to build plugin package\n');
}
});
}, 1000); // Debounce for 1 second
};
// Watch src directory for changes
fs.watch(srcPath, { recursive: true }, (eventType, filename) => {
if (filename && !filename.includes('.swp') && !filename.includes('.tmp')) {
console.log(`📝 Change detected in ${filename}`);
rebuildPackage();
}
});
// Handle process termination
process.on('SIGINT', () => {
console.log('\n👋 Stopping development server...');
viteProcess.kill();
process.exit();
});
viteProcess.on('close', (code) => {
console.log(`Vite process exited with code ${code}`);
process.exit(code);
});

View file

@ -0,0 +1,241 @@
/*
* AI Productivity Prompts Plugin Styles
* Simplified structure following KISS principles
*/
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: transparent;
color: var(--text-color);
}
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
padding: 1rem;
}
/* Common card styles */
.card {
background: var(--card-bg);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 1rem;
text-align: left;
border: 2px solid var(--extra-border-color);
}
.card-clickable:hover {
transition: transform 0.2s;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
border-color: var(--c-primary);
cursor: pointer;
}
/* Common text styles */
.text-muted {
color: var(--text-color-muted);
}
.text-primary {
color: var(--c-primary);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.header h1 {
margin: 0;
font-size: 1.5rem;
color: var(--c-primary);
}
.back-button {
padding: 0.5rem 1rem;
background: transparent;
border: 1px solid var(--c-primary);
color: var(--c-primary);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
font-family: inherit;
}
.back-button:hover {
background: var(--c-primary);
color: white;
}
.main {
flex: 1;
position: relative;
overflow: hidden;
}
.intro {
text-align: center;
margin-bottom: 2rem;
}
.intro h2 {
font-size: 1.25rem;
margin-bottom: 0.5rem;
}
/* Category Grid */
.category-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
margin-top: 2rem;
}
.category-card h3 {
margin: 0 0 0.5rem 0;
font-size: 1.1rem;
}
.category-card p {
margin: 0;
font-size: 0.9rem;
}
/* Category View */
.category-container {
max-width: 800px;
margin: 0 auto;
}
.selected-category {
padding: 1.5rem 0;
text-align: center;
border-bottom: 1px solid var(--extra-border-color);
margin-bottom: 2rem;
}
.selected-category h2 {
margin: 0;
}
/* Prompt List */
.prompt-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.prompt-item {
padding: 1rem;
cursor: pointer;
transition: all 0.2s;
}
.prompt-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
border-color: var(--c-primary);
}
.prompt-title {
margin: 0;
font-size: 1rem;
font-weight: 600;
}
/* Prompt View */
.prompt-container {
max-width: 800px;
margin: 0 auto;
}
.selected-prompt-header {
padding: 1.5rem 0;
text-align: center;
border-bottom: 1px solid var(--extra-border-color);
margin-bottom: 2rem;
}
.selected-prompt-header h2 {
margin: 0 0 0.5rem 0;
}
.selected-prompt-header p {
margin: 0;
}
.generated-prompt {
padding: 1.5rem;
}
.generated-prompt h3 {
margin: 0 0 1rem 0;
color: var(--c-primary);
}
.prompt-text {
width: 100%;
min-height: 300px;
padding: 1rem;
border: 1px solid var(--extra-border-color);
border-radius: 4px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', monospace;
font-size: 0.9rem;
line-height: 1.5;
background: var(--card-bg);
color: var(--text-color);
resize: vertical;
}
.prompt-actions {
margin-top: 1rem;
display: flex;
justify-content: center;
}
.copy-button {
padding: 0.75rem 1.5rem;
background: var(--c-primary);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
font-family: inherit;
transition: all 0.2s;
}
.copy-button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.copy-button:disabled {
opacity: 0.7;
transform: none;
cursor: not-allowed;
}
/* Simple page transition */
.page-fade {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

View file

@ -0,0 +1,173 @@
import { Component, createSignal, Show, For } from 'solid-js';
import { PROMPT_CATEGORIES, PromptCategory, renderPrompt } from './types';
import './App.css';
type ViewState = 'home' | 'category' | 'prompt';
interface SelectedPrompt {
category: PromptCategory;
prompt: { title: string; template: string };
}
const App: Component = () => {
const [currentView, setCurrentView] = createSignal<ViewState>('home');
const [selectedCategory, setSelectedCategory] = createSignal<PromptCategory | null>(
null,
);
const [selectedPrompt, setSelectedPrompt] = createSignal<SelectedPrompt | null>(null);
const [generatedPrompt, setGeneratedPrompt] = createSignal<string>('');
const handleSelectCategory = (category: PromptCategory) => {
setSelectedCategory(category);
setCurrentView('category');
};
const handleSelectPrompt = async (prompt: { title: string; template: string }) => {
const category = selectedCategory();
if (!category) return;
// Get current tasks from Super Productivity
const pluginAPI = (window as any).PluginAPI;
let tasksMd = '';
if (pluginAPI) {
try {
// Try to get current tasks as markdown
const tasks = await pluginAPI.getCurrentTasks?.();
if (tasks && tasks.length > 0) {
tasksMd = tasks.map((task: any) => `- [ ] ${task.title}`).join('\n');
}
} catch (error) {
console.log('Could not fetch tasks:', error);
}
}
const rendered = renderPrompt(prompt.template, tasksMd);
setGeneratedPrompt(rendered);
setSelectedPrompt({ category, prompt });
setCurrentView('prompt');
};
const handleBack = () => {
if (currentView() === 'prompt') {
setCurrentView('category');
setSelectedPrompt(null);
setGeneratedPrompt('');
} else if (currentView() === 'category') {
setCurrentView('home');
setSelectedCategory(null);
}
};
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(generatedPrompt());
// Show a brief success message
const button = document.querySelector('.copy-button') as HTMLButtonElement;
if (button) {
const originalText = button.textContent;
button.textContent = 'Copied!';
button.disabled = true;
setTimeout(() => {
button.textContent = originalText;
button.disabled = false;
}, 1500);
}
} catch (error) {
console.error('Failed to copy:', error);
}
};
return (
<div class="app">
<Show when={currentView() !== 'home'}>
<header class="header page-fade">
<button
class="back-button"
onClick={handleBack}
>
Back
</button>
</header>
</Show>
<main class="main">
{/* Home View */}
<Show when={currentView() === 'home'}>
<div class="intro page-fade">
<h2>AI Productivity Prompts</h2>
<p class="text-muted">Choose what you need help with:</p>
</div>
<div class="category-grid page-fade">
<For each={PROMPT_CATEGORIES}>
{(category) => (
<button
class="category-card card card-clickable"
onClick={() => handleSelectCategory(category)}
>
<h3 class="text-primary">{category.title}</h3>
<p class="text-muted">{category.prompts.length} prompts</p>
</button>
)}
</For>
</div>
</Show>
{/* Category View */}
<Show when={currentView() === 'category' && selectedCategory()}>
<div class="category-container page-fade">
<div class="selected-category">
<h2 class="text-primary">{selectedCategory()!.title}</h2>
</div>
<h3>Available Prompts:</h3>
<div class="prompt-list">
<For each={selectedCategory()!.prompts}>
{(prompt) => (
<button
class="prompt-item card card-clickable"
onClick={() => handleSelectPrompt(prompt)}
>
<h4 class="prompt-title">{prompt.title}</h4>
</button>
)}
</For>
</div>
</div>
</Show>
{/* Prompt View */}
<Show when={currentView() === 'prompt' && selectedPrompt()}>
<div class="prompt-container page-fade">
<div class="selected-prompt-header">
<h2 class="text-primary">{selectedPrompt()!.prompt.title}</h2>
<p class="text-muted">From: {selectedPrompt()!.category.title}</p>
</div>
<div class="generated-prompt card">
<h3>Generated Prompt:</h3>
<textarea
class="prompt-text"
value={generatedPrompt()}
readonly
rows="15"
/>
<div class="prompt-actions">
<button
class="copy-button"
onClick={copyToClipboard}
>
📋 Copy to clipboard
</button>
</div>
</div>
</div>
</Show>
</main>
</div>
);
};
export default App;

View file

@ -0,0 +1,89 @@
import { Component } from 'solid-js';
import './App.css';
interface ProcrastinationInfoProps {
onBackToWork: () => void;
}
export const ProcrastinationInfo: Component<ProcrastinationInfoProps> = (props) => {
return (
<div class="page-fade info-content">
<div class="intro">
<h2>Understanding Procrastination</h2>
<p>
<strong>
Procrastination is an emotion regulation problem, not a time management
problem.
</strong>
</p>
</div>
<section>
<h3>The Procrastination Cycle</h3>
<p>
When we face tasks that trigger uncomfortable emotions, we enter a feedback
loop:
</p>
<div class="procrastination-graph">
<div class="graph-item">Fear of failure</div>
<div class="sync-icon"></div>
<div class="graph-item">Avoid the task</div>
<div class="sync-icon"></div>
<div class="graph-item">Temporary relief</div>
<div class="sync-icon"></div>
<div class="graph-item">Increased anxiety</div>
</div>
</section>
<section>
<h3>Breaking the Cycle</h3>
<p>
The key is to approach procrastination with curiosity and compassion, not
judgment. Ask yourself:
</p>
<ul>
<li>What emotions come up when I think about this task?</li>
<li>What specific aspect feels most challenging?</li>
<li>What am I afraid might happen if I start?</li>
</ul>
</section>
<section>
<h3>Practical Strategies</h3>
<ul>
<li>
<strong>Start small:</strong> What's the tiniest first step you could take?
</li>
<li>
<strong>Time-box:</strong> Work for just 10-25 minutes, then take a break
</li>
<li>
<strong>Reframe:</strong> Focus on progress over perfection
</li>
<li>
<strong>Self-compassion:</strong> Speak to yourself as you would to a good
friend
</li>
</ul>
</section>
<section>
<h3>Common Triggers</h3>
<p>
Procrastination is often triggered by perfectionism, fear of failure, feeling
overwhelmed, unclear expectations, or finding the task boring. Identifying your
specific trigger is the first step to moving forward.
</p>
</section>
<div class="action-buttons">
<button
class="primary-button"
onClick={props.onBackToWork}
>
Back to work!
</button>
</div>
</div>
);
};

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
<g transform="translate(50,50)">
<!-- Full size star shape -->
<path d="M 0,-45 L 11.25,-11.25 L 45,-13.5 L 15.75,6.75 L 22.5,40.5 L 0,13.5 L -22.5,40.5 L -15.75,6.75 L -45,-13.5 L -11.25,-11.25 Z" fill="currentColor" stroke="currentColor" stroke-width="2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 371 B

View file

@ -0,0 +1,18 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>Procrastination Buster</title>
</head>
<body>
<div id="root"></div>
<script
type="module"
src="./index.tsx"
></script>
</body>
</html>

View file

@ -0,0 +1,17 @@
/* @refresh reload */
import { render } from 'solid-js/web';
import App from './App';
const root = document.getElementById('root');
function waitForPluginAPI() {
if (typeof (window as any).PluginAPI !== 'undefined') {
if (root) {
render(() => <App />, root);
}
} else {
setTimeout(waitForPluginAPI, 100);
}
}
waitForPluginAPI();

View file

@ -0,0 +1,20 @@
{
"name": "AI Productivity Prompts",
"id": "ai-productivity-prompts",
"manifestVersion": 1,
"version": "1.0.0",
"minSupVersion": "13.0.0",
"description": "AI-powered productivity prompts to help you plan your day, week, and overcome challenges",
"author": "Super Productivity Community",
"homepage": "https://github.com/johannesjo/super-productivity",
"repository": {
"type": "git",
"url": "https://github.com/johannesjo/super-productivity.git"
},
"hooks": ["currentTaskChange"],
"permissions": ["showSnack", "openDialog", "addTask", "showIndexHtmlAsView"],
"iFrame": true,
"sidePanel": true,
"isSkipMenuEntry": false,
"icon": "icon.svg"
}

View file

@ -0,0 +1,3 @@
// Procrastination Buster Plugin for Super Productivity
// import { PluginInterface } from '@super-productivity/plugin-api';
// declare const plugin: PluginInterface;

View file

@ -0,0 +1,47 @@
import { SnackType, PluginMessageType } from './types';
// Plugin message interfaces
interface PluginMessage {
type: PluginMessageType;
[key: string]: any;
}
interface AddStrategyTaskMessage extends PluginMessage {
type: PluginMessageType.ADD_STRATEGY_TASK;
strategy: string;
blockerType: string;
}
interface StartPomodoroMessage extends PluginMessage {
type: PluginMessageType.START_POMODORO;
}
interface StartFocusModeMessage extends PluginMessage {
type: PluginMessageType.START_FOCUS_MODE;
}
interface QuickAddTaskMessage extends PluginMessage {
type: PluginMessageType.QUICK_ADD_TASK;
}
type AllPluginMessages =
| AddStrategyTaskMessage
| StartPomodoroMessage
| StartFocusModeMessage
| QuickAddTaskMessage;
// Window interface augmentation
declare global {
interface Window {
PluginAPI?: {
showSnack: (config: { msg: string; type?: SnackType }) => void;
openDialog: (config: any) => Promise<void>;
onMessage: (handler: (message: AllPluginMessages) => Promise<any>) => void;
addTask: (task: { title: string; notes?: string }) => Promise<string>;
dispatchAction: (action: { type: string; [key: string]: any }) => void;
};
__pluginMessageHandler?: (message: AllPluginMessages) => Promise<any>;
}
}
export {};

View file

@ -0,0 +1,405 @@
// ============================================================================
// Interfaces
// ============================================================================
export interface PromptCategory {
title: string;
prompts: {
title: string;
template: string;
}[];
}
// ============================================================================
// Helper Functions
// ============================================================================
/**
* Renders a prompt template with optional tasks markdown
*/
export function renderPrompt(template: string, tasksMd?: string): string {
return template.replace(/{{#if tasks_md}}([\s\S]*?){{\/if}}/g, (match, content) => {
if (tasksMd) {
return content.replace(/{{tasks_md}}/g, tasksMd);
}
return '';
});
}
// ============================================================================
// Data
// ============================================================================
export const PROMPT_CATEGORIES: PromptCategory[] = [
{
title: 'What should I do today?',
prompts: [
{
title: 'Pick my Top 3 for today',
template: `You are an executive function coach. Given my tasks, help me pick a realistic Top 3 for today. Consider:
- What has the highest impact?
- What's time-sensitive or has dependencies?
- What can I realistically complete given my energy and schedule?
- Balance between urgent and important tasks
Provide specific reasoning for each choice and suggest a rough time estimate.
{{#if tasks_md}}
Here are my tasks:
{{tasks_md}}
{{/if}}`,
},
{
title: '90-Minute Sprint',
template: `Create a single 90-minute sprint plan from these tasks. Include:
- **One primary objective** (what success looks like)
- **35 atomic steps** (specific, actionable items)
- **Definition of done** (how you'll know it's complete)
- **5-minute warm-up ritual** (to get into flow)
- **5-minute cooldown + log** (to capture what you learned)
Focus on deep work that moves the needle forward.
{{#if tasks_md}}
Tasks:
{{tasks_md}}
{{/if}}`,
},
{
title: 'Energy-Based Schedule',
template: `Help me schedule my day based on my energy patterns and task requirements. Suggest:
- **High-energy tasks** for when I'm sharpest (usually morning)
- **Medium-energy tasks** for mid-day
- **Low-energy tasks** for when I'm tired
- **Buffer time** between demanding tasks
- **Recovery breaks** to maintain productivity
{{#if tasks_md}}
Tasks to schedule:
{{tasks_md}}
{{/if}}`,
},
],
},
{
title: 'Plan my week',
prompts: [
{
title: 'Weekly Game Plan',
template: `Plan my week with a strategic approach:
1. **13 weekly outcomes** (what must be accomplished this week)
2. **Required tasks** for each outcome
3. **Draft schedule** by weekday (considering energy and commitments)
4. **Risks and buffers** (what could go wrong, how to prepare)
5. **'One thing' fallback** (if everything goes sideways, what's the minimum viable success?)
Focus on outcomes over activities.
{{#if tasks_md}}
Candidate tasks:
{{tasks_md}}
{{/if}}`,
},
{
title: 'Theme Days',
template: `Organize my week using theme days to batch similar work and reduce context switching:
- **Monday**: Planning & Strategy
- **Tuesday**: Deep Work & Creation
- **Wednesday**: Communication & Meetings
- **Thursday**: Implementation & Building
- **Friday**: Review & Administrative Tasks
Assign my tasks to appropriate theme days and suggest how to batch related work.
{{#if tasks_md}}
Tasks to organize:
{{tasks_md}}
{{/if}}`,
},
],
},
{
title: "I'm procrastinating",
prompts: [
{
title: 'Anti-Procrastination Coach',
template: `I'm procrastinating. Help me identify the likely blockers and provide specific solutions:
**Common blockers to check:**
- **Fear** (of failure, judgment, or success)
- **Ambiguity** (unclear next steps)
- **Overwhelm** (task feels too big)
- **Boredom** (task feels tedious)
- **Perfectionism** (standards too high)
For each relevant blocker, give me:
1. A **tiny next action** (<10 minutes)
2. A **'first ugly draft' approach**
3. A specific **reframing** technique
{{#if tasks_md}}
Context:
{{tasks_md}}
{{/if}}`,
},
{
title: '2-Minute Rule',
template: `Apply the 2-minute rule to break through procrastination:
1. **Identify the absolute smallest step** I can take (under 2 minutes)
2. **Remove all barriers** to starting that step
3. **Set up the environment** for success
4. **Create momentum** with a micro-commitment
Remember: You're not committing to finish, just to start. Starting is often the hardest part.
{{#if tasks_md}}
Tasks I'm avoiding:
{{tasks_md}}
{{/if}}`,
},
],
},
{
title: 'I feel overwhelmed',
prompts: [
{
title: 'Triage & Tame',
template: `Help me triage these tasks and create a 2-hour rescue plan:
**Step 1: Triage into three buckets:**
- **Now** (must be done today, high impact)
- **Next** (important but can wait 24-48 hours)
- **Not Now** (low priority or can be delayed/delegated)
**Step 2: Create a 2-hour rescue plan:**
- Pick ONE task from "Now" bucket
- Break it into 20-minute chunks
- Include 10-minute breaks between chunks
- End with a 15-minute planning session for tomorrow
Focus on progress over perfection.
{{#if tasks_md}}
Tasks:
{{tasks_md}}
{{/if}}`,
},
{
title: 'Stress Audit',
template: `Let's do a quick stress audit to identify what's really driving the overwhelm:
1. **Task overload** - Too many things on my plate?
2. **Time pressure** - Unrealistic deadlines?
3. **Skill gaps** - Missing knowledge or tools?
4. **Decision fatigue** - Too many choices to make?
5. **External pressure** - Others' expectations?
For each factor present, suggest one specific action to reduce its impact this week.
{{#if tasks_md}}
Current tasks and commitments:
{{tasks_md}}
{{/if}}`,
},
],
},
{
title: 'I am not motivated',
prompts: [
{
title: 'Make It Attractive',
template: `Rewrite my tasks to be more motivating and engaging:
For each task, help me:
1. **Add a why-statement** (connect to bigger purpose)
2. **Reframe the outcome** (focus on benefits, not effort)
3. **Suggest a reward** (something I'll enjoy after completion)
4. **Create a 5-minute starter ritual** (make beginning easier)
5. **Find the learning opportunity** (what skill will I develop?)
Transform tasks from chores into opportunities.
{{#if tasks_md}}
Tasks to make attractive:
{{tasks_md}}
{{/if}}`,
},
{
title: 'Motivation Reset',
template: `Help me reset my motivation by reconnecting with my deeper drivers:
1. **Values alignment** - How do these tasks connect to what I care about?
2. **Progress visualization** - What will completing this enable?
3. **Identity reinforcement** - What kind of person does this work?
4. **Energy audit** - When am I most naturally motivated?
5. **Environmental design** - How can I set up my space for success?
Create a personalized motivation plan based on my specific situation.
{{#if tasks_md}}
Current tasks and goals:
{{tasks_md}}
{{/if}}`,
},
],
},
{
title: "I can't focus",
prompts: [
{
title: 'Focus Troubleshoot',
template: `Let's diagnose and fix focus issues systematically:
**Common focus killers:**
- **Digital distractions** (notifications, social media)
- **Mental clutter** (unfinished tasks, decisions)
- **Physical environment** (noise, clutter, comfort)
- **Energy state** (hunger, fatigue, stress)
- **Task design** (too vague, too big, too boring)
For each relevant issue, I'll provide:
1. A **quick fix** for right now
2. A **system** to prevent it recurring
3. A **focus ritual** to get back on track
{{#if tasks_md}}
Tasks requiring focus:
{{tasks_md}}
{{/if}}`,
},
{
title: 'Deep Work Block',
template: `Design a deep work block for maximum focus and productivity:
**Pre-work setup (5 minutes):**
- Clear physical and digital space
- Set specific intention and outcome
- Eliminate distractions and notifications
**Work block structure:**
- 25-45 minute focused sprint
- Clear start and stop signals
- Single task focus (no multitasking)
**Recovery (10 minutes):**
- Note progress and insights
- Physical movement or fresh air
- Prepare for next session
Customize this framework for your specific needs and tasks.
{{#if tasks_md}}
Tasks for deep work:
{{tasks_md}}
{{/if}}`,
},
],
},
{
title: 'Review and reflect',
prompts: [
{
title: 'Daily Review',
template: `Guide me through a productive daily review:
**What happened today:**
1. What did I complete? (celebrate wins, even small ones)
2. What didn't get done? (without judgment, just facts)
3. What surprised me? (unexpected challenges or insights)
**Learning and adjustment:**
4. What worked well? (systems, approaches, decisions to repeat)
5. What would I do differently? (specific adjustments for tomorrow)
6. What patterns do I notice? (energy, focus, productivity trends)
**Tomorrow's setup:**
7. What are my top 3 priorities?
8. What potential obstacles should I prepare for?
{{#if tasks_md}}
Today's tasks and activities:
{{tasks_md}}
{{/if}}`,
},
{
title: 'Weekly Retrospective',
template: `Conduct a comprehensive weekly retrospective:
**Week in review:**
1. **Wins and accomplishments** (what am I proud of?)
2. **Challenges and obstacles** (what was harder than expected?)
3. **Progress toward goals** (am I on track with bigger objectives?)
**Systems and processes:**
4. **What's working well?** (tools, habits, routines to keep)
5. **What needs adjustment?** (pain points to address next week)
6. **What experiments should I try?** (one small thing to test)
**Next week preparation:**
7. **Key outcomes** (what success looks like)
8. **Potential risks** (what could derail progress)
9. **Energy management** (how to maintain sustainable productivity)
{{#if tasks_md}}
This week's tasks and outcomes:
{{tasks_md}}
{{/if}}`,
},
],
},
{
title: 'Prepare for meetings',
prompts: [
{
title: 'Meeting Prep Assistant',
template: `Help me prepare for productive meetings:
**Before the meeting:**
1. **Clear objective** - What specific outcome do we need?
2. **My role** - How can I contribute most effectively?
3. **Key questions** - What do I need to ask or clarify?
4. **Preparation needed** - What should I review or bring?
**During the meeting:**
5. **Focus points** - What are the most important items?
6. **Decision needs** - What requires resolution today?
7. **Action items** - Who does what by when?
**After the meeting:**
8. **Follow-up actions** - What are my next steps?
9. **Communication** - Who needs to be updated?
{{#if tasks_md}}
Meeting context and related tasks:
{{tasks_md}}
{{/if}}`,
},
{
title: 'Difficult Conversation Prep',
template: `Prepare for a challenging conversation with confidence and clarity:
**Preparation framework:**
1. **Objective** - What outcome do I want? (be specific and realistic)
2. **Their perspective** - What might they be thinking/feeling?
3. **Common ground** - Where do our interests align?
4. **Key points** - What are my 2-3 most important messages?
5. **Evidence** - What facts support my position?
6. **Compromise options** - Where can I be flexible?
7. **Worst case plan** - How will I handle pushback or conflict?
Focus on mutual problem-solving rather than winning.
{{#if tasks_md}}
Context and background:
{{tasks_md}}
{{/if}}`,
},
],
},
];

View file

@ -0,0 +1,37 @@
// Simple test for the renderPrompt function - FIXED VERSION
function renderPrompt(template, tasksMd) {
return template.replace(/{{#if tasks_md}}([\s\S]*?){{\/if}}/g, (match, content) => {
if (tasksMd) {
return content.replace(/{{tasks_md}}/g, tasksMd);
}
return '';
});
}
// Test cases
console.log('Test 1 - Without tasks:');
const template1 = 'Hello {{#if tasks_md}}here are tasks: {{tasks_md}}{{/if}} end.';
console.log(renderPrompt(template1));
console.log('\nTest 2 - With tasks:');
console.log(renderPrompt(template1, '- Task 1\n- Task 2'));
console.log('\nTest 3 - Real prompt template:');
const realTemplate = `You are an executive function coach. Given my tasks, help me pick a realistic Top 3 for today.
{{#if tasks_md}}
Here are my tasks:
{{tasks_md}}
{{/if}}`;
console.log('Without tasks:');
console.log(renderPrompt(realTemplate));
console.log('\nWith tasks:');
console.log(
renderPrompt(
realTemplate,
'- [ ] Complete project report\n- [ ] Review PR #123\n- [ ] Meeting with team',
),
);

View file

@ -0,0 +1,35 @@
// Simple test for the renderPrompt function
function renderPrompt(template, tasksMd) {
return template.replace(
/{{#if tasks_md}}([\s\S]*?){{\/if}}/g,
tasksMd ? `$1`.replace(/{{tasks_md}}/g, tasksMd) : '',
);
}
// Test cases
console.log('Test 1 - Without tasks:');
const template1 = 'Hello {{#if tasks_md}}here are tasks: {{tasks_md}}{{/if}} end.';
console.log(renderPrompt(template1));
console.log('\nTest 2 - With tasks:');
console.log(renderPrompt(template1, '- Task 1\n- Task 2'));
console.log('\nTest 3 - Real prompt template:');
const realTemplate = `You are an executive function coach. Given my tasks, help me pick a realistic Top 3 for today.
{{#if tasks_md}}
Here are my tasks:
{{tasks_md}}
{{/if}}`;
console.log('Without tasks:');
console.log(renderPrompt(realTemplate));
console.log('\nWith tasks:');
console.log(
renderPrompt(
realTemplate,
'- [ ] Complete project report\n- [ ] Review PR #123\n- [ ] Meeting with team',
),
);

View file

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "preserve",
"jsxImportSource": "solid-js",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"noEmit": true,
"types": ["vite/client"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,55 @@
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';
import { resolve } from 'path';
import fs from 'fs';
import path from 'path';
export default defineConfig({
base: './',
plugins: [
solidPlugin(),
{
name: 'copy-files',
closeBundle() {
// Copy manifest.json to dist
const manifestSrc = path.resolve(__dirname, 'src/manifest.json');
const manifestDest = path.resolve(__dirname, 'dist/manifest.json');
fs.copyFileSync(manifestSrc, manifestDest);
// Copy icon.svg to dist root
const iconSrc = path.resolve(__dirname, 'src/assets/icon.svg');
const iconDest = path.resolve(__dirname, 'dist/icon.svg');
fs.copyFileSync(iconSrc, iconDest);
// Move index.html from src subdirectory to root
const htmlSrc = path.resolve(__dirname, 'dist/src/index.html');
const htmlDest = path.resolve(__dirname, 'dist/index.html');
if (fs.existsSync(htmlSrc)) {
fs.renameSync(htmlSrc, htmlDest);
// Remove the src directory
fs.rmSync(path.resolve(__dirname, 'dist/src'), {
recursive: true,
force: true,
});
}
},
},
],
build: {
outDir: 'dist',
emptyOutDir: true,
rollupOptions: {
input: {
index: resolve(__dirname, 'src/index.html'),
plugin: resolve(__dirname, 'src/plugin.ts'),
},
output: {
entryFileNames: '[name].js',
chunkFileNames: '[name]-[hash].js',
assetFileNames: '[name].[ext]',
},
},
copyPublicDir: false,
},
publicDir: 'src/assets',
});

View file

@ -94,6 +94,7 @@ export class PluginService implements OnDestroy {
'assets/bundled-plugins/sync-md',
'assets/bundled-plugins/api-test-plugin',
'assets/bundled-plugins/procrastination-buster',
'assets/bundled-plugins/ai-productivity-prompts',
];
// Only load manifests for discovery