@prowl-ai/prowl-skill 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,237 @@
1
+ 'use strict';
2
+ /**
3
+ * What this session has asked Prowl to do, kept where the next hook can read it.
4
+ *
5
+ * `PreToolUse` sees a call start; `PostToolUse` sees it finish several seconds later,
6
+ * in a different process; the status line renders it in a third. Nothing in a hook
7
+ * payload carries state between them, so it goes through a file keyed by session.
8
+ *
9
+ * **`~/.prowl/` and not the project tree.** The token file already lives there — this
10
+ * plugin has one home on the machine. A file under the repository would be committed
11
+ * by someone eventually, and a file under `/tmp` would vanish mid-session.
12
+ *
13
+ * **Keyed by `session_id`.** Not by pid: the status-line reference is explicit that a
14
+ * pid changes on every invocation and defeats exactly this kind of cache.
15
+ *
16
+ * Nothing here throws. A hook that raises breaks the operator's turn for a widget,
17
+ * which is a bad trade in every direction; an unreadable file is an empty state, and
18
+ * a write that cannot happen is a widget that says less rather than a turn that dies.
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+
24
+ /** Where a session's file lives, relative to home. */
25
+ const DIR = ['.prowl', 'status'];
26
+
27
+ /** How many finished calls the ticker remembers. The array IS the display rule. */
28
+ const RECENT = 5;
29
+
30
+ /** How long a lock may be held before it is assumed dead. Hooks are short. */
31
+ const LOCK_STALE_MS = 5000;
32
+
33
+ /** The canonical empty state. Every absent field is absent, never zero. */
34
+ function empty(sessionId) {
35
+ return {
36
+ session_id: sessionId || null,
37
+ updated_at: null,
38
+ open: {},
39
+ recent: [],
40
+ totals: { calls: 0, ok: 0, failed: 0, usd: null },
41
+ progress: null,
42
+ server: null,
43
+ spoken: [],
44
+ };
45
+ }
46
+
47
+ /**
48
+ * A session id becomes a filename, so nothing in it may be a path.
49
+ *
50
+ * Separators first, then every run of dots: while they are still separators, `..` is
51
+ * still recognisable as a traversal.
52
+ */
53
+ function fileFor(home, sessionId) {
54
+ const key = String(sessionId || 'unknown')
55
+ .replace(/[^A-Za-z0-9._-]+/g, '_')
56
+ .replace(/\.{2,}/g, '_')
57
+ .replace(/^[._]+/, '')
58
+ .slice(0, 80);
59
+ return path.join(home, ...DIR, `${key || 'unknown'}.json`);
60
+ }
61
+
62
+ /** The recorded state, or an empty one. An unparsable file is an empty one. */
63
+ function read(home, sessionId) {
64
+ try {
65
+ const raw = fs.readFileSync(fileFor(home, sessionId), 'utf8');
66
+ const parsed = JSON.parse(raw);
67
+ if (!parsed || typeof parsed !== 'object') return empty(sessionId);
68
+ // A file written by an older shape must not make today's renderer throw.
69
+ return Object.assign(empty(sessionId), parsed);
70
+ } catch (e) {
71
+ return empty(sessionId);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Write, atomically.
77
+ *
78
+ * A temporary file in the same directory and then `rename`, because a reader that
79
+ * arrives mid-write must see the old document or the new one and never half of
80
+ * either. The temp name carries the pid so two writers cannot collide on it.
81
+ */
82
+ function writeAtomic(file, state) {
83
+ try {
84
+ fs.mkdirSync(path.dirname(file), { recursive: true });
85
+ const tmp = `${file}.${process.pid}.tmp`;
86
+ fs.writeFileSync(tmp, JSON.stringify(state), 'utf8');
87
+ fs.renameSync(tmp, file);
88
+ return true;
89
+ } catch (e) {
90
+ return false;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Run `fn(state)` under a lock and persist what it returns.
96
+ *
97
+ * `rename` alone stops a torn document; it does not stop a lost update, and two calls
98
+ * finishing in the same instant is the normal case for an agent that batches tool
99
+ * calls. `mkdir` is the atomic primitive available everywhere, so it is the lock.
100
+ *
101
+ * **The lock is never allowed to cost a turn.** After roughly fifty milliseconds the
102
+ * update proceeds unlocked: a widget that occasionally loses one record is better
103
+ * than a hook that hangs, and a stale lock left by a killed process is cleared by age
104
+ * rather than by an operator.
105
+ */
106
+ function update(home, sessionId, fn) {
107
+ const file = fileFor(home, sessionId);
108
+ const lock = `${file}.lock`;
109
+ let held = false;
110
+ for (let i = 0; i < 50; i += 1) {
111
+ try {
112
+ fs.mkdirSync(path.dirname(file), { recursive: true });
113
+ fs.mkdirSync(lock);
114
+ held = true;
115
+ break;
116
+ } catch (e) {
117
+ try {
118
+ const age = Date.now() - fs.statSync(lock).mtimeMs;
119
+ if (age > LOCK_STALE_MS) { fs.rmdirSync(lock); continue; }
120
+ } catch (e2) { /* it went away between the two calls, which is fine */ }
121
+ // Busy-wait one millisecond. `Atomics.wait` is the only synchronous sleep a
122
+ // hook can use, and a hook is synchronous by construction.
123
+ try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1); } catch (e3) { /* no SAB */ }
124
+ }
125
+ }
126
+ let next;
127
+ try {
128
+ next = fn(read(home, sessionId));
129
+ } catch (e) {
130
+ next = null;
131
+ }
132
+ let ok = false;
133
+ if (next) {
134
+ next.updated_at = next.updated_at || new Date().toISOString();
135
+ ok = writeAtomic(file, next);
136
+ }
137
+ if (held) { try { fs.rmdirSync(lock); } catch (e) { /* already gone */ } }
138
+ return ok ? next : null;
139
+ }
140
+
141
+ /** A call started. Keyed by `tool_use_id`, so the in-flight count counts calls. */
142
+ function openCall(home, sessionId, rec) {
143
+ if (!rec || !rec.id) return null;
144
+ return update(home, sessionId, (s) => {
145
+ s.session_id = s.session_id || sessionId || null;
146
+ s.open[rec.id] = { tool: rec.tool || null, at: rec.at || new Date().toISOString() };
147
+ s.updated_at = rec.at || new Date().toISOString();
148
+ return s;
149
+ });
150
+ }
151
+
152
+ /**
153
+ * A call finished.
154
+ *
155
+ * `ms` is computed only when the start was seen. A plugin installed mid-session, or a
156
+ * session resumed across a restart, produces a close with no open — and the duration
157
+ * is then **absent**, never zero. `usd` is added only when the response carried one;
158
+ * a free tool must not drag the total to a number the server never said.
159
+ */
160
+ function closeCall(home, sessionId, rec) {
161
+ if (!rec || !rec.id) return null;
162
+ return update(home, sessionId, (s) => {
163
+ const started = s.open[rec.id];
164
+ delete s.open[rec.id];
165
+ const at = rec.at || new Date().toISOString();
166
+ const ms = started && started.at ? Math.max(0, Date.parse(at) - Date.parse(started.at)) : null;
167
+ const entry = {
168
+ tool: rec.tool || (started && started.tool) || null,
169
+ ok: rec.ok !== false,
170
+ ms: Number.isFinite(ms) ? ms : null,
171
+ usd: typeof rec.usd === 'number' && Number.isFinite(rec.usd) ? rec.usd : null,
172
+ at,
173
+ };
174
+ s.recent.push(entry);
175
+ while (s.recent.length > RECENT) s.recent.shift();
176
+ s.totals.calls += 1;
177
+ if (entry.ok) s.totals.ok += 1; else s.totals.failed += 1;
178
+ if (entry.usd !== null) s.totals.usd = (s.totals.usd === null ? 0 : s.totals.usd) + entry.usd;
179
+ if (typeof rec.progress === 'number') {
180
+ s.progress = { session_id: rec.progress_session || null, value: rec.progress, at };
181
+ }
182
+ // The server's own accounting, when it volunteered it. Kept beside the sum rather
183
+ // than folded into it: two figures that disagree are a fact worth keeping.
184
+ if (rec.server && typeof rec.server === 'object') s.server = Object.assign({ at }, rec.server);
185
+ s.updated_at = at;
186
+ return s;
187
+ });
188
+ }
189
+
190
+ /**
191
+ * Has this exact thing already been said?
192
+ *
193
+ * Returns `true` the first time a fingerprint is seen and `false` afterwards, so a
194
+ * failing call that the agent retries five times interrupts once. The fingerprint is
195
+ * recorded by the same write that answers, because two hooks asking at once must not
196
+ * both be told "first".
197
+ */
198
+ function claimSpeech(home, sessionId, fingerprint) {
199
+ if (!fingerprint) return false;
200
+ let first = false;
201
+ update(home, sessionId, (s) => {
202
+ if (s.spoken.includes(fingerprint)) return s;
203
+ first = true;
204
+ s.spoken.push(fingerprint);
205
+ while (s.spoken.length > 50) s.spoken.shift();
206
+ return s;
207
+ });
208
+ return first;
209
+ }
210
+
211
+ /**
212
+ * Delete session files older than `maxAgeMs`.
213
+ *
214
+ * Without it this directory grows one small file per session forever — the kind of
215
+ * litter nobody notices until it is thousands of files.
216
+ */
217
+ function prune(home, nowMs, maxAgeMs) {
218
+ const dir = path.join(home, ...DIR);
219
+ let removed = 0;
220
+ try {
221
+ for (const name of fs.readdirSync(dir)) {
222
+ const f = path.join(dir, name);
223
+ try {
224
+ if (nowMs - fs.statSync(f).mtimeMs > maxAgeMs) {
225
+ if (fs.statSync(f).isDirectory()) fs.rmdirSync(f); else fs.unlinkSync(f);
226
+ removed += 1;
227
+ }
228
+ } catch (e) { /* a file that vanished mid-sweep needed no pruning */ }
229
+ }
230
+ } catch (e) { /* nothing to prune is the normal case */ }
231
+ return removed;
232
+ }
233
+
234
+ module.exports = {
235
+ empty, fileFor, read, update, openCall, closeCall, claimSpeech, prune,
236
+ DIR, RECENT, LOCK_STALE_MS,
237
+ };
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: prowl
3
+ description: Connects this agent to the Prowl MCP — a single MCP endpoint exposing 448 market-intelligence tools (SEO, backlinks, ads, SERP, web scraping, AI) plus the full Prowl analysis pipeline, billed pay-as-you-go from a USD wallet. Use when researching competitors, pulling SEO/SERP/keyword/backlink data, analyzing ad creative or spend, comparing pricing or funnels, validating a product idea, or any task that needs real market data instead of guesses.
4
+ license: MIT
5
+ compatibility: Requires network access to prowl.chat and a Prowl API token; the bundled MCP config runs `npx mcp-remote` (needs Node.js 18+).
6
+ metadata:
7
+ author: prowl.chat
8
+ version: "0.3.0"
9
+ ---
10
+
11
+ # Prowl — market intelligence for your agent
12
+
13
+ Prowl is a single MCP endpoint that exposes **448 market-intelligence API tools** across 15 providers (DataForSEO, SearchAPI, SerpApi, SpyFu, Majestic, Foreplay, Firecrawl, Exa, Perplexity, Gemini, …) plus the full Prowl pipeline — `prowl_analyze`, async research sessions, recurring schedules, artifacts and exports. Call real data instead of guessing.
14
+
15
+ - MCP URL: `https://prowl.chat/mcp`
16
+ - Transport: Streamable HTTP
17
+ - Auth header: `Authorization: Bearer prowl_<your_key>`
18
+
19
+ ## Setup (one time)
20
+
21
+ Prowl API keys (`prowl_...`) are billing-bearing — every metered call debits a USD wallet. Treat the key as a secret; never commit it to a public repo.
22
+
23
+ 1. The human signs up at [https://prowl.chat](https://prowl.chat) (gets $5 starter credit), optionally tops up the wallet, and generates a `prowl_...` key (shown once) under **MCP Home → API keys & config**.
24
+ 2. Provide the key to this plugin via either:
25
+ - environment variable `PROWL_MCP_TOKEN`, or
26
+ - a token file at `~/.prowl/prowl_mcp_token` (`mkdir -p ~/.prowl && printf '%s' "prowl_YOUR_KEY" > ~/.prowl/prowl_mcp_token && chmod 600 ~/.prowl/prowl_mcp_token`).
27
+ 3. The bundled `.mcp.json` reads the token automatically and connects via `mcp-remote`.
28
+
29
+ For manual per-client setup (Cursor `.cursor/mcp.json`, Claude Code `claude mcp add`, Codex `config.toml`, generic MCP), see the hosted playbook: [https://prowl.chat/mcp/install.md](https://prowl.chat/mcp/install.md).
30
+
31
+ ## Core tools
32
+
33
+ 22 MCP tools front the catalog. The ones that matter day to day:
34
+
35
+ | Tool | Purpose | Cost |
36
+ |------|---------|------|
37
+ | `prowl_list_tools` | Browse the 448 tools by category | free |
38
+ | `prowl_search_tools` | Semantic search over the catalog | free |
39
+ | `prowl_tool_info` | JSON input schema + estimated cost for one tool | free |
40
+ | `prowl_call_tool` | Invoke any single tool | metered |
41
+ | `prowl_analyze` | Full multi-agent research report in one shot | metered, tier-capped |
42
+ | `prowl_list_playbooks` | The 8 fixed, persona-tuned report shapes | free |
43
+ | `prowl_start_session` / `prowl_session_status` / `prowl_get_session` | Async deep research: kick off, poll, read back | metered |
44
+ | `prowl_generate_artifact` / `prowl_export_report` | Visuals, infographics, audio; Markdown/PDF/PPTX export | metered |
45
+ | `prowl_schedule_create` / `_list` / `_pause` / `_resume` / `_cancel` | Recurring hunts | metered per run |
46
+ | `prowl_get_stats` / `prowl_get_error_feed` / `prowl_reset_session` | Run accounting, failures, state reset | free |
47
+
48
+ MCP tool args are **flat**, not nested.
49
+
50
+ Recommended flow: `prowl_search_tools` → `prowl_tool_info` → `prowl_call_tool` for one fact; `prowl_analyze` for a complete strategy report. Pass a stable `session_id` across calls to keep the report cache and conversation memory scoped to one conversation.
51
+
52
+ Full reference: [https://prowl.chat/mcp/skill.md](https://prowl.chat/mcp/skill.md)
53
+
54
+ ## Tiers
55
+
56
+ `prowl_analyze` and `prowl_start_session` take a `tier`, which sets the tool budget and the hard provider-cost cap for the run:
57
+
58
+ | Tier | Use for | Provider cost cap |
59
+ |------|---------|------------------:|
60
+ | `basic` | One question, fast turnaround | $2.50 |
61
+ | `deep` | Full competitive report | $8.00 |
62
+ | `max` | Exhaustive, research-grade | $18.00 |
63
+
64
+ You are never billed more than the reserved hold.
65
+
66
+ ## Playbooks
67
+
68
+ Pass `playbook_id` to `prowl_analyze` to force a fixed report shape instead of a dynamically composed one:
69
+
70
+ `geo-visibility-audit` · `competitor-teardown` · `content-engine` · `local-and-reputation` · `mobile-aso` · `amazon-marketplace` · `idea-validation` · `channel-economics-audit`
71
+
72
+ Call `prowl_list_playbooks` (free) for what each one covers.
73
+
74
+ ## Verify
75
+
76
+ 1. List MCP tools; confirm `prowl_list_tools`, `prowl_tool_info`, `prowl_call_tool`, `prowl_analyze`.
77
+ 2. Run `prowl_list_tools` (free) → 448 tools grouped by category.
78
+ 3. Connectivity check (no auth): `curl https://prowl.chat/mcp/health`.
79
+
80
+ ## Billing
81
+
82
+ - **Subscription pool** (plan renewal, burns at period end) + **extra pool** (top-ups, never expires).
83
+ - Each `prowl_call_tool` response carries a `billing` object (`estimated_cost_usd`, `actual_cost_usd`, `debited`).
84
+ - Insufficient balance blocks the call before it runs — top up at MCP Home.
85
+
86
+ ## The status widget
87
+
88
+ Installed with the plugin, on by default, and it calls no API — every figure it shows
89
+ was already in a response this agent received.
90
+
91
+ While a call runs it records it; when the call returns it records the debit the server
92
+ reported. Two things reach the operator without being asked for: a **taskbar/dock
93
+ progress bar** when `prowl_session_status` reports a fraction, and a **one-time
94
+ notice** when a call fails or the wallet blocks one. A repeated failure is said once —
95
+ an agent retrying five times must not produce five notices.
96
+
97
+ A line at the bottom of the terminal shows the rest: the in-flight call and how long it
98
+ has been running, calls / ok / failed, spend, and the last five tools with their
99
+ verdicts. No plugin manifest can declare a `statusLine`, so the operator wires it once,
100
+ in `~/.claude/settings.json`:
101
+
102
+ ```json
103
+ {
104
+ "statusLine": {
105
+ "type": "command",
106
+ "command": "node \"$HOME/.prowl/prowl-statusline.js\"",
107
+ "refreshInterval": 2
108
+ }
109
+ }
110
+ ```
111
+
112
+ Copy the script to that stable path, so a plugin update — whose cache directory
113
+ carries the version — cannot break a line the operator wired:
114
+
115
+ ```bash
116
+ mkdir -p ~/.prowl
117
+ cp "$(find ~/.claude/plugins -path '*prowl/statusline/prowl-statusline.js' | head -1)" ~/.prowl/
118
+ ```
119
+
120
+ The copy finds the plugin's library on its own; `PROWL_LIB` overrides the search if
121
+ the plugin lives somewhere unusual. Finding nothing, it prints nothing.
122
+
123
+ `refreshInterval` matters: status-line updates go quiet while the session waits, which
124
+ is exactly when a Prowl call is in flight and the elapsed segment must keep moving.
125
+ Remove the line again by deleting the `statusLine` field, or with `/statusline clear`.
126
+
127
+ State lives in `~/.prowl/status/<session_id>.json` beside the token, is pruned after
128
+ seven days, and nothing is ever written to `~/.claude/settings.json` by the plugin.
129
+
130
+ ## When to use
131
+
132
+ Competitive/market intelligence, SEO/SERP/keyword/backlink research, ad creative & spend research, pricing/funnel analysis, review mining, AI-search (GEO/AEO) visibility, product-idea validation — anything needing real numbers.
133
+
134
+ ## When not to use
135
+
136
+ Generic coding with no research/data component, or tasks fully answerable from the local codebase.
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * The bottom line — where a Prowl call is, without asking.
5
+ *
6
+ * **Opt-in, and it has to be.** `statusLine` is not a field any plugin manifest can
7
+ * declare, so a plugin cannot ship one; the operator wires this script themselves,
8
+ * with one command, and can unwire it with one. That is a worse installation story
9
+ * than the hooks have and a better one than writing into somebody's settings
10
+ * uninvited.
11
+ *
12
+ * It reads two things and prints one line:
13
+ *
14
+ * - the session file, found by the `session_id` in its own payload. The status-line
15
+ * reference prescribes exactly this: a pid changes on every invocation and defeats
16
+ * any cache keyed by it.
17
+ * - `cost` and `context_window` from that payload, which exist in **no** hook event.
18
+ * This is the only component that can show what the session has spent on the model
19
+ * and how much of the window it has eaten, which is why they are shown here and
20
+ * nowhere else rather than estimated somewhere convenient.
21
+ *
22
+ * It runs on every render, so it does the least possible: read one small JSON file,
23
+ * format, write. Nothing is computed that was not already recorded, and nothing is
24
+ * written at all.
25
+ *
26
+ * Prints nothing when there is nothing to say. An empty status line is the honest
27
+ * rendering of "this session has not called Prowl", and better than a line that says
28
+ * `0 calls` in a repository where the plugin was never used.
29
+ */
30
+
31
+ const path = require('path');
32
+ const os = require('os');
33
+ const fs = require('fs');
34
+
35
+ /**
36
+ * Where `lib/` is, from wherever this file was run.
37
+ *
38
+ * Beside the plugin it is one directory up. But the documented installation copies
39
+ * this single file somewhere stable — because a plugin's cache path carries its
40
+ * version, and a status line wired to it breaks on the next update — and from there
41
+ * `../lib` is somebody's home directory. So the sibling is tried, then an explicit
42
+ * override, then the plugin is looked up where plugins live.
43
+ *
44
+ * Returns `null` rather than throwing. A status line that cannot find its library
45
+ * prints nothing, which is the same thing it prints when there is no Prowl call to
46
+ * report — and far better than a stack trace under the prompt.
47
+ */
48
+ function resolveLib() {
49
+ const candidates = [
50
+ path.join(__dirname, '..', 'lib'),
51
+ process.env.PROWL_LIB || '',
52
+ ].filter(Boolean);
53
+
54
+ const plugins = path.join(os.homedir(), '.claude', 'plugins');
55
+ // Bounded, breadth-first, and it stops at the first hit: a full walk of a plugin
56
+ // cache on every status-line render would be a widget that costs a frame.
57
+ const roots = [path.join(plugins, 'cache'), path.join(plugins, 'marketplaces')];
58
+ for (const root of roots) {
59
+ try {
60
+ for (const owner of fs.readdirSync(root)) {
61
+ for (const name of fs.readdirSync(path.join(root, owner))) {
62
+ if (!/prowl/i.test(name) && !/prowl/i.test(owner)) continue;
63
+ const dir = path.join(root, owner, name);
64
+ for (const version of fs.readdirSync(dir)) {
65
+ candidates.push(path.join(dir, version, 'lib'));
66
+ }
67
+ }
68
+ }
69
+ } catch (e) { /* no such directory is the normal case on most machines */ }
70
+ }
71
+
72
+ for (const c of candidates) {
73
+ try {
74
+ if (fs.existsSync(path.join(c, 'state.js')) && fs.existsSync(path.join(c, 'render.js'))) return c;
75
+ } catch (e) { /* keep looking */ }
76
+ }
77
+ return null;
78
+ }
79
+
80
+ let raw = '';
81
+ process.stdin.setEncoding('utf8');
82
+ process.stdin.on('data', (c) => { raw += c; });
83
+ process.stdin.on('end', () => {
84
+ try {
85
+ const data = raw.trim().startsWith('{') ? JSON.parse(raw) : {};
86
+
87
+ const LIB = resolveLib();
88
+ if (!LIB) return process.exit(0);
89
+
90
+ const state = require(path.join(LIB, 'state.js'));
91
+ const render = require(path.join(LIB, 'render.js'));
92
+
93
+ const line = render.line(state.read(os.homedir(), data.session_id), {
94
+ now: Date.now(),
95
+ cost: data.cost,
96
+ context: data.context_window,
97
+ // Claude Code sets COLUMNS before running the script; `tput cols` cannot work
98
+ // here because the output is captured rather than attached to the terminal.
99
+ columns: Number(process.env.COLUMNS) || null,
100
+ });
101
+
102
+ if (line) process.stdout.write(`${line}\n`);
103
+ } catch (e) {
104
+ /* A status line that throws would paint a stack trace under the prompt. */
105
+ }
106
+ process.exit(0);
107
+ });
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "prowl-cli",
3
+ "description": "Prowl CLI skill: drive the 448-tool Prowl market-intelligence catalog from the shell with @prowl-ai/cli — no MCP server required.",
4
+ "version": "0.1.1",
5
+ "author": {
6
+ "name": "Prowl",
7
+ "email": "support@prowl.chat",
8
+ "url": "https://prowl.chat/"
9
+ },
10
+ "homepage": "https://prowl.chat/",
11
+ "repository": "https://github.com/PROWL-AI/prowl-skill",
12
+ "license": "MIT"
13
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "prowl-cli",
3
+ "version": "0.1.1",
4
+ "description": "Prowl CLI skill: drive the 448-tool Prowl market-intelligence catalog from the shell with @prowl-ai/cli — no MCP server required.",
5
+ "author": {
6
+ "name": "Prowl",
7
+ "email": "support@prowl.chat",
8
+ "url": "https://prowl.chat/"
9
+ },
10
+ "homepage": "https://prowl.chat/",
11
+ "repository": "https://github.com/PROWL-AI/prowl-skill",
12
+ "license": "MIT",
13
+ "keywords": [
14
+ "prowl",
15
+ "cli",
16
+ "market-intelligence",
17
+ "competitive-analysis",
18
+ "seo",
19
+ "serp",
20
+ "ads"
21
+ ],
22
+ "skills": "./skills/",
23
+ "interface": {
24
+ "displayName": "Prowl CLI",
25
+ "shortDescription": "448 market-intelligence tools from the shell",
26
+ "longDescription": "Teaches the agent the Prowl command-line client (@prowl-ai/cli): install and key handling, the free catalog commands, metered tool calls, analyze tiers with hard cost caps, playbooks, JSON output and exit codes. Useful wherever a shell is available and no MCP server is configured.",
27
+ "developerName": "Prowl",
28
+ "category": "Research",
29
+ "capabilities": [
30
+ "Read",
31
+ "Write"
32
+ ],
33
+ "websiteURL": "https://prowl.chat/",
34
+ "privacyPolicyURL": "https://prowl.chat/privacy/",
35
+ "termsOfServiceURL": "https://prowl.chat/terms/",
36
+ "defaultPrompt": [
37
+ "Search the Prowl tool catalog for backlink tools",
38
+ "Run prowl analyze on a competitor domain",
39
+ "Check my Prowl wallet balance"
40
+ ],
41
+ "brandColor": "#00ff41",
42
+ "screenshots": []
43
+ }
44
+ }
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: prowl-cli
3
+ description: Drives the Prowl CLI (`@prowl-ai/cli`) — a shell client for Prowl exposing 448 market-intelligence tools (SEO, backlinks, 60+ SERP engines, ads, web scraping, AI) plus the full Prowl research pipeline, billed pay-as-you-go from a USD wallet. Use when a shell is available and market data is needed in a script, a CI job or a one-off command — competitor research, SERP and keyword data, backlink profiles, ad creative research, pricing and demand validation — without configuring an MCP server.
4
+ license: MIT
5
+ compatibility: Requires Node.js 18+, the `@prowl-ai/cli` npm package (installed globally or run via npx) and a Prowl API key; makes network calls to prowl.chat.
6
+ metadata:
7
+ author: prowl.chat
8
+ version: "0.1.1"
9
+ ---
10
+
11
+ # Prowl CLI — market intelligence from the shell
12
+
13
+ `@prowl-ai/cli` is the command-line client for [Prowl](https://prowl.chat): one binary that reaches **448 market-intelligence tools** across 15 providers, plus the Prowl analysis pipeline. Every metered call debits a USD wallet.
14
+
15
+ Use this skill when a shell is available. Use the sibling `prowl` plugin instead when the agent should call Prowl as MCP tools inside its own tool loop.
16
+
17
+ ## Setup (one time)
18
+
19
+ Prowl API keys (`prowl_...`) are **billing-bearing** — treat them as secrets and never commit them.
20
+
21
+ ```bash
22
+ npm install -g @prowl-ai/cli
23
+ ```
24
+
25
+ The human generates a key at [prowl.chat](https://prowl.chat) (MCP Home → API keys), then supplies it one of three ways:
26
+
27
+ ```bash
28
+ export PROWL_API_KEY=prowl_... # environment
29
+ prowl tools list --key prowl_... # per-invocation flag
30
+ mkdir -p ~/.prowl && printf %s 'prowl_...' > ~/.prowl/prowl_mcp_token && chmod 600 ~/.prowl/prowl_mcp_token
31
+ ```
32
+
33
+ Never print the key back to the user, never pass it in a command you echo into a log, and never write it into a repo file.
34
+
35
+ Requires Node.js >= 18.
36
+
37
+ ## Commands
38
+
39
+ ```
40
+ prowl auth status | login | logout
41
+ prowl tools list # 448 tools by category (free)
42
+ prowl tools search "<query>" # semantic catalog search (free)
43
+ prowl tools info <tool_name> # input schema + estimated cost (free)
44
+ prowl call <tool_name> --params '<json>' # invoke one tool (wallet debit)
45
+ prowl analyze "<query>" [--tier basic|deep|max] [--playbook <id>] [--session <id>]
46
+ prowl wallet # balance
47
+ prowl version
48
+ ```
49
+
50
+ Global flags: `--json` (one machine-readable JSON document on stdout — always use this when parsing), `--key <k>`, `-h`.
51
+
52
+ Recommended flow: `tools search` → `tools info` (check the cost) → `call`. Reach for `analyze` when the goal is a full report rather than one data point. Pass a stable `--session <id>` across consecutive `analyze` calls to keep the report cache and conversation history scoped to one investigation.
53
+
54
+ ## Tiers and cost control
55
+
56
+ `analyze` defaults to `--tier basic`. Each tier holds a hard provider-cost cap for the run — billing never exceeds the reserved hold.
57
+
58
+ | Tier | Use for | Cap |
59
+ |------|---------|----:|
60
+ | `basic` | one question, fast turnaround | $2.50 |
61
+ | `deep` | full competitive report | $8.00 |
62
+ | `max` | exhaustive, research-grade | $18.00 |
63
+
64
+ Check `prowl tools info <tool>` before a `call` in a loop, and `prowl wallet` before a batch. Confirm with the human before running `--tier max` or a batch that could exceed a few dollars.
65
+
66
+ ## Playbooks
67
+
68
+ `--playbook <id>` forces a fixed, persona-tuned report shape:
69
+
70
+ `geo-visibility-audit` · `competitor-teardown` · `content-engine` · `local-and-reputation` · `mobile-aso` · `amazon-marketplace` · `idea-validation` · `channel-economics-audit`
71
+
72
+ ## Exit codes
73
+
74
+ `0` ok · `1` runtime/tool error · `2` usage · `3` auth (missing or invalid key) · `4` insufficient balance · `5` network/timeout.
75
+
76
+ Handle them: `3` means ask the human for a key, `4` means stop and report the balance rather than retrying, `5` is the only one worth a retry.
77
+
78
+ ## Examples
79
+
80
+ ```bash
81
+ prowl tools search "backlinks"
82
+ prowl tools info majestic_get_back_link_data
83
+ prowl call extract_domain_from_url --params '{"url":"https://stripe.com/pricing"}'
84
+ prowl analyze "competitors of stripe.com" --tier basic --json
85
+ prowl analyze "is there demand for an AI receipt scanner" --playbook idea-validation --tier deep
86
+ ```
87
+
88
+ ## When to use
89
+
90
+ Competitor and market research, SEO/SERP/keyword data, backlink profiles, ad creative and spend research, pricing and demand validation — from a script, a CI job, or a shell one-liner.
91
+
92
+ ## When not to use
93
+
94
+ Tasks answerable from the local codebase, or sessions where the Prowl MCP server is already configured — there the `prowl` plugin's tools are the better path.
95
+
96
+ Full tool reference: [https://prowl.chat/mcp/skill.md](https://prowl.chat/mcp/skill.md)