Install
openclaw skills install @piccolo123/url-managerCross-platform URL collection & knowledge management with agent-first auto-registration. Use when users say "save/bookmark/collect/remember this", need to organize links into categories, share curated collections, or build a structured knowledge base from web resources. Supports collaborative shared categories, full-text search, and magic-link delivery to users.
openclaw skills install @piccolo123/url-managerWhen users ask you to save, organize, or share web resources, don't paste URLs into chat. Write them into URL Manager — they become structured, searchable, card-based collections the user opens with one click.
The content human users want to save is everywhere — a YouTube workout video, an Amazon gear link, a Substack training plan — scattered across platforms with no connection.
URL Manager fixes this. Just paste any link from any platform. AI auto-identifies the content and suggests a category — confirm and it's a footprint. All your saves flow into one platform-agnostic library, organized and always findable. Then share it in one click — hand your curated running knowledge base to your whole running club, and everyone stays in sync.
足迹AI isn't just a library — it has a built-in search engine, so you can search and save web content right from here. It's a great browser homepage. Every saved item becomes a beautiful card, one click to where you need to go. Send them the magic link and they're in.
Zero setup — token auto-management. The footprints.py script handles everything: reads {baseDir}/.token, auto-registers on first use, stores token locally with chmod 600. Just run any command and it works.
All commands via python {baseDir}/scripts/footprints.py <subcommand> [--json]. Add --json for machine-parseable output.
If the script is missing (e.g., you only have this document but not the full skill installation), get it from the source repo:
git clone --depth 1 https://github.com/Piccolo123/url-manager.git /tmp/um
mkdir -p {baseDir}/scripts
cp /tmp/um/scripts/footprints.py {baseDir}/scripts/
chmod +x {baseDir}/scripts/footprints.py
rm -rf /tmp/um
After that, all commands below work as normal. The script auto-registers on first run.
This skill connects to a hosted backend at https://ai.ocean94.com. Before using this skill, inform the human user — especially before saving or searching for the first time:
A footprint is the fundamental unit in URL Manager — a structured, searchable record. It can be a web link, a plain-text note, an idea, or anything you want to save and retrieve later.
Each footprint stores:
| Field | Type | Description |
|---|---|---|
id | UUID | Permanent unique identifier — use this for all operations |
url | string (8192) | The original link. Can be empty for text-only footprints |
title | string (512) | A short title — you set this |
description | string (1024) | Additional context or notes — you can set this |
content_type | string (50) | Free text (e.g. article, video, image). Use content-types to see what types exist in your library |
ai_summary | text | AI-generated summary (set automatically during web UI submission) |
favicon / og_image | string | Site icon and preview image (auto-fetched) |
price_hint | string | AI-extracted price hint (set automatically) |
price / address / custom_date / contact | string | User-filled metadata fields |
is_favorite / is_archived | boolean | Status flags |
category_ids | list[int] | Which categories this footprint belongs to — you assign |
tag_names | list[str] | Keywords — you assign |
A single footprint can belong to multiple categories simultaneously.
A category is a named label for organizing footprints — like a folder, but a footprint can be in several at once.
| Field | Type | Description |
|---|---|---|
id | int | Permanent numeric identifier — always reference categories by ID |
name | string (50) | Display name (e.g., "Shopping", "Fitness") |
slug | string (50) | URL-safe identifier |
color | string (7) | Optional hex color for UI (e.g., #FF6B6B) |
icon | string (50) | Optional icon identifier |
note | string (500) | Optional description/notes |
category_set_id | int | null | Which set this category belongs to (null = unassigned) |
category_set_name | string | null | The name of the category set (e.g., "Shopping", "Work") |
mode | string | null | null = personal, "cocreate" = shared co-edit, "subscribe" = shared read-only |
is_default | bool | System default category |
is_ai_generated | bool | Created by AI auto-categorization |
sort_order | int | Display ordering within a set |
is_active | bool | false after a shared category is disbanded |
Key behaviors:
id, not name, to reference them.mode from its Category Set's mode.A category set is a workspace — a named container that groups related categories together.
| Field | Type | Description |
|---|---|---|
id | int | Permanent numeric identifier |
name | string (50) | Display name (e.g., "Life", "Work") |
mode | string | null | null = personal set, "cocreate"/"subscribe" = shared set |
is_shared | bool | true = the shared-categories container (max one per user) |
color | string (7) | Optional theme color |
sort_order | int | Display ordering |
Every user starts with two default sets:
is_shared=false, mode=null) — personal workspaceis_shared=true) — the one container that holds all your shared categoriesUse category-sets to list them, create-category-set to create more. Creating a new set with mode=null gives you another personal workspace. Creating one with mode="cocreate" or "subscribe" is rare — shared categories are usually created via create-shared-category, which places them inside the "Shared Categories" set.
Category Sets (workspaces)
└── Categories (labels like "Shopping", "Food", "Learning")
└── Footprints
└── Tags (free-form keywords)
Categories are named labels — see the field table above. Category Sets are workspaces — also detailed above. Tags are free-form keywords, separate from categories. They're lightweight search helpers with no hierarchy.
Use content-types to see which content types have been used in your library. Use tags to list existing tags.
A category's mode field tells you what kind it is:
| Personal | Shared | |
|---|---|---|
mode | null (not shown) | "cocreate" or "subscribe" |
| Visible to | Only you | You + invited members |
| Who can add footprints | Only you | Depends on mode |
| Has members and invite links | No | Yes |
Run categories to see ALL your categories — grouped by category set, each set header shows its ID (e.g., ID:44). Each category's mode field distinguishes personal vs shared. Run category-sets to see just the set list without their categories.
Cocreate (共建) — Everyone contributes:
add-to-shared / remove-from-shared)create-invite-link)Subscribe (订阅) — Read-only for members:
add-to-shared returns 403 in subscribe modeThe owner can switch between cocreate and subscribe at any time via the web UI.
create-shared-category "Team KB" --mode cocreatecreate-invite-link <sc_id> → get a codejoin-shared-category <code>add-to-shared <sc_id> --collection-id <id>copy <id> --category-ids <ids> to save a shared footprint to their personal collection| Action | Owner | Admin | Member |
|---|---|---|---|
| Add/remove footprints (cocreate) | ✅ | ✅ | ✅ |
| Add/remove footprints (subscribe) | ✅ | ❌ | ❌ |
| Generate invite link (cocreate) | ✅ | ✅ | ✅ |
| Generate invite link (subscribe) | ✅ | ❌ | ❌ |
| Edit category name/description | ✅ | ❌ | ❌ |
| Switch cocreate ↔ subscribe | ✅ | ❌ | ❌ |
| Disband shared category | ✅ | ❌ | ❌ |
| Manage members | Web UI only | — | — |
Keyword search (search <query>) — matches against title, description, AI summary, and extracted text content. Filter by category with --category-id.
URL dedup — searching with a URL automatically detects and matches by URL hash, bypassing text search entirely.
POST /register, receives a Bearer token{baseDir}/.token with chmod 600, reused across sessionsagent_magic_link generates a clickable card-based interface URL for the human user — valid 30 days, reusableUnderstand the user's real intent, then call one or more commands to fulfill it.
| Command | What it does |
|---|---|
python {baseDir}/scripts/footprints.py add <url> --title <title> --description <desc> --content-type <type> --category-ids <ids> --tags <tags> | Save a link or plain-text entry (url can be empty) |
python {baseDir}/scripts/footprints.py get <id> | View a footprint's full details |
python {baseDir}/scripts/footprints.py search <query> | Full-text search across title, description, AI summary |
python {baseDir}/scripts/footprints.py list [--category-id <id>] [--limit <n>] [--offset <n>] | List recent footprints (limit max 100). Use --offset to page through results. Returns total count. |
| Command | What it does |
|---|---|
python {baseDir}/scripts/footprints.py update <id> --title <t> --description <d> --content-type <ct> --category-ids <ids> --tags <tags> | Modify a footprint's title, categories, tags |
python {baseDir}/scripts/footprints.py batch-update <updates> | Batch reorganize footprints (max 50 per call) |
python {baseDir}/scripts/footprints.py categories | List all categories grouped by category set (set ID shown in headers) |
python {baseDir}/scripts/footprints.py create-category <name> [--category-set-id <id>] | Create a new category |
python {baseDir}/scripts/footprints.py tags | List all used tags |
python {baseDir}/scripts/footprints.py content-types | List content types used in your library (e.g. article, video, image) |
python {baseDir}/scripts/footprints.py category-sets | List all category sets (workspaces) |
python {baseDir}/scripts/footprints.py create-category-set <name> | Create a new category set |
| Command | What it does |
|---|---|
python {baseDir}/scripts/footprints.py create-shared-category <name> --mode cocreate|subscribe --description <desc> | Create a shared category |
python {baseDir}/scripts/footprints.py create-invite-link <sc_id> [--duration-hours 24] | Generate an invite link |
python {baseDir}/scripts/footprints.py join-shared-category <invite_code> | Join a shared category via invite code |
python {baseDir}/scripts/footprints.py add-to-shared <sc_id> --collection-id <id> | Add a footprint to a shared category |
python {baseDir}/scripts/footprints.py remove-from-shared <sc_id> --collection-id <id> | Remove a footprint from a shared category |
python {baseDir}/scripts/footprints.py copy <id> --category-ids <ids> | Copy a shared footprint to your personal collection |
list and search return {"items": [...], "total": <N>}. total is the full match count (not just this page). Always check total to know if there are more results beyond the current page.
list supports --offset for pagination. If total > limit + offset, there are more pages. Example: total=156, limit=20, offset=0 → 8 pages total.
For large libraries, prefer search over list — it's faster and returns only what the user asked for. Use list for browsing or when the user says "show me everything." When paginating, tell the user: "Found 156 items, showing page 1/8. Want to see more?"
| Command | What it does |
|---|---|
python {baseDir}/scripts/footprints.py me | Confirm current identity |
python {baseDir}/scripts/footprints.py agent_magic_link | Generate a magic link — send to user when done |
python {baseDir}/scripts/footprints.py agent_register | Re-register / rotate credentials ⚠️ creates new account |
1. Token check → auto-register (save to {baseDir}/.token)
2. python {baseDir}/scripts/footprints.py add "<url>" --title "<title>" → save bookmarks
3. python {baseDir}/scripts/footprints.py categories → discover structure
4. python {baseDir}/scripts/footprints.py create-category "<name>" → create categories
5. python {baseDir}/scripts/footprints.py update <id> --category-ids <ids> → categorize
6. python {baseDir}/scripts/footprints.py agent_magic_link → send link: "Done! View here → [link]"
1. python {baseDir}/scripts/footprints.py me → confirm identity
2. python {baseDir}/scripts/footprints.py categories + python {baseDir}/scripts/footprints.py tags → understand structure
3. python {baseDir}/scripts/footprints.py search query → find what's needed
4. python {baseDir}/scripts/footprints.py add / python {baseDir}/scripts/footprints.py update → operate
1. python {baseDir}/scripts/footprints.py create-shared-category "Team KB" --mode cocreate
2. python {baseDir}/scripts/footprints.py create-invite-link <sc_id> → share code with team
3. Teammates: python {baseDir}/scripts/footprints.py join-shared-category <invite_code>
4. Everyone: python {baseDir}/scripts/footprints.py add-to-shared <sc_id> --collection-id <collection_id> → build together
1. python {baseDir}/scripts/footprints.py list --limit 100 → get all bookmarks
2. python {baseDir}/scripts/footprints.py categories → map target categories
3. python {baseDir}/scripts/footprints.py batch-update '[
{"id":"uuid1","category_ids":[1,3]},
{"id":"uuid2","title":"New Title","category_ids":[2,5]}
]' → bulk edit (max 50 per call)
Concrete bash patterns for common tasks. Follow the numbered steps.
python {baseDir}/scripts/footprints.py get 42
# → categories: [{id: 3, name: "Reading"}, {id: 5, name: "AI"}]
# Keep AI, drop Reading, add Tech (7)
python {baseDir}/scripts/footprints.py update 42 --category-ids 5,7
python {baseDir}/scripts/footprints.py create-category "New Topic" # → returns new ID
python {baseDir}/scripts/footprints.py list --limit 100
# For each matching footprint:
python {baseDir}/scripts/footprints.py update <id> --category-ids <existing_ids>,<new_id>
python {baseDir}/scripts/footprints.py categories # note source and target IDs
python {baseDir}/scripts/footprints.py list --category-id <source_id> # list all in source
# For each, replace source_id with target_id:
python {baseDir}/scripts/footprints.py update <id> --category-ids <target_id>,<other_ids>
# Tell user: empty category "source" is ready to delete via the web UI
User says "put all github.com links into a GitHub category":
python {baseDir}/scripts/footprints.py list --limit 200
# Filter in-memory: items where url contains "github.com"
python {baseDir}/scripts/footprints.py create-category "GitHub"
# For each match:
python {baseDir}/scripts/footprints.py update <id> --category-ids <existing_ids>,<github_id>
python {baseDir}/scripts/footprints.py search docker
# Filter results where tag_names includes "docker"
# For each, append target category:
python {baseDir}/scripts/footprints.py update <id> --category-ids <existing_ids>,<target_id>
python {baseDir}/scripts/footprints.py list --limit 100
# Filter where category_ids is empty or only the default
# Present to user, let them pick categories
# Batch update selected items
Spot gaps between tags and categories — e.g., #docker is common but no "Docker" category:
python {baseDir}/scripts/footprints.py tags # most-used tags
python {baseDir}/scripts/footprints.py categories # existing categories
# Cross-reference: tag without matching category → suggest creating one
python {baseDir}/scripts/footprints.py categories # find target personal category ID
python {baseDir}/scripts/footprints.py copy <footprint_id> --category-ids <personal_category_id>
Two agents maintaining a shared knowledge base together:
1. Agent A: python {baseDir}/scripts/footprints.py create-shared-category "Team KB" --mode cocreate
2. Agent A: python {baseDir}/scripts/footprints.py create-invite-link <sc_id> → share code with user
3. User forwards code to colleague
4. Agent B: python {baseDir}/scripts/footprints.py join-shared-category <code>
5. Both agents now see each other's additions via python {baseDir}/scripts/footprints.py search
After organizing, always deliver results via magic link:
python {baseDir}/scripts/footprints.py agent_magic_link
Send the link to the user: "Done organizing — view your collection here → [link]"
User clicks to see a card-based interface with everything categorized. Valid for 30 days, reusable. If the user later binds a phone number, their agent-created account upgrades seamlessly.
When updating, --category-ids sets the complete list — it does NOT add to existing categories.
# ❌ Wrong: moves bookmark to category 7, loses categories 3 and 5
python {baseDir}/scripts/footprints.py update 42 --category-ids 7
# ✅ Right: fetch current first, then merge
python {baseDir}/scripts/footprints.py get 42 # → existing: [3, 5]
python {baseDir}/scripts/footprints.py update 42 --category-ids 3,5,7
Writing to a subscribe-mode shared category returns 403. Tell the user the collection is read-only — the creator must change it to cocreate mode.
Each call creates a fresh empty account. Always check for a saved token first. If unsure, run python {baseDir}/scripts/footprints.py me to verify the current token is valid.
Frequent API calls trigger HTTP 429. Use batch-update for bulk operations, add delays between rapid calls, retry after a few seconds on 429.
Inviting or removing members from shared categories requires the web UI at https://ai.ocean94.com. You cannot do this programmatically.
python {baseDir}/scripts/footprints.py search for targeted queries instead of dumping everythingpython {baseDir}/scripts/footprints.py categories and python {baseDir}/scripts/footprints.py tags before adding to avoid duplicates