
Research
/Security News
737 Chrome VPN Extensions Linked to Brand Impersonation and Browser Traffic Redirection
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.
@egradman/jio
Advanced tools
I keep my brain in Obsidian. Throughout the day, as I collect useful links either for work or for personal projects or fun, I jam them in Obsidian. Hence the name.
Jio is designed for people who keep notes in Markdown files. It scans your vault for links, builds a full-text search index, and lets you find any bookmark instantly -- from the terminal, a browser search bar, or any HTTP client.
It extracts URLs from your Markdown vault, indexes them with SQLite FTS5, and provides fast search through both a CLI and an HTTP API. It has a Cloudflare Worker, and a tool to sync your database to that worker, enabling you to search your bookmarks using an OpenSearch compatible API (Chrome custom search engine, Raycast Quicklink, etc).
I primarily interact with it using Openclaw. My HEARTBEAT.md includes a directive to rebuild and sync my bookmarks. I can also ask Openclaw to find any "forgotten links," which have been added but not navigated in the first week, as a weak proxy for things I added but did not follow up on.
Because I don't like to mix business and business and business and personal, you can scope your bookmarks by name and create tokens that restrict the search engine to individual scopes: so the Chrome custom search engine on my Dayjob Mac only has access to Dayjob links.
Jio is distributed as a skill. Install it in OpenClaw, ask OpenClaw to configure it for your Cloudflare Workers, assign a master token, deploy it, and enjoy!
[title](url)) and bare URLs from .md filesnpx @egradman/jio refresh --vault ~/notes
npx @egradman/jio search "kubernetes deploy"
npm install -g @egradman/jio
Then use jio directly:
jio refresh --vault ~/notes
jio search "kubernetes deploy"
git clone https://github.com/egradman/jio.git
cd jio
npm install
npm link
# Import bookmarks from your vault
jio refresh --vault ~/notes
# Search from the terminal
jio search "kubernetes deploy"
# Open the first matching result in your browser
jio open "github actions"
All commands also work with npx @egradman/jio if you haven't installed globally.
jio_conf.json)The CLI looks for a jio_conf.json config file in three locations (first found wins):
./jio_conf.json (project directory)~/.config/jio_conf.json (XDG config)~/.jio_conf.json (home directory)cp jio_conf.example.json jio_conf.json
# Edit jio_conf.json with your values
{
"masterToken": "your-secret-here",
"syncServer": "https://jio.example.com"
}
This is the recommended way to configure jio sync. The file is gitignored. For per-project config, place it in the project directory. For global config, place it in your home directory as ~/.jio_conf.json.
Create a .env file in the project root for CLI settings:
# Path to SQLite database (default: ./bookmarks.db)
DB_PATH=./bookmarks.db
# Path to your Markdown vault (default: ~/vault)
VAULT_PATH=~/vault
Environment variables MASTER_TOKEN and SYNC_SERVER are also supported as a fallback for jio sync, but jio_conf.json is preferred.
Scopes let you organize bookmarks into categories. There are two ways to assign scopes:
Directory-level -- Place a .jio.json file in any vault directory:
{
"scope": "work"
}
All links discovered in that directory (and subdirectories) inherit the scope.
File-level -- Add YAML frontmatter to any Markdown file:
---
jio_scope: personal
---
# My notes
...
File-level scopes override directory-level scopes.
jio refreshScan your vault and import/update bookmarks.
jio refresh [--vault <path>] [--quiet]
| Option | Description |
|---|---|
--vault <path> | Path to vault directory (default: $VAULT_PATH or ~/vault) |
--quiet | Suppress output |
jio search <query>Search bookmarks from the terminal.
jio search "docker compose" [--scope <scope>] [--limit <n>] [--json]
| Option | Description |
|---|---|
--scope <scope> | Filter results to a specific scope |
--limit <n> | Maximum number of results |
--json | Output as JSON |
jio open <query>Search and open the first matching result in your default browser.
jio open "grafana dashboards"
jio forgottenFind bookmarks you saved but never clicked. Queries the remote Cloudflare Worker (not the local database).
jio forgotten [--days <n>] [--scope <scope>] [--limit <n>] [--all] [--json] [--server <url>] [--token <token>]
| Option | Description |
|---|---|
--days <n> | Minimum age in days (default: 14) |
--scope <scope> | Filter by scope |
--limit <n> | Maximum number of results (default: 20) |
--all | Show all forgotten links (sets limit to 1000) |
--json | Output as JSON |
--server <url> | Worker URL (overrides jio_conf.json and SYNC_SERVER) |
--token <token> | Master token (overrides jio_conf.json and MASTER_TOKEN) |
jio statsDisplay bookmark statistics. Queries the remote Cloudflare Worker (not the local database).
jio stats [--json] [--server <url>] [--token <token>]
| Option | Description |
|---|---|
--json | Output as JSON |
--server <url> | Worker URL (overrides jio_conf.json and SYNC_SERVER) |
--token <token> | Master token (overrides jio_conf.json and MASTER_TOKEN) |
jio tokensManage API tokens for HTTP access.
# List all tokens
jio tokens list [--json]
# Create a new token
jio tokens add --name "chrome" [--scope <scope>]
# Delete a token
jio tokens delete --id <id>
Tokens are read-only and used for search. Tokens with a --scope can only search within that scope. Tokens without a scope have access to all bookmarks. Write operations (sync, token management on the Worker) use the MASTER_TOKEN env var instead.
jio syncPush local bookmarks to a remote Cloudflare Worker.
jio sync [--server <url>] [--token <master-token>]
| Option | Description |
|---|---|
--server <url> | Worker URL |
--token <token> | Master token |
Resolution order for both values: CLI flags > jio_conf.json > environment variables (SYNC_SERVER, MASTER_TOKEN).
If you have jio_conf.json configured, just run jio sync with no flags.
These endpoints are served by the Cloudflare Worker. Search endpoints require a read-only API token. Write endpoints require the master token.
GET /Returns an HTML page with usage instructions for the API.
GET /search/:token?q=<query>Search bookmarks.
Parameters:
| Parameter | Description |
|---|---|
q | Search query string |
Behavior:
"grafana 2"), that number selects a specific result from the listAccept: application/json for JSON responsesJSON response format (OpenSearch Suggestions):
[
"query",
["Title 1", "Title 2"],
["Description 1", "Description 2"],
["https://url1.com", "https://url2.com"]
]
HTML response: An interactive results page with keyboard navigation (press 1-9 to open a result).
GET /go/:token?url=<url>Redirect to a URL while tracking the click. Increments the followed_count and updates last_followed for the bookmark.
POST /import/:masterTokenImport links into the database. Requires the master token.
Request body:
{
"links": [
{ "url": "https://example.com", "title": "Example", "description": "...", "scope": "work" }
]
}
Response:
{ "imported": 42 }
Links are upserted -- existing URLs have their title, description, and scope updated.
GET /tokens/:masterTokenList all API tokens. Requires the master token.
POST /tokens/:masterTokenCreate a new API token. Requires the master token.
Request body:
{ "name": "chrome", "scope": "work" }
Response:
{ "token": "abc123...", "name": "chrome", "scope": "work" }
DELETE /tokens/:masterToken/:idDelete an API token by ID. Requires the master token.
GET /stats/:masterTokenReturns bookmark statistics. Requires the master token.
Response:
{
"total": 500,
"withClicks": 42,
"totalClicks": 128,
"forgottenCount": 200,
"byScope": [{ "scope": "work", "count": 300 }, { "scope": "personal", "count": 200 }],
"topClicked": [{ "url": "https://example.com", "title": "Example", "scope": "work", "followed_count": 15 }]
}
GET /forgotten/:masterTokenReturns links that were saved but never clicked. Requires the master token.
| Parameter | Description |
|---|---|
days | Minimum age in days (default: 14) |
scope | Filter by scope |
limit | Maximum number of results (default: 20) |
Response:
{
"forgotten": [{ "url": "https://example.com", "title": "Example", "scope": "work", "discovered_at": "2025-01-01", "followed_count": 0 }],
"days": 14,
"count": 1
}
After deploying the Worker and creating a token, add jio as a custom search engine in your browser:
URL: https://jio.<your-subdomain>.workers.dev/search/YOUR_TOKEN?q=%s
In Chrome: Settings > Search engine > Manage search engines > Add.
Jio uses SQLite with WAL mode for concurrent reads. The database is created automatically on first run and contains:
The database file is stored at DB_PATH (default: ./bookmarks.db).
During jio refresh, Jio scans all .md files in your vault and extracts:
[link text](https://example.com) -- the link text becomes the titlehttps://example.com found anywhere in the textIt automatically filters out:
.png, .jpg, .css, .js, .woff, etc.)Links are upserted -- existing URLs are updated with new metadata, and new URLs are inserted.
Jio runs on Cloudflare Workers with D1 for globally distributed, serverless bookmark search.
If deploying from a headless/SSH machine, wrangler login can't open a browser directly. Two options:
Port-forward (recommended for one-off setup):
# On your local machine
ssh -L 8976:localhost:8976 your-server
# On the remote machine
npx wrangler login --browser false
# Open the printed URL in your local browser to authorize
API token (better for CI or persistent remote access):
export CLOUDFLARE_API_TOKEN="your-token"
# 1. Install dependencies
npm install
# 2. Create the D1 database
npx wrangler d1 create jio-db
# Copy the database_id from the output into wrangler.toml
# 3. Run schema migrations against remote D1
npx wrangler d1 migrations apply jio-db --remote
# 4. Generate a master token and set it everywhere
MASTER_TOKEN=$(openssl rand -hex 32)
echo "$MASTER_TOKEN" | npx wrangler secret put MASTER_TOKEN
# Write CLI config so `jio sync` works
mkdir -p ~/.config
cat > ~/.config/jio_conf.json <<EOF
{
"masterToken": "$MASTER_TOKEN",
"syncServer": "https://jio.<your-subdomain>.workers.dev"
}
EOF
# 5. Deploy the Worker
npx wrangler deploy
# 6. Sync local bookmarks to the Worker
jio refresh --vault ~/vault
jio sync
Create and manage search tokens on the Worker using the master token:
# Create a search token
curl -X POST https://jio.example.com/tokens/<master-token> \
-H 'Content-Type: application/json' \
-d '{"name": "chrome", "scope": "work"}'
# List tokens
curl https://jio.example.com/tokens/<master-token>
# Delete a token
curl -X DELETE https://jio.example.com/tokens/<master-token>/1
Alternative: SQL dump (for initial setup or bulk replace):
sqlite3 bookmarks.db .dump > d1-import.sql
npx wrangler d1 execute jio-db --file=d1-import.sql
# Copy the example and set your local master token
cp .dev.vars.example .dev.vars
# Start the local Worker
npm run worker:dev
This starts a local Worker with a local D1 database at http://localhost:8787. Wrangler automatically loads .dev.vars as environment variables during local development. The .dev.vars file is gitignored.
Set BASE_URL in wrangler.toml under [vars] for non-secret config:
[vars]
BASE_URL = "https://jio.example.com"
MASTER_TOKEN should never go in wrangler.toml (it would be committed to git). Instead:
.dev.vars (gitignored, loaded automatically by wrangler dev)npx wrangler secret put MASTER_TOKEN (encrypted, stored by Cloudflare)Jio is distributed as an OpenClaw skill. The SKILL.md file contains the skill definition that tells OpenClaw how to configure and use jio. You can install this skill in OpenClaw to enable your AI agent to manage and search your bookmarks automatically, including refreshing your vault, querying forgotten links, and managing tokens.
ISC
FAQs
Personal bookmark manager - extract, index, and search vault links
We found that @egradman/jio demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.