✓ Verified 💻 Development ✓ Enhanced Data

Taskflow

Structured project/task management for OpenClaw agents — markdown-first authoring, SQLite-backed que

Rating
4.1 (289 reviews)
Downloads
830 downloads
Version
1.0.0

Overview

Structured project/task management for OpenClaw agents — markdown-first authoring, SQLite-backed querying.

Complete Documentation

View Source →

TaskFlow — Agent Skill Reference

TaskFlow gives any OpenClaw agent a structured project/task/plan system with markdown-first authoring, SQLite-backed querying, and bidirectional sync.

Principle: Markdown is canonical. Edit tasks/*.md directly. The SQLite DB is a derived index, not the source of truth.


Security

OPENCLAW_WORKSPACE Trust Boundary

OPENCLAW_WORKSPACE is a high-trust value. All TaskFlow scripts resolve file paths from it, and the CLI and sync daemon use it to locate the SQLite database, markdown task files, and log directory.

Rules for safe use:

  • Set it only from trusted, controlled sources. The value must come from:
  • Your own shell profile (.zshrc, .bashrc, /etc/environment)
  • The systemd user unit Environment= directive in a template you control
  • The macOS LaunchAgent EnvironmentVariables dictionary you installed
Never accept OPENCLAW_WORKSPACE from:
  • User-supplied CLI arguments or HTTP request parameters
  • Untrusted config files read at runtime
  • Any external input that has not been explicitly validated
  • Validate the path exists before use. Any script that reads OPENCLAW_WORKSPACE should confirm the directory exists before proceeding:
js
import { existsSync } from 'node:fs'
   import path from 'node:path'

   const workspace = process.env.OPENCLAW_WORKSPACE
   if (!workspace) {
     console.error('OPENCLAW_WORKSPACE is not set. Aborting.')
     process.exit(1)
   }
   if (!existsSync(workspace)) {
     console.error(`OPENCLAW_WORKSPACE path does not exist: ${workspace}`)
     process.exit(1)
   }
   // Resolve to absolute path to neutralize any relative-path tricks
   const safeWorkspace = path.resolve(workspace)
  • Do not construct paths from untrusted input. Even with a valid OPENCLAW_WORKSPACE, never concatenate unvalidated user input onto it (e.g. path.join(workspace, userSlug, '../../../etc/passwd')). Use path.resolve() and check that the resolved path starts with the workspace root:
js
function safeJoin(base, ...parts) {
     const resolved = path.resolve(base, ...parts)
     if (!resolved.startsWith(path.resolve(base) + path.sep)) {
       throw new Error(`Path traversal attempt detected: ${resolved}`)
     }
     return resolved
   }
  • Treat OPENCLAW_WORKSPACE as a local system path only. It must point to a directory on the local filesystem. Remote paths (NFS mounts, network shares) may work but are outside the tested configuration and could introduce TOCTOU (time-of-check/time-of-use) race conditions.

Setup

1. Set environment variable

Add to your shell profile (.zshrc, .bashrc, etc.):

bash
export OPENCLAW_WORKSPACE="/path/to/your/.openclaw/workspace"

All TaskFlow scripts and the CLI resolve paths from this variable. Without it, they fall back to process.cwd(), which is almost never what you want.

See also: OPENCLAW_WORKSPACE Trust Boundary above for security requirements.

2. Link the CLI

bash
ln -sf {baseDir}/scripts/taskflow-cli.mjs /opt/homebrew/bin/taskflow  # macOS (Apple Silicon)
# or: ln -sf {baseDir}/scripts/taskflow-cli.mjs /usr/local/bin/taskflow

3. Run the setup wizard

bash
taskflow setup

The wizard handles the rest: creates workspace directories, walks you through adding your first project(s), initializes the database, syncs, and optionally installs the macOS LaunchAgent for periodic sync.

Alternative — manual setup:

Manual steps (if you prefer explicit control)

bash
# Create workspace dirs
mkdir -p "$OPENCLAW_WORKSPACE/tasks" "$OPENCLAW_WORKSPACE/plans" "$OPENCLAW_WORKSPACE/memory" "$OPENCLAW_WORKSPACE/logs"

# Bootstrap the DB schema
taskflow init

# Create PROJECTS.md and tasks/<slug>-tasks.md manually (see templates/)

# Sync markdown → DB
taskflow sync files-to-db

# Verify
taskflow status


First Run

For agents (OpenClaw / AI)

When a user asks you to set up TaskFlow or you detect it has not been initialized:

  • Detect state. Check for $OPENCLAW_WORKSPACE/PROJECTS.md and $OPENCLAW_WORKSPACE/memory/taskflow.sqlite.
  • If clean slate: Ask the user for their first project name and description, then run:
bash
taskflow setup --name "Project Name" --desc "One-liner description"
Follow up by running taskflow status to confirm.
  • If PROJECTS.md exists but no DB: Run taskflow setup (it detects the state automatically and offers to init + sync).
  • If both exist: Run taskflow status — already set up.
  • After setup, update AGENTS.md with the new project slug so future sessions discover it via cat PROJECTS.md.

For humans (CLI)

bash
taskflow setup

The interactive wizard will:

  • Detect your existing workspace state
  • Walk you through naming your first project(s)
  • Create PROJECTS.md and tasks/-tasks.md from templates
  • Initialize the SQLite database and sync
  • Offer to install the periodic-sync daemon (LaunchAgent on macOS, systemd timer on Linux) for automatic 60s sync
Non-interactive (scripted installs):

bash
taskflow setup --name "My Project" --desc "What it does"

Passing --name skips all interactive prompts (daemon install is also skipped in non-interactive mode).


Directory Layout

text
<workspace>/
├── PROJECTS.md                      # Project registry (one ## block per project)
├── tasks/<slug>-tasks.md            # Task list per project
├── plans/<slug>-plan.md             # Optional: architecture/design doc per project
└── taskflow/
    ├── SKILL.md                     # This file
    ├── scripts/
    │   ├── taskflow-cli.mjs         # CLI entry point (symlink target)
    │   ├── task-sync.mjs            # Bidirectional markdown ↔ SQLite sync
    │   ├── init-db.mjs              # Bootstrap SQLite schema (idempotent)
    │   ├── export-projects-overview.mjs  # JSON export of project/task state
    │   └── apple-notes-export.mjs   # Optional: project state → Apple Notes (macOS only)
    ├── templates/                   # Starter files for new projects
    ├── schema/
    │   └── taskflow.sql             # Full DDL
    └── system/
        ├── com.taskflow.sync.plist.xml  # Periodic sync (macOS LaunchAgent)
        ├── taskflow-sync.service        # Periodic sync (Linux systemd user unit)
        └── taskflow-sync.timer          # Systemd timer (60s interval)
<workspace>/
└── taskflow.config.json                 # Apple Notes config (auto-created on first notes run)


Creating a Project

Follow this full checklist when creating a new project:

1. Add a block to PROJECTS.md

markdown
## <slug>
- Name: <Human-Readable Name>
- Status: active
- Description: One-sentence description of the project.
  • slug is lowercase, hyphenated (e.g., my-project). It becomes the canonical project ID everywhere.
  • Valid status values: active, paused, done.

2. Create the task file

Copy taskflow/templates/tasks-template.mdtasks/-tasks.md and update the project name in the heading.

The file must contain these five section headers in this order:

markdown
# <Project Name> — Tasks

## In Progress
## Pending Validation
## Backlog
## Blocked
## Done

3. Optionally create a plan file

Copy taskflow/templates/plan-template.mdplans/-plan.md for architecture docs, design decisions, and phased roadmaps. Plan files are not synced to SQLite — they are reference-only for the agent.

4. DB row (auto-created on first sync)

You do not need to manually insert into the projects table. The sync engine auto-creates the project row from PROJECTS.md on the next files-to-db run. If you want to be explicit via Node.js, use a parameterized statement:

js
// Safe: parameterized insert — no string interpolation in the SQL
db.prepare(`INSERT INTO projects (id, name, description, status)
            VALUES (:id, :name, :description, 'active')`)
  .run({ id: slug, name: projectName, description: projectDesc })


Task Line Format

Every task line follows this exact format:

text
- [x| ] (task:<id>) [<priority>] [<owner>] <title>

FieldDetails
[ ] / [x]Open / completed. Sync drives status from section header, not this checkbox.
(task:)Task ID. Format: -NNN (zero-padded 3-digit). Sequential per project.
[]Required. Must come before owner tag. See priority table below.
[]Optional. Agent/model tag (e.g., codex, sonnet, claude).
</td><td class="px-4 py-2 text-sm text-gray-700">Human-readable task title.</td></tr></tbody></table> <h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">⚠️ Tag Order Rule</h3></p><p class="my-4 text-gray-700 leading-relaxed"><strong class="font-semibold">Priority tag MUST come before owner tag.</strong> The sync parser is positional — it reads the first <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[Px]</code> bracket as priority, and the next <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[tag]</code> as owner. Swapping them will misparse the task.</p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">⚠️ Title Sanitization Rules</h3></p><p class="my-4 text-gray-700 leading-relaxed">Task titles must be <strong class="font-semibold">plain text only</strong>. Before writing any user-supplied string as a task title, apply the following rules: <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-decimal"><strong class="font-semibold">Reject lines that look like section headers.</strong> A title may not start with one or more <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">#</code> characters followed by a space (e.g. <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200"># My heading</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">## Done</code>). These would corrupt the sync parser's section detection.</li> <li class="ml-6 mb-2 text-gray-700 list-decimal"><strong class="font-semibold">Reject the exact section header strings</strong> even without leading whitespace:</li> <li class="ml-6 mb-2 text-gray-700 list-disc"><code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">In Progress</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">Pending Validation</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">Backlog</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">Blocked</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">Done</code></li> <li class="ml-6 mb-2 text-gray-700 list-disc">Comparison must be case-insensitive.</li> <li class="ml-6 mb-2 text-gray-700 list-decimal"><strong class="font-semibold">Escape or strip markdown special characters</strong> that have structural meaning in the task file:</li> </ul> | Character | Risk | Safe action | |-----------|------|-------------| | <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">#</code> | Looks like a header | Strip or reject | | <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">- </code> (dash + space at line start) | Looks like a list item / task | Strip leading <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">- </code> | | <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[ ]</code> / <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[x]</code> | Looks like a checkbox | Escape brackets: <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">\[</code> <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">\]</code> | | <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">]</code> / <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[</code> alone | Can corrupt <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">(task:id)</code> parse | Escape: <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">\[</code> <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">\]</code> | | Newlines (<code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">\n</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">\r</code>) | Creates multi-line titles | Strip / reject | <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-decimal"><strong class="font-semibold">Maximum length.</strong> Titles should be ≤ 200 characters. Truncate or reject longer strings.</li> </ul> <strong class="font-semibold">Example sanitization (Node.js):</strong></p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">js</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-js">// Safe: sanitize a user-supplied task title before writing to markdown function sanitizeTitle(raw) { if (typeof raw !== 'string') throw new TypeError('title must be a string') // Strip newlines let title = raw.replace(/[\r\n]+/g, ' ').trim() // Reject lines that look like section headers (# Heading or bare header words) if (/^#{1,6}\s/.test(title)) { throw new Error('Title may not start with a markdown heading (#)') } const BANNED_HEADERS = /^(in progress|pending validation|backlog|blocked|done)$/i if (BANNED_HEADERS.test(title)) { throw new Error('Title may not be a reserved section header name') } // Escape structural markdown characters title = title .replace(/\[/g, '\\[') .replace(/\]/g, '\\]') // Enforce length limit if (title.length > 200) { throw new Error('Title exceeds 200 character limit') } return title }</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed">These rules apply whenever a task title comes from <strong class="font-semibold">any external or user-supplied source</strong> (CLI args, API payloads, file imports). Titles hard-coded by agents in their own sessions are low-risk but should still avoid structural characters.</p><p class="my-4 text-gray-700 leading-relaxed">✅ Correct: <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">- [ ] (task:myproject-007) [P1] [codex] Implement search</code> ❌ Wrong: <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">- [ ] (task:myproject-007) [codex] [P1] Implement search</code></p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">Priority Levels (Configurable)</h3></p><p class="my-4 text-gray-700 leading-relaxed"><table class="w-full my-6 border-collapse"><thead><tr><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Tag</th><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Default Meaning</th></tr></thead><tbody><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">P0</td><td class="px-4 py-2 text-sm text-gray-700">Critical — must do now, blocks everything</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">P1</td><td class="px-4 py-2 text-sm text-gray-700">High — important, do soon</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">P2</td><td class="px-4 py-2 text-sm text-gray-700">Normal — standard priority (default)</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">P3</td><td class="px-4 py-2 text-sm text-gray-700">Low — nice to have</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">P9</td><td class="px-4 py-2 text-sm text-gray-700">Someday — no urgency, parking lot</td></tr></tbody></table> Priorities are configurable per-installation but the tags themselves (<code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">P0</code>–<code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">P3</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">P9</code>) are what the sync engine validates.</p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">Optional Note Lines</h3></p><p class="my-4 text-gray-700 leading-relaxed">A note can follow a task line as an indented <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">- note:</code> line:</p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">markdown</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-markdown">- [ ] (task:myproject-003) [P1] [codex] Implement auth flow - note: blocked on API key from vendor</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed"><blockquote class="border-l-4 border-primary-300 pl-4 py-2 my-4 text-gray-700 italic bg-primary-50 rounded-r"><strong class="font-semibold">Known limitation (v1):</strong> Notes are one-way. Removing or editing a note in markdown does not propagate to the DB. This is tracked for a post-MVP fix.</blockquote></p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">Example Task File Section</h3></p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">markdown</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-markdown">## In Progress - [ ] (task:myproject-001) [P1] [codex] Wire up OAuth login - note: PR open, needs review ## Backlog - [ ] (task:myproject-002) [P2] Add rate limiting middleware - [ ] (task:myproject-003) [P3] Write integration tests</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed"><hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Adding a New Task</h2> <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-decimal"><strong class="font-semibold">Determine the next ID.</strong> Scan the task file for the highest existing <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200"><slug>-NNN</code> and increment by 1. Or query SQLite using a <strong class="font-semibold">parameterized statement</strong> (never interpolate the slug into SQL strings):</li> </ul> <div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">js</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-js">// Node.js — safe, parameterized const db = new DatabaseSync(dbPath) const row = db .prepare(`SELECT MAX(CAST(SUBSTR(id, LENGTH(:slug) + 2) AS INTEGER)) AS max_seq FROM tasks_v2 WHERE project_id = :slug`) .get({ slug: projectSlug }) const nextSeq = (row.max_seq ?? 0) + 1 const nextId = `${projectSlug}-${String(nextSeq).padStart(3, '0')}`</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed"> > ⚠️ <strong class="font-semibold">Never construct SQL by string interpolation.</strong> Use <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">db.prepare()</code> with named or positional parameters (<code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">?</code> or <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">:name</code>) for all values that come from external input. This applies even for read-only queries. <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-decimal"><strong class="font-semibold">Append the task line</strong> to the correct section (<code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">## Backlog</code> for new work, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">## In Progress</code> if starting immediately).</li> <li class="ml-6 mb-2 text-gray-700 list-decimal"><strong class="font-semibold">Format the line</strong> using the exact format above. No trailing spaces. Priority tag before owner tag.</li> </ul> <hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Updating Task Status</h2></p><p class="my-4 text-gray-700 leading-relaxed"><strong class="font-semibold">Move the task line</strong> from its current section to the target section in the markdown file.</p><p class="my-4 text-gray-700 leading-relaxed"><table class="w-full my-6 border-collapse"><thead><tr><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Target State</th><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Move to Section</th></tr></thead><tbody><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">Started / picked up</td><td class="px-4 py-2 text-sm text-gray-700">## In Progress</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">Needs human review</td><td class="px-4 py-2 text-sm text-gray-700">## Pending Validation</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">Not started yet</td><td class="px-4 py-2 text-sm text-gray-700">## Backlog</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">Waiting on dependency</td><td class="px-4 py-2 text-sm text-gray-700">## Blocked</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">Finished</td><td class="px-4 py-2 text-sm text-gray-700">## Done</td></tr></tbody></table> Also flip the checkbox: <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[ ]</code> for active states, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[x]</code> for <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">Done</code> (and optionally <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">Pending Validation</code>).</p><p class="my-4 text-gray-700 leading-relaxed">The periodic sync (60s) will pick up the change and update SQLite automatically. To force an immediate sync:</p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash">node taskflow/scripts/task-sync.mjs files-to-db</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed"><hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Querying Tasks</h2></p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">Simple: Read the markdown file directly</h3></p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash">cat tasks/<slug>-tasks.md</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed">For a quick in-session view, just read the relevant section.</p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">Advanced: Query SQLite</h3></p><p class="my-4 text-gray-700 leading-relaxed"><blockquote class="border-l-4 border-primary-300 pl-4 py-2 my-4 text-gray-700 italic bg-primary-50 rounded-r">⚠️ <strong class="font-semibold">SQL Safety Rule:</strong> Any query that incorporates a variable value (project slug, task ID, status string, etc.) <strong class="font-semibold">must</strong> use parameterized statements — not string interpolation. The <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">sqlite3</code> CLI examples below use only <strong class="font-semibold">static, hardcoded literal values</strong> and are shown as diagnostic/inspection tools only. For programmatic use, always use the Node.js <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">db.prepare()</code> API with bound parameters.</blockquote></p><p class="my-4 text-gray-700 leading-relaxed">#### sqlite3 CLI (static queries — for manual inspection only)</p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash"># All in-progress tasks across all projects (by priority) # Safe: 'in_progress' is a static literal, not a variable sqlite3 "$OPENCLAW_WORKSPACE/memory/taskflow.sqlite" \ "SELECT id, project_id, priority, title FROM tasks_v2 WHERE status = 'in_progress' ORDER BY priority, project_id;" # Task count by status per project (no variables — safe for CLI) sqlite3 "$OPENCLAW_WORKSPACE/memory/taskflow.sqlite" \ "SELECT project_id, status, COUNT(*) AS count FROM tasks_v2 GROUP BY project_id, status ORDER BY project_id, status;"</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed"><blockquote class="border-l-4 border-primary-300 pl-4 py-2 my-4 text-gray-700 italic bg-primary-50 rounded-r">Do <strong class="font-semibold">not</strong> embed shell variables directly in the SQL string (e.g. <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">WHERE project_id = '$SLUG'</code>). That pattern is SQL injection waiting to happen. Use the Node.js API with parameters instead.</blockquote></p><p class="my-4 text-gray-700 leading-relaxed">#### Node.js API — parameterized queries (required for programmatic use)</p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">js</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-js">import { DatabaseSync } from 'node:sqlite' import path from 'node:path' const dbPath = path.join(process.env.OPENCLAW_WORKSPACE, 'memory', 'taskflow.sqlite') const db = new DatabaseSync(dbPath) db.exec('PRAGMA foreign_keys = ON') // ── Backlog for a specific project ───────────────────────────── // :slug is a named parameter — never interpolated into the SQL string const backlog = db .prepare(`SELECT id, priority, title FROM tasks_v2 WHERE project_id = :slug AND status = 'backlog' ORDER BY priority`) .all({ slug: 'my-project' }) // value bound at runtime, never in SQL string // ── Audit trail for a specific task ──────────────────────────── const transitions = db .prepare(`SELECT from_status, to_status, actor, at FROM task_transitions_v2 WHERE task_id = ? ORDER BY at`) .all('my-project-007') // positional parameter — also safe // ── Write: update task status ─────────────────────────────────── // NEVER: db.exec(`UPDATE tasks_v2 SET status='${newStatus}' WHERE id='${id}'`) // ALWAYS: db.prepare(`UPDATE tasks_v2 SET status = :status, updated_at = datetime('now') WHERE id = :id`) .run({ status: 'done', id: 'my-project-007' })</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">CLI Quick Reference</h3></p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash"># Terminal summary: all projects + task counts by status taskflow status # Add a task in markdown with automatic next ID taskflow add taskflow "Implement quick add command" --priority P1 --owner codex # List current tasks for a project (excludes done by default) taskflow list taskflow taskflow list --project "TaskFlow" --all taskflow list task --status backlog,pending_validation --json # JSON export of full project/task state (for dashboards, integrations) node taskflow/scripts/export-projects-overview.mjs # Detect drift between markdown and DB (exit 1 if mismatch) node taskflow/scripts/task-sync.mjs check # Sync markdown → DB (normal direction; run after editing task files) node taskflow/scripts/task-sync.mjs files-to-db # Sync DB → markdown (run after programmatic DB updates) node taskflow/scripts/task-sync.mjs db-to-files</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">Apple Notes Export (Optional — macOS Only)</h3></p><p class="my-4 text-gray-700 leading-relaxed">TaskFlow can maintain a live Apple Note with your current project status. The note is rendered as rich HTML and written via AppleScript.</p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash"># Push current status to Apple Notes (creates note on first run) taskflow notes</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed">On first run (or during <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">taskflow setup</code>), a new note is created in the configured folder and its Core Data ID is saved to:</p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">text</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-text">$OPENCLAW_WORKSPACE/taskflow.config.json</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed">Config schema:</p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">json</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-json">{ "appleNotesId": "x-coredata://...", "appleNotesFolder": "Notes", "appleNotesTitle": "TaskFlow - Project Status" }</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed"><strong class="font-semibold">Important — never delete the shared note.</strong> The note is always edited in-place. Deleting and recreating it generates a new Core Data ID and breaks any existing share links. If the note is accidentally deleted, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">taskflow notes</code> will create a new one and update the config automatically.</p><p class="my-4 text-gray-700 leading-relaxed">For hourly auto-refresh, add a cron entry:</p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash"># Run: crontab -e 0 * * * * OPENCLAW_WORKSPACE=/path/to/workspace /path/to/node /path/to/taskflow/scripts/apple-notes-export.mjs</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed">Or install a dedicated LaunchAgent (macOS) targeting <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">apple-notes-export.mjs</code> with an hourly <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">StartInterval</code> of <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">3600</code>.</p><p class="my-4 text-gray-700 leading-relaxed">This feature is entirely optional and macOS-specific. On other platforms, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">taskflow notes</code> exits gracefully with a message.</p><p class="my-4 text-gray-700 leading-relaxed"><hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Memory Integration Rules</h2></p><p class="my-4 text-gray-700 leading-relaxed">These rules keep daily memory logs clean and prevent duplication.</p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">✅ Do</h3> <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-disc">Reference task IDs in daily memory logs when you complete or advance work:</li> </ul> <div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">text</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-text">Completed `myproject-007` (OAuth login). Moved `myproject-008` to In Progress.</code></pre> </div> <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-disc">Keep memory entries narrative — what happened, what you decided, what's next.</li> </ul> <h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">❌ Do Not</h3> <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-disc"><strong class="font-semibold">Never duplicate the backlog in daily memory files.</strong> <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">tasks/<slug>-tasks.md</code> is the single source of truth for all pending work. Memory files should not list what's left to do.</li> <li class="ml-6 mb-2 text-gray-700 list-disc">Do not track task state changes in memory (e.g., "Task 007 is now in progress"). Only note meaningful progress events or decisions.</li> <li class="ml-6 mb-2 text-gray-700 list-disc">Do not create new tasks in memory files. Add them to the task file directly.</li> </ul> <h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">Pattern: Loading Project Context</h3></p><p class="my-4 text-gray-700 leading-relaxed">At the start of a session involving a project: <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-decimal"><code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">cat PROJECTS.md</code> — identify the project slug and status</li> <li class="ml-6 mb-2 text-gray-700 list-decimal"><code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">cat tasks/<slug>-tasks.md</code> — load current task state</li> <li class="ml-6 mb-2 text-gray-700 list-decimal"><code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">cat plans/<slug>-plan.md</code> — load architecture context (if it exists)</li> <li class="ml-6 mb-2 text-gray-700 list-decimal">Begin work. Record task ID references in memory at session end.</li> </ul> <hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Periodic Sync Daemon</h2></p><p class="my-4 text-gray-700 leading-relaxed">The sync daemon runs <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">task-sync.mjs files-to-db</code> every <strong class="font-semibold">60 seconds</strong> in the background. This means markdown edits are automatically reflected in SQLite within a minute. <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-disc">Logs: <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">logs/taskflow-sync.stdout.log</code> and <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">logs/taskflow-sync.stderr.log</code> (relative to workspace)</li> <li class="ml-6 mb-2 text-gray-700 list-disc">Lock: Advisory TTL lock in <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">sync_state</code> table prevents concurrent syncs</li> <li class="ml-6 mb-2 text-gray-700 list-disc">Conflict resolution: Last-write-wins per sync direction</li> </ul> <h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">Quickest install (auto-detects OS)</h3></p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash">taskflow install-daemon</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed">This detects your platform and installs the appropriate unit. On macOS it installs and loads the LaunchAgent; on Linux it writes systemd user units and enables the timer.</p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">macOS — LaunchAgent (manual steps)</h3></p><p class="my-4 text-gray-700 leading-relaxed">Templates: <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">taskflow/system/com.taskflow.sync.plist.xml</code> <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-decimal">Copy <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">taskflow/system/com.taskflow.sync.plist.xml</code> → <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">~/Library/LaunchAgents/com.taskflow.sync.plist</code></li> <li class="ml-6 mb-2 text-gray-700 list-decimal">Replace <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">{{workspace}}</code> with the absolute path to your workspace (no trailing slash)</li> <li class="ml-6 mb-2 text-gray-700 list-decimal">Replace <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">{{node}}</code> with the path to your <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">node</code> binary (<code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">which node</code>)</li> <li class="ml-6 mb-2 text-gray-700 list-decimal">Load: <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">launchctl load ~/Library/LaunchAgents/com.taskflow.sync.plist</code></li> <li class="ml-6 mb-2 text-gray-700 list-decimal">Verify: <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">launchctl list | grep taskflow</code></li> </ul> Uninstall: <div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash">launchctl unload ~/Library/LaunchAgents/com.taskflow.sync.plist rm ~/Library/LaunchAgents/com.taskflow.sync.plist</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">Linux — systemd user timer (manual steps)</h3></p><p class="my-4 text-gray-700 leading-relaxed">Templates: <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">taskflow/system/taskflow-sync.service</code> and <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">taskflow/system/taskflow-sync.timer</code></p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash"># Create the user unit directory mkdir -p ~/.config/systemd/user # Copy templates, replacing placeholders sed -e "s|{{workspace}}|$OPENCLAW_WORKSPACE|g" \ -e "s|{{node}}|$(which node)|g" \ taskflow/system/taskflow-sync.service > ~/.config/systemd/user/taskflow-sync.service sed -e "s|{{workspace}}|$OPENCLAW_WORKSPACE|g" \ -e "s|{{node}}|$(which node)|g" \ taskflow/system/taskflow-sync.timer > ~/.config/systemd/user/taskflow-sync.timer # Enable and start systemctl --user daemon-reload systemctl --user enable --now taskflow-sync.timer</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed">Verify: <div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash">systemctl --user status taskflow-sync.timer journalctl --user -u taskflow-sync.service</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed">Uninstall: <div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash">systemctl --user disable --now taskflow-sync.timer rm ~/.config/systemd/user/taskflow-sync.{service,timer} systemctl --user daemon-reload</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed"><blockquote class="border-l-4 border-primary-300 pl-4 py-2 my-4 text-gray-700 italic bg-primary-50 rounded-r"><strong class="font-semibold">Note:</strong> systemd user units require a login session. To run them without an interactive session (e.g. on a server), enable lingering: <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">loginctl enable-linger $USER</code></blockquote></p><p class="my-4 text-gray-700 leading-relaxed"><hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Section Header → DB Status Map</h2></p><p class="my-4 text-gray-700 leading-relaxed"><table class="w-full my-6 border-collapse"><thead><tr><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Markdown Header</th><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">DB <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">status</code> value</th></tr></thead><tbody><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">## In Progress</td><td class="px-4 py-2 text-sm text-gray-700">in_progress</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">## Pending Validation</td><td class="px-4 py-2 text-sm text-gray-700">pending_validation</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">## Backlog</td><td class="px-4 py-2 text-sm text-gray-700">backlog</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">## Blocked</td><td class="px-4 py-2 text-sm text-gray-700">blocked</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">## Done</td><td class="px-4 py-2 text-sm text-gray-700">done</td></tr></tbody></table> <strong class="font-semibold">Section headers are fixed.</strong> Do not rename them. The sync parser maps these exact strings.</p><p class="my-4 text-gray-700 leading-relaxed"><hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Known Quirks</h2></p><p class="my-4 text-gray-700 leading-relaxed">Things that work but might trip you up: <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-disc"><strong class="font-semibold"><code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">MAX(id)</code> is lexicographic.</strong> Task IDs are text, so <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">SELECT MAX(id)</code> works only because IDs are zero-padded (<code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">-001</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">-002</code>). If you create <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">-1</code> instead of <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">-001</code>, sequencing breaks. Always zero-pad to 3 digits.</li> <li class="ml-6 mb-2 text-gray-700 list-disc"><strong class="font-semibold">Checkbox state is decorative.</strong> Status comes from which <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">##</code> section a task lives under, not whether it's <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[x]</code> or <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[ ]</code>. The sync engine ignores the checkbox on read. On write-back, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">done</code> tasks get <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[x]</code>, everything else gets <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[ ]</code>.</li> <li class="ml-6 mb-2 text-gray-700 list-disc"><strong class="font-semibold">Notes survive deletion.</strong> If you remove a <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">- note:</code> line from markdown, the old note stays in the DB (COALESCE preserves it). This is intentional for v1 -- notes are one-way display. To truly clear a note, update the DB directly.</li> <li class="ml-6 mb-2 text-gray-700 list-disc"><strong class="font-semibold">Lock TTL is 60 seconds.</strong> If a sync crashes without releasing the lock, the next run will be blocked for up to 60s. The SIGTERM/SIGINT handlers try to clean up, but a <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">kill -9</code> won't. The lock auto-expires.</li> <li class="ml-6 mb-2 text-gray-700 list-disc"><strong class="font-semibold">Auto-project creation derives names from slugs.</strong> If sync encounters a task file with no matching <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">projects</code> row, it creates one with a name like "My Project" from slug "my-project". The name might not be what you want -- fix it in PROJECTS.md and re-sync.</li> <li class="ml-6 mb-2 text-gray-700 list-disc"><strong class="font-semibold">Tag order is strict.</strong> <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[P1] [codex]</code> works. <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">[codex] [P1]</code> silently assigns <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">codex</code> as... nothing useful. Priority tag must come first.</li> </ul> <hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Known Limitations (v1)</h2> <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-disc">Notes are one-way (markdown → DB). Removing a note in markdown does not clear it in DB.</li> <li class="ml-6 mb-2 text-gray-700 list-disc"><code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">db-to-files</code> rewrites all project task files, even unchanged ones.</li> <li class="ml-6 mb-2 text-gray-700 list-disc">One task file per project (1:1 mapping). Multiple files per project is post-MVP.</li> <li class="ml-6 mb-2 text-gray-700 list-disc">Periodic sync daemon: macOS (LaunchAgent) and Linux (systemd user timer) are supported. Run <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">taskflow install-daemon</code> to install.</li> <li class="ml-6 mb-2 text-gray-700 list-disc">Node.js 22.5+ required (<code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">node:sqlite</code>). No Python fallback in v1.</li> </ul> <hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Quick Cheat Sheet</h2></p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">text</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-text">New project: PROJECTS.md block + tasks/<slug>-tasks.md + optional plans/<slug>-plan.md New task: taskflow add <project> "title" (or append manually to section) Update status: Move line to correct ## section, flip checkbox if needed Query simple: cat tasks/<slug>-tasks.md Query complex: Use db.prepare('SELECT ... WHERE id = ?').all(id) — never interpolate variables into SQL CLI status: taskflow status CLI add: taskflow add dashboard "Fix cron panel" --priority P1 --owner codex Force sync: node taskflow/scripts/task-sync.mjs files-to-db Memory rule: Reference IDs in logs; never copy backlog into memory</code></pre> </div> </p></div> </div> </div> <!-- Installation --> <section class="card" aria-labelledby="installation-heading" data-astro-cid-jrlgpo3w> <h2 id="installation-heading" class="text-2xl font-bold text-gray-900 mb-4" data-astro-cid-jrlgpo3w>Installation</h2> <div class="code-block-container relative group " data-astro-cid-i4kugh4e> <div class="flex items-center justify-between px-4 py-2 bg-gray-800 border-b border-gray-700 rounded-t-lg" data-astro-cid-i4kugh4e> <span class="text-sm text-gray-300 font-mono" data-astro-cid-i4kugh4e>Terminal</span> <span class="text-xs text-gray-500 uppercase" data-astro-cid-i4kugh4e>bash</span> </div> <div class="relative" data-astro-cid-i4kugh4e> <pre class="!mt-0 !rounded-t-none" data-astro-cid-i4kugh4e><code id="code-gylhsnicy" class="language-bash" data-astro-cid-i4kugh4e> openclaw install taskflow </code></pre> <!-- Copy Button --> <button class="copy-button absolute top-2 right-2 p-2 bg-gray-700 hover:bg-gray-600 text-gray-300 rounded-lg transition-all duration-200 opacity-0 group-hover:opacity-100 focus:opacity-100" data-code-id="code-gylhsnicy" aria-label="Copy code to clipboard" data-astro-cid-i4kugh4e> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" data-astro-cid-i4kugh4e> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" data-astro-cid-i4kugh4e></path> </svg> <span class="copy-text sr-only" data-astro-cid-i4kugh4e>Copy</span> </button> <!-- Copied Indicator --> <div class="copied-indicator absolute top-2 right-14 px-3 py-1.5 bg-green-600 text-white text-sm rounded-lg opacity-0 transition-opacity duration-200 pointer-events-none" data-astro-cid-i4kugh4e> Copied! </div> </div> </div> <script>(function(){const codeId = "code-gylhsnicy"; const copyButton = document.querySelector(`[data-code-id="${codeId}"]`); const copiedIndicator = document.querySelector('.copied-indicator'); if (copyButton) { copyButton.addEventListener('click', async () => { const codeElement = document.getElementById(codeId); const code = codeElement?.textContent || ''; try { await navigator.clipboard.writeText(code); // Show copied state copyButton.classList.add('copied'); copiedIndicator?.classList.remove('opacity-0'); // Reset after 2 seconds setTimeout(() => { copyButton.classList.remove('copied'); copiedIndicator?.classList.add('opacity-0'); }, 2000); } catch (err) { console.error('Failed to copy code:', err); } }); } })();</script> </section> <!-- Verification Command (if available from enhanced data) --> <!-- Code Examples (if available) --> <div class="card" data-astro-cid-cpq6uwpo><h2 class="text-2xl font-bold text-gray-900 mb-6 flex items-center" data-astro-cid-cpq6uwpo><span class="text-2xl mr-2" data-astro-cid-cpq6uwpo>💻</span>Code Examples</h2><div class="space-y-6" data-astro-cid-cpq6uwpo><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>export OPENCLAW_WORKSPACE="/path/to/your/.openclaw/workspace"</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>export-openclawworkspacepathtoyouropenclawworkspace.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>All TaskFlow scripts and the CLI resolve paths from this variable. Without it, they fall back to `process.cwd()`, which is almost never what you want. > **See also:** [OPENCLAW_WORKSPACE Trust Boundary](#openclaw_workspace-trust-boundary) above for security requirements. ### 2. Link the CLI</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>taskflow setup</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>taskflow-setup.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>The wizard handles the rest: creates workspace directories, walks you through adding your first project(s), initializes the database, syncs, and optionally installs the macOS LaunchAgent for periodic sync. **Alternative — manual setup:** <details> <summary>Manual steps (if you prefer explicit control)</summary></code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>taskflow status</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>taskflow-status.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo></details> --- ## First Run ### For agents (OpenClaw / AI) When a user asks you to set up TaskFlow or you detect it has not been initialized: 1. **Detect state.** Check for `$OPENCLAW_WORKSPACE/PROJECTS.md` and `$OPENCLAW_WORKSPACE/memory/taskflow.sqlite`. 2. **If clean slate:** Ask the user for their first project name and description, then run:</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>taskflow setup</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>taskflow-setup.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>The interactive wizard will: - Detect your existing workspace state - Walk you through naming your first project(s) - Create `PROJECTS.md` and `tasks/<slug>-tasks.md` from templates - Initialize the SQLite database and sync - Offer to install the periodic-sync daemon (LaunchAgent on macOS, systemd timer on Linux) for automatic 60s sync **Non-interactive (scripted installs):**</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>taskflow setup --name "My Project" --desc "What it does"</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>taskflow-setup---name-my-project---desc-what-it-does.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>Passing `--name` skips all interactive prompts (daemon install is also skipped in non-interactive mode). --- ## Directory Layout</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>└── taskflow.config.json # Apple Notes config (auto-created on first notes run)</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>-taskflowconfigjson--apple-notes-config-auto-created-on-first-notes-run.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>--- ## Creating a Project Follow this full checklist when creating a new project: ### 1. Add a block to `PROJECTS.md`</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>- Description: One-sentence description of the project.</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>--description-one-sentence-description-of-the-project.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>- `slug` is lowercase, hyphenated (e.g., `my-project`). It becomes the canonical project ID everywhere. - Valid status values: `active`, `paused`, `done`. ### 2. Create the task file Copy `taskflow/templates/tasks-template.md` → `tasks/<slug>-tasks.md` and update the project name in the heading. The file **must** contain these five section headers in this order:</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>## Done</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>-done.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>### 3. Optionally create a plan file Copy `taskflow/templates/plan-template.md` → `plans/<slug>-plan.md` for architecture docs, design decisions, and phased roadmaps. Plan files are **not** synced to SQLite — they are reference-only for the agent. ### 4. DB row (auto-created on first sync) You do **not** need to manually insert into the `projects` table. The sync engine auto-creates the project row from `PROJECTS.md` on the next `files-to-db` run. If you want to be explicit via Node.js, use a parameterized statement:</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>.run({ id: slug, name: projectName, description: projectDesc })</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>-run-id-slug-name-projectname-description-projectdesc-.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>--- ## Task Line Format Every task line follows this exact format:</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>- [x| ] (task:<id>) [<priority>] [<owner>] <title></h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>--x--taskid-priority-owner-title.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>| Field | Details | |---|---| | `[ ]` / `[x]` | Open / completed. Sync drives status from section header, not this checkbox. | | `(task:<id>)` | Task ID. Format: `<slug>-NNN` (zero-padded 3-digit). Sequential per project. | | `[<priority>]` | **Required. Must come before owner tag.** See priority table below. | | `[<owner>]` | Optional. Agent/model tag (e.g., `codex`, `sonnet`, `claude`). | | `<title>` | Human-readable task title. | ### ⚠️ Tag Order Rule **Priority tag MUST come before owner tag.** The sync parser is positional — it reads the first `[Px]` bracket as priority, and the next `[tag]` as owner. Swapping them will misparse the task. ### ⚠️ Title Sanitization Rules Task titles must be **plain text only**. Before writing any user-supplied string as a task title, apply the following rules: 1. **Reject lines that look like section headers.** A title may not start with one or more `#` characters followed by a space (e.g. `# My heading`, `## Done`). These would corrupt the sync parser's section detection. 2. **Reject the exact section header strings** even without leading whitespace: - `In Progress`, `Pending Validation`, `Backlog`, `Blocked`, `Done` - Comparison must be case-insensitive. 3. **Escape or strip markdown special characters** that have structural meaning in the task file: | Character | Risk | Safe action | |-----------|------|-------------| | `#` | Looks like a header | Strip or reject | | `- ` (dash + space at line start) | Looks like a list item / task | Strip leading `- ` | | `[ ]` / `[x]` | Looks like a checkbox | Escape brackets: `\[` `\]` | | `]` / `[` alone | Can corrupt `(task:id)` parse | Escape: `\[` `\]` | | Newlines (`\n`, `\r`) | Creates multi-line titles | Strip / reject | 4. **Maximum length.** Titles should be ≤ 200 characters. Truncate or reject longer strings. **Example sanitization (Node.js):**</code></pre></div></div></div></div> <!-- Configuration Table (if available) --> <!-- Requirements --> <!-- Code Example --> <!-- Testing Notes --> <!-- Tags --> <div class="card" data-astro-cid-jrlgpo3w> <h2 class="text-2xl font-bold text-gray-900 mb-4" data-astro-cid-jrlgpo3w>Tags</h2> <div class="flex flex-wrap gap-2" data-astro-cid-jrlgpo3w> <span class="px-3 py-1.5 text-sm text-gray-600 bg-gray-50 rounded-lg border border-gray-200" data-astro-cid-jrlgpo3w> #coding_agents-and-ides </span> </div> </div> </div> <!-- Sidebar --> <div class="space-y-6" data-astro-cid-jrlgpo3w> <!-- Quick Info --> <div class="card" data-astro-cid-jrlgpo3w> <h3 class="font-semibold text-gray-900 mb-4" data-astro-cid-jrlgpo3w>Quick Info</h3> <div class="space-y-3 text-sm" data-astro-cid-jrlgpo3w> <div class="flex justify-between" data-astro-cid-jrlgpo3w> <span class="text-gray-600" data-astro-cid-jrlgpo3w>Category</span> <span class="font-medium text-gray-900" data-astro-cid-jrlgpo3w>Development</span> </div> <div class="flex justify-between" data-astro-cid-jrlgpo3w> <span class="text-gray-600" data-astro-cid-jrlgpo3w>Model</span> <span class="font-medium text-gray-900" data-astro-cid-jrlgpo3w>Claude 3.5</span> </div> <div class="flex justify-between" data-astro-cid-jrlgpo3w> <span class="text-gray-600" data-astro-cid-jrlgpo3w>Complexity</span> <span class="font-medium text-gray-900" data-astro-cid-jrlgpo3w>Multi-Agent</span> </div> <div class="flex justify-between" data-astro-cid-jrlgpo3w> <span class="text-gray-600" data-astro-cid-jrlgpo3w>Author</span> <span class="font-medium text-gray-900" data-astro-cid-jrlgpo3w>sm0ls</span> </div> <div class="flex justify-between" data-astro-cid-jrlgpo3w> <span class="text-gray-600" data-astro-cid-jrlgpo3w>Last Updated</span> <span class="font-medium text-gray-900" data-astro-cid-jrlgpo3w>3/10/2026</span> </div> </div> </div> <!-- Model Badge --> <div class="card bg-gradient-to-br from-blue-50 to-indigo-50 border-blue-100" data-astro-cid-jrlgpo3w> <div class="flex items-center justify-between" data-astro-cid-jrlgpo3w> <div class="flex items-center space-x-3" data-astro-cid-jrlgpo3w> <div class="text-3xl" data-astro-cid-jrlgpo3w>🚀</div> <div data-astro-cid-jrlgpo3w> <div class="text-sm text-gray-600" data-astro-cid-jrlgpo3w>Optimized for</div> <div class="font-semibold text-gray-900" data-astro-cid-jrlgpo3w>Claude 3.5</div> </div> </div> <span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg bg-purple-50 text-purple-700 border border-purple-200 hover:bg-purple-100 transition-colors" title="Optimized for Claude 3.5 Sonnet" data-astro-cid-3xlfahoe> <span data-astro-cid-3xlfahoe>🧠</span> </span> </div> </div> <!-- Install CTA --> <div class="card bg-gradient-to-br from-primary-500 to-primary-600 text-white border-0" data-astro-cid-jrlgpo3w> <h3 class="font-semibold text-lg mb-2" data-astro-cid-jrlgpo3w>Ready to Install?</h3> <p class="text-primary-100 text-sm mb-4" data-astro-cid-jrlgpo3w> Get started with this skill in seconds </p> <div class="bg-white/20 backdrop-blur-sm rounded-lg p-3 font-mono text-sm" data-astro-cid-jrlgpo3w> openclaw install taskflow </div> </div> <!-- External Links (if available) --> <div class="card" data-astro-cid-jrlgpo3w> <h3 class="font-semibold text-gray-900 mb-4" data-astro-cid-jrlgpo3w>Resources</h3> <div class="space-y-3" data-astro-cid-jrlgpo3w> <a href="https://github.com/openclaw/skills/tree/main/skills/sm0ls/taskflow/SKILL.md" target="_blank" rel="noopener noreferrer" class="flex items-center justify-between p-3 rounded-lg border border-gray-200 hover:border-primary-300 hover:bg-primary-50 transition-colors" data-astro-cid-jrlgpo3w> <div class="flex items-center space-x-3" data-astro-cid-jrlgpo3w> <div class="text-2xl" data-astro-cid-jrlgpo3w>📂</div> <div data-astro-cid-jrlgpo3w> <div class="font-medium text-gray-900" data-astro-cid-jrlgpo3w>OpenClaw Skills</div> <div class="text-xs text-gray-500" data-astro-cid-jrlgpo3w>View on OpenClaw GitHub</div> </div> </div> <span class="text-primary-600" data-astro-cid-jrlgpo3w>→</span> </a> </div> </div> </div> </div> </div> </section> <section class="section bg-gray-50" data-astro-cid-jrlgpo3w> <div class="container" data-astro-cid-jrlgpo3w> <h2 class="text-2xl font-bold text-gray-900 mb-6" data-astro-cid-jrlgpo3w>Related Skills</h2> <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6" data-astro-cid-jrlgpo3w> <a href="/skills/4claw/" class="skill-card group block" data-astro-cid-rwasicqo> <!-- Header --> <div class="flex items-start justify-between mb-4" data-astro-cid-rwasicqo> <div class="flex-1" data-astro-cid-rwasicqo> <div class="flex items-center space-x-2 mb-2" data-astro-cid-rwasicqo> <span class="inline-flex items-center px-2 py-0.5 text-xs font-semibold text-green-700 bg-green-50 rounded-full border border-green-200" data-astro-cid-rwasicqo> ✓ Verified </span> <span class="inline-flex items-center px-2 py-0.5 text-xs font-medium text-gray-600 bg-gray-100 rounded-full" data-astro-cid-rwasicqo> 💻 Development </span> </div> <h3 class="text-lg font-semibold text-gray-900 group-hover:text-primary-600 transition-colors" data-astro-cid-rwasicqo> 4claw </h3> </div> </div> <!-- Description --> <p class="text-sm text-gray-600 mb-4 line-clamp-2" data-astro-cid-rwasicqo> 4claw — a moderated imageboard for AI agents. </p> <!-- Badges --> <div class="flex flex-wrap gap-2 mb-4" data-astro-cid-rwasicqo> <span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg bg-purple-50 text-purple-700 border border-purple-200 hover:bg-purple-100 transition-colors" title="Optimized for Claude 3.5 Sonnet" data-astro-cid-3xlfahoe> <span data-astro-cid-3xlfahoe>🧠</span> <span data-astro-cid-3xlfahoe>Claude-Ready</span> </span> <span class="inline-flex items-center px-2.5 py-1 text-xs font-medium rounded-lg class={complexityInfo.level === 1 ? 'text-green-700 bg-green-50' : complexityInfo.level === 2 ? 'text-blue-700 bg-blue-50' : 'text-purple-700 bg-purple-50'}> {complexityInfo.level === 1 ? '⚡' : complexityInfo.level === 2 ? '🔗' : '🧠'} {complexityInfo.name} </span> </div> <!-- Tags --> {skill.tags.length > 0 && ( <div class=" flex flex-wrap gap-1.5 mb-4" data-astro-cid-rwasicqo> <span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #ai_and-llms </span> </span></div> )} <!-- Footer Stats --> <div class="flex items-center justify-between pt-4 border-t border-gray-100" data-astro-cid-rwasicqo> <div class="flex items-center space-x-4 text-sm text-gray-500" data-astro-cid-rwasicqo> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span class="text-yellow-500" data-astro-cid-rwasicqo>★</span> <span class="font-medium" data-astro-cid-rwasicqo>4.4</span> <span class="text-xs" data-astro-cid-rwasicqo>(118)</span> </div> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span data-astro-cid-rwasicqo>↓</span> <span class="font-medium" data-astro-cid-rwasicqo>4,990</span> </div> </div> <div class="text-xs text-gray-400" data-astro-cid-rwasicqo> v1.0.0 </div> </div> </a> <a href="/skills/aap-passport/" class="skill-card group block" data-astro-cid-rwasicqo> <!-- Header --> <div class="flex items-start justify-between mb-4" data-astro-cid-rwasicqo> <div class="flex-1" data-astro-cid-rwasicqo> <div class="flex items-center space-x-2 mb-2" data-astro-cid-rwasicqo> <span class="inline-flex items-center px-2 py-0.5 text-xs font-semibold text-green-700 bg-green-50 rounded-full border border-green-200" data-astro-cid-rwasicqo> ✓ Verified </span> <span class="inline-flex items-center px-2 py-0.5 text-xs font-medium text-gray-600 bg-gray-100 rounded-full" data-astro-cid-rwasicqo> 💻 Development </span> </div> <h3 class="text-lg font-semibold text-gray-900 group-hover:text-primary-600 transition-colors" data-astro-cid-rwasicqo> Aap Passport </h3> </div> </div> <!-- Description --> <p class="text-sm text-gray-600 mb-4 line-clamp-2" data-astro-cid-rwasicqo> Agent Attestation Protocol - The Reverse Turing Test. </p> <!-- Badges --> <div class="flex flex-wrap gap-2 mb-4" data-astro-cid-rwasicqo> <span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg bg-purple-50 text-purple-700 border border-purple-200 hover:bg-purple-100 transition-colors" title="Optimized for Claude 3.5 Sonnet" data-astro-cid-3xlfahoe> <span data-astro-cid-3xlfahoe>🧠</span> <span data-astro-cid-3xlfahoe>Claude-Ready</span> </span> <span class="inline-flex items-center px-2.5 py-1 text-xs font-medium rounded-lg class={complexityInfo.level === 1 ? 'text-green-700 bg-green-50' : complexityInfo.level === 2 ? 'text-blue-700 bg-blue-50' : 'text-purple-700 bg-purple-50'}> {complexityInfo.level === 1 ? '⚡' : complexityInfo.level === 2 ? '🔗' : '🧠'} {complexityInfo.name} </span> </div> <!-- Tags --> {skill.tags.length > 0 && ( <div class=" flex flex-wrap gap-1.5 mb-4" data-astro-cid-rwasicqo> <span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #ai_and-llms </span> </span></div> )} <!-- Footer Stats --> <div class="flex items-center justify-between pt-4 border-t border-gray-100" data-astro-cid-rwasicqo> <div class="flex items-center space-x-4 text-sm text-gray-500" data-astro-cid-rwasicqo> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span class="text-yellow-500" data-astro-cid-rwasicqo>★</span> <span class="font-medium" data-astro-cid-rwasicqo>4.3</span> <span class="text-xs" data-astro-cid-rwasicqo>(89)</span> </div> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span data-astro-cid-rwasicqo>↓</span> <span class="font-medium" data-astro-cid-rwasicqo>4,621</span> </div> </div> <div class="text-xs text-gray-400" data-astro-cid-rwasicqo> v1.0.0 </div> </div> </a> <a href="/skills/acestep-lyrics-transcription/" class="skill-card group block" data-astro-cid-rwasicqo> <!-- Header --> <div class="flex items-start justify-between mb-4" data-astro-cid-rwasicqo> <div class="flex-1" data-astro-cid-rwasicqo> <div class="flex items-center space-x-2 mb-2" data-astro-cid-rwasicqo> <span class="inline-flex items-center px-2 py-0.5 text-xs font-semibold text-green-700 bg-green-50 rounded-full border border-green-200" data-astro-cid-rwasicqo> ✓ Verified </span> <span class="inline-flex items-center px-2 py-0.5 text-xs font-medium text-gray-600 bg-gray-100 rounded-full" data-astro-cid-rwasicqo> 💻 Development </span> </div> <h3 class="text-lg font-semibold text-gray-900 group-hover:text-primary-600 transition-colors" data-astro-cid-rwasicqo> Acestep Lyrics Transcription </h3> </div> </div> <!-- Description --> <p class="text-sm text-gray-600 mb-4 line-clamp-2" data-astro-cid-rwasicqo> Transcribe audio to timestamped lyrics using OpenAI Whisper or ElevenLabs Scribe API. </p> <!-- Badges --> <div class="flex flex-wrap gap-2 mb-4" data-astro-cid-rwasicqo> <span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg bg-green-50 text-emerald-700 border border-emerald-200 hover:bg-green-100 transition-colors" title="Works best with GPT-4 and GPT-4 Turbo" data-astro-cid-3xlfahoe> <span data-astro-cid-3xlfahoe>⚡</span> <span data-astro-cid-3xlfahoe>GPT-Optimized</span> </span> <span class="inline-flex items-center px-2.5 py-1 text-xs font-medium rounded-lg class={complexityInfo.level === 1 ? 'text-green-700 bg-green-50' : complexityInfo.level === 2 ? 'text-blue-700 bg-blue-50' : 'text-purple-700 bg-purple-50'}> {complexityInfo.level === 1 ? '⚡' : complexityInfo.level === 2 ? '🔗' : '🧠'} {complexityInfo.name} </span> </div> <!-- Tags --> {skill.tags.length > 0 && ( <div class=" flex flex-wrap gap-1.5 mb-4" data-astro-cid-rwasicqo> <span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #ai_and-llms </span><span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #api </span><span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #script </span> </span></div> )} <!-- Footer Stats --> <div class="flex items-center justify-between pt-4 border-t border-gray-100" data-astro-cid-rwasicqo> <div class="flex items-center space-x-4 text-sm text-gray-500" data-astro-cid-rwasicqo> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span class="text-yellow-500" data-astro-cid-rwasicqo>★</span> <span class="font-medium" data-astro-cid-rwasicqo>3.8</span> <span class="text-xs" data-astro-cid-rwasicqo>(274)</span> </div> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span data-astro-cid-rwasicqo>↓</span> <span class="font-medium" data-astro-cid-rwasicqo>17,648</span> </div> </div> <div class="text-xs text-gray-400" data-astro-cid-rwasicqo> v1.0.0 </div> </div> </a> <a href="/skills/adaptive-suite/" class="skill-card group block" data-astro-cid-rwasicqo> <!-- Header --> <div class="flex items-start justify-between mb-4" data-astro-cid-rwasicqo> <div class="flex-1" data-astro-cid-rwasicqo> <div class="flex items-center space-x-2 mb-2" data-astro-cid-rwasicqo> <span class="inline-flex items-center px-2 py-0.5 text-xs font-semibold text-green-700 bg-green-50 rounded-full border border-green-200" data-astro-cid-rwasicqo> ✓ Verified </span> <span class="inline-flex items-center px-2 py-0.5 text-xs font-medium text-gray-600 bg-gray-100 rounded-full" data-astro-cid-rwasicqo> 💻 Development </span> </div> <h3 class="text-lg font-semibold text-gray-900 group-hover:text-primary-600 transition-colors" data-astro-cid-rwasicqo> Adaptive Suite </h3> </div> </div> <!-- Description --> <p class="text-sm text-gray-600 mb-4 line-clamp-2" data-astro-cid-rwasicqo> A continuously adaptive skill suite that empowers Clawdbot. </p> <!-- Badges --> <div class="flex flex-wrap gap-2 mb-4" data-astro-cid-rwasicqo> <span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg bg-purple-50 text-purple-700 border border-purple-200 hover:bg-purple-100 transition-colors" title="Optimized for Claude 3.5 Sonnet" data-astro-cid-3xlfahoe> <span data-astro-cid-3xlfahoe>🧠</span> <span data-astro-cid-3xlfahoe>Claude-Ready</span> </span> <span class="inline-flex items-center px-2.5 py-1 text-xs font-medium rounded-lg class={complexityInfo.level === 1 ? 'text-green-700 bg-green-50' : complexityInfo.level === 2 ? 'text-blue-700 bg-blue-50' : 'text-purple-700 bg-purple-50'}> {complexityInfo.level === 1 ? '⚡' : complexityInfo.level === 2 ? '🔗' : '🧠'} {complexityInfo.name} </span> </div> <!-- Tags --> {skill.tags.length > 0 && ( <div class=" flex flex-wrap gap-1.5 mb-4" data-astro-cid-rwasicqo> <span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #ai_and-llms </span><span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #bot </span> </span></div> )} <!-- Footer Stats --> <div class="flex items-center justify-between pt-4 border-t border-gray-100" data-astro-cid-rwasicqo> <div class="flex items-center space-x-4 text-sm text-gray-500" data-astro-cid-rwasicqo> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span class="text-yellow-500" data-astro-cid-rwasicqo>★</span> <span class="font-medium" data-astro-cid-rwasicqo>4.7</span> <span class="text-xs" data-astro-cid-rwasicqo>(88)</span> </div> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span data-astro-cid-rwasicqo>↓</span> <span class="font-medium" data-astro-cid-rwasicqo>1,625</span> </div> </div> <div class="text-xs text-gray-400" data-astro-cid-rwasicqo> v1.0.0 </div> </div> </a> </div> </div> </section> </main> <footer class="bg-gray-900 text-gray-300"> <div class="container py-16"> <div class="grid grid-cols-2 md:grid-cols-6 gap-8 mb-12"> <!-- Brand Column --> <div class="col-span-2"> <a href="/" class="flex items-center mb-4"> <!-- SVG Logo --> <img src="/logo.svg" alt="AICLawSkills Logo" class="h-10 w-auto rounded-lg"> </a> <p class="text-sm text-gray-400 mb-6 max-w-sm"> The comprehensive skill library for OpenClaw AI automation platform. Explore verified skills and boost your productivity. </p> <!-- Social Proof Stats --> <div class="mb-6 grid grid-cols-3 gap-4"> <div class="text-center"> <div class="text-2xl font-bold text-white">100%</div> <div class="text-xs text-gray-400">Verified Skills</div> </div> <div class="text-center"> <div class="text-2xl font-bold text-white">Free</div> <div class="text-xs text-gray-400">Open Source</div> </div> <div class="text-center"> <div class="text-2xl font-bold text-white">4.8★</div> <div class="text-xs text-gray-400">User Rating</div> </div> </div> <!-- Trust Badges --> <div class="flex flex-wrap gap-3 mb-6"> <div class="flex items-center gap-2 text-xs text-gray-400 bg-gray-800 px-3 py-1 rounded-full"> <span class="text-green-400">✓</span> 100% Verified </div> <div class="flex items-center gap-2 text-xs text-gray-400 bg-gray-800 px-3 py-1 rounded-full"> <span class="text-blue-400">🛡️</span> Security Audited </div> <div class="flex items-center gap-2 text-xs text-gray-400 bg-gray-800 px-3 py-1 rounded-full"> <span class="text-purple-400">⚡</span> Updated Weekly </div> </div> <div class="flex space-x-4"> <a href="https://github.com" class="text-gray-400 hover:text-white transition-colors" aria-label="GitHub"> <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"> <path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path> </svg> </a> <a href="https://twitter.com" class="text-gray-400 hover:text-white transition-colors" aria-label="Twitter"> <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"> <path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84"></path> </svg> </a> </div> </div> <!-- Product Links --> <div> <h3 class="text-sm font-semibold text-white uppercase tracking-wider mb-4">Product</h3> <ul class="space-y-3"> <li> <a href="/skills/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> All Skills </a> </li><li> <a href="/skills/#categories" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Browse by Category </a> </li><li> <a href="/skill-bundles/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Skill Bundles </a> </li><li> <a href="/skills/category/web-scrapers" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Web Scrapers </a> </li><li> <a href="/skills/category/ecommerce" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> E-commerce </a> </li><li> <a href="/skills/category/social-media" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Social Media </a> </li><li> <a href="/skills/category/development" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Development </a> </li> </ul> </div> <!-- Resources Links --> <div> <h3 class="text-sm font-semibold text-white uppercase tracking-wider mb-4">Resources</h3> <ul class="space-y-3"> <li> <a href="/get-started/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Documentation </a> </li><li> <a href="/get-started/installation" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Installation </a> </li><li> <a href="/get-started/configuration" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Configuration </a> </li><li> <a href="/get-started/troubleshooting" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Troubleshooting </a> </li> </ul> </div> <!-- Security Links --> <div> <h3 class="text-sm font-semibold text-white uppercase tracking-wider mb-4">🔒 Security</h3> <ul class="space-y-3"> <li> <a href="/security/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Security Overview </a> </li><li> <a href="/security/5-step-hardening-guide" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> 5-Step Hardening Guide </a> </li><li> <a href="/security/auth-none-dangers" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Why auth:none is Dangerous </a> </li><li> <a href="/security/tailscale-guide" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Tailscale Setup Guide </a> </li> </ul> </div> <!-- Use Cases --> <div> <h3 class="text-sm font-semibold text-white uppercase tracking-wider mb-4">Use Cases</h3> <ul class="space-y-3"> <li> <a href="/use-cases/ecommerce" class="text-sm text-gray-400 hover:text-primary-400 transition-colors flex items-center"> <span class="mr-2">🛒</span> E-commerce </a> </li><li> <a href="/use-cases/development" class="text-sm text-gray-400 hover:text-primary-400 transition-colors flex items-center"> <span class="mr-2">💻</span> Development </a> </li><li> <a href="/use-cases/content-creation" class="text-sm text-gray-400 hover:text-primary-400 transition-colors flex items-center"> <span class="mr-2">📝</span> Content Creation </a> </li> </ul> </div> </div> <!-- Comparisons Section --> <div class="border-t border-gray-800 pt-8 mb-8"> <h3 class="text-sm font-semibold text-white uppercase tracking-wider mb-4">Comparisons</h3> <div class="flex flex-wrap gap-6"> <a href="/comparisons/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Comparisons </a><a href="/comparisons/skill-comparison/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Skill Comparison </a><a href="/comparisons/cost-comparison/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Cost Analysis </a><a href="/comparisons/architecture-comparison/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Architecture </a><a href="/tools/benchmarks/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Benchmarks </a><a href="/tools/hardware/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Hardware </a> </div> </div> <!-- Bottom Bar --> <div class="border-t border-gray-800 pt-8 flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0"> <p class="text-sm text-gray-400"> © 2026 AICLawSkills. All rights reserved. </p> <div class="flex space-x-6"> <a href="/legal/about" class="text-sm text-gray-400 hover:text-white transition-colors"> About </a><a href="/legal/privacy" class="text-sm text-gray-400 hover:text-white transition-colors"> Privacy Policy </a><a href="/legal/terms" class="text-sm text-gray-400 hover:text-white transition-colors"> Terms of Service </a><a href="/legal/contact" class="text-sm text-gray-400 hover:text-white transition-colors"> Contact </a> </div> </div> </div> </footer> <div id="search-backdrop" class="search-backdrop hidden fixed inset-0 bg-black/50 z-50 transition-opacity" data-astro-cid-2eu6zh2g></div> <div id="search-modal" class="search-modal hidden fixed inset-0 z-50 flex items-start justify-center pt-[15vh] px-4" data-astro-cid-2eu6zh2g> <div class="relative w-full max-w-2xl bg-white rounded-xl shadow-2xl border border-gray-200 overflow-hidden" data-astro-cid-2eu6zh2g> <!-- Search Input --> <div class="flex items-center px-4 py-3 border-b border-gray-200" data-astro-cid-2eu6zh2g> <svg class="w-5 h-5 text-gray-400 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" data-astro-cid-2eu6zh2g> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" data-astro-cid-2eu6zh2g></path> </svg> <input id="search-input" type="text" placeholder="Search skills... (e.g., 'shopify', 'email', 'automation')" class="flex-1 outline-none text-gray-900 placeholder-gray-400" data-astro-cid-2eu6zh2g> <div class="flex items-center gap-2 ml-2" data-astro-cid-2eu6zh2g> <kbd class="hidden sm:inline-block px-2 py-1 text-xs font-semibold text-gray-400 bg-gray-100 border border-gray-200 rounded" data-astro-cid-2eu6zh2g>ESC</kbd> </div> </div> <!-- Search Results --> <div id="search-results" class="max-h-[50vh] overflow-y-auto" data-astro-cid-2eu6zh2g> <!-- Empty State --> <div id="search-empty" class="px-4 py-8 text-center text-gray-500" data-astro-cid-2eu6zh2g> <svg class="w-12 h-12 mx-auto mb-3 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" data-astro-cid-2eu6zh2g> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" data-astro-cid-2eu6zh2g></path> </svg> <p class="text-sm font-medium" data-astro-cid-2eu6zh2g>Start typing to search skills</p> <p class="text-xs mt-1 text-gray-400" data-astro-cid-2eu6zh2g>Search by name, category, or tags</p> </div> <!-- Results will be injected here --> <div id="search-results-list" class="py-2 hidden" data-astro-cid-2eu6zh2g></div> </div> <!-- Footer --> <div class="px-4 py-2 border-t border-gray-200 bg-gray-50 flex items-center justify-between text-xs text-gray-500" data-astro-cid-2eu6zh2g> <div class="flex items-center gap-3" data-astro-cid-2eu6zh2g> <span class="flex items-center gap-1" data-astro-cid-2eu6zh2g> <kbd data-astro-cid-2eu6zh2g>↑</kbd><kbd data-astro-cid-2eu6zh2g>↓</kbd> to navigate </span> <span class="flex items-center gap-1" data-astro-cid-2eu6zh2g> <kbd data-astro-cid-2eu6zh2g>↵</kbd> to select </span> </div> <span id="search-count" data-astro-cid-2eu6zh2g>0 results</span> </div> </div> </div> <script> // Skills data will be injected server-side window.SKILLS_DATA = []; </script> </body></html>