@hashimkarim/usagestat-linux-x64-gnu 2.0.0-alpha.1
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.
- package/LICENSE +57744 -0
- package/native-manifest.json +1414 -0
- package/package.json +254 -0
- package/plugins/abacus-ai/icon.svg +18 -0
- package/plugins/abacus-ai/plugin.js +121 -0
- package/plugins/abacus-ai/plugin.json +17 -0
- package/plugins/alibaba/icon-color.svg +1 -0
- package/plugins/alibaba/icon.svg +1 -0
- package/plugins/alibaba/plugin.js +322 -0
- package/plugins/alibaba/plugin.json +20 -0
- package/plugins/alibaba-token-plan/icon-color.svg +1 -0
- package/plugins/alibaba-token-plan/icon.svg +1 -0
- package/plugins/alibaba-token-plan/plugin.js +544 -0
- package/plugins/alibaba-token-plan/plugin.json +19 -0
- package/plugins/amp/icon-color.svg +1 -0
- package/plugins/amp/icon-conflict.svg +6 -0
- package/plugins/amp/icon.svg +1 -0
- package/plugins/amp/plugin.js +175 -0
- package/plugins/amp/plugin.json +16 -0
- package/plugins/antigravity/icon-color.svg +1 -0
- package/plugins/antigravity/icon-conflict.svg +3 -0
- package/plugins/antigravity/icon.svg +1 -0
- package/plugins/antigravity/plugin.js +739 -0
- package/plugins/antigravity/plugin.json +31 -0
- package/plugins/antigravity-cli/icon.svg +3 -0
- package/plugins/antigravity-cli/plugin.js +570 -0
- package/plugins/antigravity-cli/plugin.json +16 -0
- package/plugins/antigravity-ide/icon.svg +3 -0
- package/plugins/antigravity-ide/plugin.js +257 -0
- package/plugins/antigravity-ide/plugin.json +29 -0
- package/plugins/augment/icon.svg +14 -0
- package/plugins/augment/plugin.js +59 -0
- package/plugins/augment/plugin.json +18 -0
- package/plugins/aws-bedrock/icon-color.svg +1 -0
- package/plugins/aws-bedrock/icon.svg +1 -0
- package/plugins/aws-bedrock/plugin.js +206 -0
- package/plugins/aws-bedrock/plugin.json +18 -0
- package/plugins/azure-openai/icon-color.svg +1 -0
- package/plugins/azure-openai/icon.svg +1 -0
- package/plugins/azure-openai/plugin.js +145 -0
- package/plugins/azure-openai/plugin.json +17 -0
- package/plugins/chutes/icon.svg +7 -0
- package/plugins/chutes/plugin.js +236 -0
- package/plugins/chutes/plugin.json +19 -0
- package/plugins/claude/icon-color.svg +1 -0
- package/plugins/claude/icon.svg +1 -0
- package/plugins/claude/plugin.js +1723 -0
- package/plugins/claude/plugin.json +30 -0
- package/plugins/codebuff/icon.svg +92 -0
- package/plugins/codebuff/plugin.js +113 -0
- package/plugins/codebuff/plugin.json +17 -0
- package/plugins/codex/icon-color.svg +5 -0
- package/plugins/codex/icon.svg +1 -0
- package/plugins/codex/plugin.js +1009 -0
- package/plugins/codex/plugin.json +82 -0
- package/plugins/command-code/icon.svg +3 -0
- package/plugins/command-code/plugin.js +184 -0
- package/plugins/command-code/plugin.json +20 -0
- package/plugins/copilot/icon-color.svg +1 -0
- package/plugins/copilot/icon.svg +1 -0
- package/plugins/copilot/plugin.js +684 -0
- package/plugins/copilot/plugin.json +21 -0
- package/plugins/crof/icon.svg +3 -0
- package/plugins/crof/plugin.js +53 -0
- package/plugins/crof/plugin.json +17 -0
- package/plugins/crofai/icon.svg +16 -0
- package/plugins/crofai/plugin.js +81 -0
- package/plugins/crofai/plugin.json +18 -0
- package/plugins/cursor/icon-conflict.svg +3 -0
- package/plugins/cursor/icon.svg +1 -0
- package/plugins/cursor/plugin.js +1379 -0
- package/plugins/cursor/plugin.json +23 -0
- package/plugins/cursor-nightly/icon.png +0 -0
- package/plugins/cursor-nightly/plugin.js +1379 -0
- package/plugins/cursor-nightly/plugin.json +23 -0
- package/plugins/deepgram/icon.svg +1 -0
- package/plugins/deepgram/plugin.js +20 -0
- package/plugins/deepgram/plugin.json +17 -0
- package/plugins/deepseek/icon-color.svg +1 -0
- package/plugins/deepseek/icon.svg +1 -0
- package/plugins/deepseek/plugin.js +71 -0
- package/plugins/deepseek/plugin.json +17 -0
- package/plugins/devin/icon.svg +5 -0
- package/plugins/devin/plugin.js +341 -0
- package/plugins/devin/plugin.json +16 -0
- package/plugins/doubao/icon-color.svg +1 -0
- package/plugins/doubao/icon.svg +1 -0
- package/plugins/doubao/plugin.js +92 -0
- package/plugins/doubao/plugin.json +17 -0
- package/plugins/droid/icon.svg +1 -0
- package/plugins/droid/plugin.json +20 -0
- package/plugins/elevenlabs/icon.svg +4 -0
- package/plugins/elevenlabs/plugin.js +48 -0
- package/plugins/elevenlabs/plugin.json +18 -0
- package/plugins/factory/icon-conflict.svg +1 -0
- package/plugins/factory/icon.svg +1 -0
- package/plugins/factory/plugin.js +487 -0
- package/plugins/factory/plugin.json +15 -0
- package/plugins/fireworks-ai/icon.svg +6 -0
- package/plugins/fireworks-ai/plugin.js +370 -0
- package/plugins/fireworks-ai/plugin.json +19 -0
- package/plugins/gemini/icon-color.svg +1 -0
- package/plugins/gemini/icon.svg +1 -0
- package/plugins/gemini/plugin.js +460 -0
- package/plugins/gemini/plugin.json +16 -0
- package/plugins/grok/icon.svg +1 -0
- package/plugins/grok/plugin.js +297 -0
- package/plugins/grok/plugin.json +19 -0
- package/plugins/groqcloud/icon.svg +1 -0
- package/plugins/groqcloud/plugin.js +117 -0
- package/plugins/groqcloud/plugin.json +19 -0
- package/plugins/jetbrains-ai-assistant/icon-color.svg +14 -0
- package/plugins/jetbrains-ai-assistant/icon-conflict.svg +3 -0
- package/plugins/jetbrains-ai-assistant/icon.svg +14 -0
- package/plugins/jetbrains-ai-assistant/plugin.js +313 -0
- package/plugins/jetbrains-ai-assistant/plugin.json +16 -0
- package/plugins/kilo/icon.svg +1 -0
- package/plugins/kilo/plugin.js +111 -0
- package/plugins/kilo/plugin.json +19 -0
- package/plugins/kimi/icon-color.svg +1 -0
- package/plugins/kimi/icon-conflict.svg +3 -0
- package/plugins/kimi/icon.svg +1 -0
- package/plugins/kimi/plugin.js +358 -0
- package/plugins/kimi/plugin.json +15 -0
- package/plugins/kimi-k2/icon-color.svg +1 -0
- package/plugins/kimi-k2/icon.svg +1 -0
- package/plugins/kimi-k2/plugin.js +58 -0
- package/plugins/kimi-k2/plugin.json +18 -0
- package/plugins/kiro/icon-color.svg +5 -0
- package/plugins/kiro/icon-conflict.svg +1 -0
- package/plugins/kiro/icon.svg +4 -0
- package/plugins/kiro/plugin.js +390 -0
- package/plugins/kiro/plugin.json +16 -0
- package/plugins/litellm/icon.svg +16 -0
- package/plugins/litellm/plugin.js +199 -0
- package/plugins/litellm/plugin.json +16 -0
- package/plugins/llm-proxy/icon.svg +1 -0
- package/plugins/llm-proxy/plugin.js +40 -0
- package/plugins/llm-proxy/plugin.json +17 -0
- package/plugins/manus/icon.svg +6 -0
- package/plugins/manus/plugin.js +50 -0
- package/plugins/manus/plugin.json +17 -0
- package/plugins/mimo/icon.svg +1 -0
- package/plugins/mimo/plugin.js +177 -0
- package/plugins/mimo/plugin.json +19 -0
- package/plugins/minimax/icon-color.svg +1 -0
- package/plugins/minimax/icon-conflict.svg +4 -0
- package/plugins/minimax/icon.svg +1 -0
- package/plugins/minimax/plugin.js +453 -0
- package/plugins/minimax/plugin.json +14 -0
- package/plugins/mistral/icon-color.svg +1 -0
- package/plugins/mistral/icon.svg +1 -0
- package/plugins/mistral/plugin.js +152 -0
- package/plugins/mistral/plugin.json +18 -0
- package/plugins/moonshot/icon.svg +1 -0
- package/plugins/moonshot/plugin.js +43 -0
- package/plugins/moonshot/plugin.json +16 -0
- package/plugins/nanogpt/icon-color.svg +17 -0
- package/plugins/nanogpt/icon.svg +10 -0
- package/plugins/nanogpt/plugin.js +80 -0
- package/plugins/nanogpt/plugin.json +17 -0
- package/plugins/neuralwatt/icon.svg +3 -0
- package/plugins/neuralwatt/plugin.js +152 -0
- package/plugins/neuralwatt/plugin.json +17 -0
- package/plugins/ollama/icon.svg +1 -0
- package/plugins/ollama/plugin.js +92 -0
- package/plugins/ollama/plugin.json +20 -0
- package/plugins/openai-api/icon.svg +1 -0
- package/plugins/openai-api/plugin.js +533 -0
- package/plugins/openai-api/plugin.json +26 -0
- package/plugins/opencode/icon.svg +1 -0
- package/plugins/opencode/plugin.js +342 -0
- package/plugins/opencode/plugin.json +19 -0
- package/plugins/opencode-go/icon-conflict.svg +1 -0
- package/plugins/opencode-go/icon.svg +1 -0
- package/plugins/opencode-go/plugin.js +617 -0
- package/plugins/opencode-go/plugin.json +50 -0
- package/plugins/openrouter/icon.svg +1 -0
- package/plugins/openrouter/plugin.js +156 -0
- package/plugins/openrouter/plugin.json +8 -0
- package/plugins/perplexity/icon-color.svg +1 -0
- package/plugins/perplexity/icon-conflict.svg +1 -0
- package/plugins/perplexity/icon.svg +1 -0
- package/plugins/perplexity/plugin.js +425 -0
- package/plugins/perplexity/plugin.json +19 -0
- package/plugins/poe/icon.svg +1 -0
- package/plugins/poe/plugin.js +190 -0
- package/plugins/poe/plugin.json +19 -0
- package/plugins/stepfun/icon-color.svg +1 -0
- package/plugins/stepfun/icon.svg +1 -0
- package/plugins/stepfun/plugin.js +155 -0
- package/plugins/stepfun/plugin.json +18 -0
- package/plugins/synthetic/icon-conflict.svg +12 -0
- package/plugins/synthetic/icon.svg +14 -0
- package/plugins/synthetic/plugin.js +331 -0
- package/plugins/synthetic/plugin.json +23 -0
- package/plugins/t3chat/icon-color.svg +28 -0
- package/plugins/t3chat/icon.svg +4 -0
- package/plugins/t3chat/plugin.js +243 -0
- package/plugins/t3chat/plugin.json +20 -0
- package/plugins/venice/icon-color.svg +1 -0
- package/plugins/venice/icon.svg +1 -0
- package/plugins/venice/plugin.js +69 -0
- package/plugins/venice/plugin.json +16 -0
- package/plugins/vertex-ai/icon-color.svg +1 -0
- package/plugins/vertex-ai/icon.svg +1 -0
- package/plugins/vertex-ai/plugin.js +172 -0
- package/plugins/vertex-ai/plugin.json +18 -0
- package/plugins/warp/icon-color.svg +10 -0
- package/plugins/warp/icon.svg +6 -0
- package/plugins/warp/plugin.js +108 -0
- package/plugins/warp/plugin.json +18 -0
- package/plugins/windsurf/icon-conflict.svg +3 -0
- package/plugins/windsurf/icon.svg +1 -0
- package/plugins/windsurf/plugin.js +208 -0
- package/plugins/windsurf/plugin.json +16 -0
- package/plugins/zai/icon-conflict.svg +5 -0
- package/plugins/zai/icon.svg +1 -0
- package/plugins/zai/plugin.js +188 -0
- package/plugins/zai/plugin.json +16 -0
- package/plugins/zed/icon.svg +4 -0
- package/plugins/zed/plugin.js +270 -0
- package/plugins/zed/plugin.json +19 -0
- package/usagestat +0 -0
- package/usagestatd +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
globalThis.__usagestat_plugin = {
|
|
2
|
+
probe(ctx) {
|
|
3
|
+
const apiKey = readApiKey(ctx);
|
|
4
|
+
const apiBase = readApiBase(ctx);
|
|
5
|
+
|
|
6
|
+
const credits = requestJson(ctx, {
|
|
7
|
+
method: "GET",
|
|
8
|
+
url: apiBase + "/credits",
|
|
9
|
+
headers: {
|
|
10
|
+
"Authorization": "Bearer " + apiKey,
|
|
11
|
+
"Accept": "application/json"
|
|
12
|
+
},
|
|
13
|
+
timeoutMs: 15000
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (credits.resp.status === 401 || credits.resp.status === 403) {
|
|
17
|
+
throw "OpenRouter API key is invalid.";
|
|
18
|
+
}
|
|
19
|
+
if (credits.resp.status < 200 || credits.resp.status >= 300) {
|
|
20
|
+
throw "OpenRouter credits request failed (HTTP " + credits.resp.status + ").";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const data = credits.json && credits.json.data ? credits.json.data : {};
|
|
24
|
+
const totalCredits = readNumber(data.total_credits);
|
|
25
|
+
const totalUsage = readNumber(data.total_usage);
|
|
26
|
+
|
|
27
|
+
if (totalCredits === null || totalUsage === null) {
|
|
28
|
+
throw "OpenRouter credits response did not include usage totals.";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const remaining = Math.max(0, totalCredits - totalUsage);
|
|
32
|
+
const usedPercent = totalCredits > 0 ? clamp((totalUsage / totalCredits) * 100, 0, 100) : 0;
|
|
33
|
+
const metrics = [
|
|
34
|
+
{
|
|
35
|
+
type: "progress",
|
|
36
|
+
label: "Credits",
|
|
37
|
+
used: round2(totalUsage),
|
|
38
|
+
limit: round2(totalCredits),
|
|
39
|
+
format: { kind: "dollars" }
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: "text",
|
|
43
|
+
label: "Balance",
|
|
44
|
+
value: "$" + round2(remaining).toFixed(2)
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: "text",
|
|
48
|
+
label: "Used",
|
|
49
|
+
value: "$" + round2(totalUsage).toFixed(2) + " (" + round1(usedPercent) + "%)"
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const key = requestJson(ctx, {
|
|
55
|
+
method: "GET",
|
|
56
|
+
url: apiBase + "/key",
|
|
57
|
+
headers: {
|
|
58
|
+
"Authorization": "Bearer " + apiKey,
|
|
59
|
+
"Accept": "application/json"
|
|
60
|
+
},
|
|
61
|
+
timeoutMs: 15000
|
|
62
|
+
});
|
|
63
|
+
if (key.resp.status >= 200 && key.resp.status < 300 && key.json && key.json.data) {
|
|
64
|
+
appendKeyMetrics(metrics, key.json.data);
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
ctx.host.log.warn("OpenRouter key details request failed: " + String(error));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
displayName: "OpenRouter",
|
|
72
|
+
source: "api",
|
|
73
|
+
metrics
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function readApiKey(ctx) {
|
|
79
|
+
const value = ctx.host.env.get("OPENROUTER_API_KEY");
|
|
80
|
+
if (typeof value === "string" && value.trim()) {
|
|
81
|
+
return value.trim();
|
|
82
|
+
}
|
|
83
|
+
throw "No OPENROUTER_API_KEY found.";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function readApiBase(ctx) {
|
|
87
|
+
const value = ctx.host.env.get("OPENROUTER_API_BASE");
|
|
88
|
+
if (typeof value === "string" && value.trim()) {
|
|
89
|
+
return value.trim().replace(/\/+$/, "");
|
|
90
|
+
}
|
|
91
|
+
return "https://openrouter.ai/api/v1/auth";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function requestJson(ctx, request) {
|
|
95
|
+
let resp;
|
|
96
|
+
try {
|
|
97
|
+
resp = ctx.host.http.request(request);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
ctx.host.log.error("HTTP request failed: " + String(error));
|
|
100
|
+
throw "OpenRouter request failed. Check your connection.";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let json = null;
|
|
104
|
+
try {
|
|
105
|
+
json = resp.bodyText ? JSON.parse(resp.bodyText) : null;
|
|
106
|
+
} catch {
|
|
107
|
+
throw "OpenRouter response was not valid JSON.";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { resp, json };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function appendKeyMetrics(metrics, data) {
|
|
114
|
+
const limit = readNumber(data.limit);
|
|
115
|
+
const usage = readNumber(data.usage);
|
|
116
|
+
if (limit !== null && usage !== null && limit > 0) {
|
|
117
|
+
metrics.push({
|
|
118
|
+
type: "progress",
|
|
119
|
+
label: "Key Limit",
|
|
120
|
+
used: round2(usage),
|
|
121
|
+
limit: round2(limit),
|
|
122
|
+
format: { kind: "dollars" }
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const rateLimit = data.rate_limit || data.rateLimit;
|
|
127
|
+
if (rateLimit && rateLimit.requests !== undefined) {
|
|
128
|
+
const interval = typeof rateLimit.interval === "string" ? " / " + rateLimit.interval : "";
|
|
129
|
+
metrics.push({
|
|
130
|
+
type: "text",
|
|
131
|
+
label: "Rate Limit",
|
|
132
|
+
value: String(rateLimit.requests) + " requests" + interval
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function readNumber(value) {
|
|
138
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
139
|
+
if (typeof value === "string" && value.trim()) {
|
|
140
|
+
const parsed = Number(value);
|
|
141
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function clamp(value, min, max) {
|
|
147
|
+
return Math.max(min, Math.min(max, value));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function round1(value) {
|
|
151
|
+
return Math.round(value * 10) / 10;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function round2(value) {
|
|
155
|
+
return Math.round(value * 100) / 100;
|
|
156
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Perplexity</title><path d="M19.785 0v7.272H22.5V17.62h-2.935V24l-7.037-6.194v6.145h-1.091v-6.152L4.392 24v-6.465H1.5V7.188h2.884V0l7.053 6.494V.19h1.09v6.49L19.786 0zm-7.257 9.044v7.319l5.946 5.234V14.44l-5.946-5.397zm-1.099-.08l-5.946 5.398v7.235l5.946-5.234V8.965zm8.136 7.58h1.844V8.349H13.46l6.105 5.54v2.655zm-8.982-8.28H2.59v8.195h1.8v-2.576l6.192-5.62zM5.475 2.476v4.71h5.115l-5.115-4.71zm13.219 0l-5.115 4.71h5.115v-4.71z" fill="#22B8CD" fill-rule="nonzero"></path></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg height="100" style="flex:none;line-height:1" viewBox="0 0 24 24" width="100" xmlns="http://www.w3.org/2000/svg"><title>Perplexity</title><path d="M19.785 0v7.272H22.5V17.62h-2.935V24l-7.037-6.194v6.145h-1.091v-6.152L4.392 24v-6.465H1.5V7.188h2.884V0l7.053 6.494V.19h1.09v6.49L19.786 0zm-7.257 9.044v7.319l5.946 5.234V14.44l-5.946-5.397zm-1.099-.08l-5.946 5.398v7.235l5.946-5.234V8.965zm8.136 7.58h1.844V8.349H13.46l6.105 5.54v2.655zm-8.982-8.28H2.59v8.195h1.8v-2.576l6.192-5.62zM5.475 2.476v4.71h5.115l-5.115-4.71zm13.219 0l-5.115 4.71h5.115v-4.71z" fill="currentColor" fill-rule="nonzero"></path></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Perplexity</title><path d="M19.785 0v7.272H22.5V17.62h-2.935V24l-7.037-6.194v6.145h-1.091v-6.152L4.392 24v-6.465H1.5V7.188h2.884V0l7.053 6.494V.19h1.09v6.49L19.786 0zm-7.257 9.044v7.319l5.946 5.234V14.44l-5.946-5.397zm-1.099-.08l-5.946 5.398v7.235l5.946-5.234V8.965zm8.136 7.58h1.844V8.349H13.46l6.105 5.54v2.655zm-8.982-8.28H2.59v8.195h1.8v-2.576l6.192-5.62zM5.475 2.476v4.71h5.115l-5.115-4.71zm13.219 0l-5.115 4.71h5.115v-4.71z"></path></svg>
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
const LOCAL_USER_ENDPOINT = "https://www.perplexity.ai/api/user"
|
|
3
|
+
const REST_API_BASE = "https://www.perplexity.ai/rest/pplx-api/v2"
|
|
4
|
+
const REST_GROUPS_ENDPOINT = REST_API_BASE + "/groups"
|
|
5
|
+
const RATE_LIMIT_ENDPOINT = "https://www.perplexity.ai/rest/rate-limit/all"
|
|
6
|
+
|
|
7
|
+
const LOCAL_CACHE_DB_PATHS = [
|
|
8
|
+
"~/Library/Containers/ai.perplexity.mac/Data/Library/Caches/ai.perplexity.mac/Cache.db",
|
|
9
|
+
"~/Library/Caches/ai.perplexity.mac/Cache.db",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
// Only need request_object; receiver body is optional and can be malformed.
|
|
13
|
+
const LOCAL_SESSION_SQL =
|
|
14
|
+
"SELECT hex(b.request_object) AS requestHex " +
|
|
15
|
+
"FROM cfurl_cache_response r " +
|
|
16
|
+
"JOIN cfurl_cache_blob_data b ON b.entry_ID = r.entry_ID " +
|
|
17
|
+
"WHERE r.request_key = '" + LOCAL_USER_ENDPOINT + "' " +
|
|
18
|
+
"ORDER BY r.entry_ID DESC LIMIT 1;"
|
|
19
|
+
|
|
20
|
+
const BEARER_HEX_PREFIX = "42656172657220" // "Bearer "
|
|
21
|
+
const ASK_UA_HEX_PREFIX = "41736B2F" // Ask/
|
|
22
|
+
const MACOS_DEVICE_ID_HEX_PREFIX = "6D61636F733A" // macos:
|
|
23
|
+
const MAX_REQUEST_FIELD_LENGTH = 220
|
|
24
|
+
const RATE_LIMIT_CATEGORIES = [
|
|
25
|
+
{ key: "remaining_pro", label: "Queries" },
|
|
26
|
+
{ key: "remaining_research", label: "Deep Research" },
|
|
27
|
+
{ key: "remaining_labs", label: "Labs" },
|
|
28
|
+
{ key: "remaining_agentic_research", label: "Agentic Research" },
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
function readNumberField(obj, keys) {
|
|
32
|
+
if (!obj || typeof obj !== "object") return null
|
|
33
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
34
|
+
const n = Number(obj[keys[i]])
|
|
35
|
+
if (Number.isFinite(n)) return n
|
|
36
|
+
}
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseMoneyNumber(value) {
|
|
41
|
+
if (value === null || value === undefined) return null
|
|
42
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null
|
|
43
|
+
if (typeof value !== "string") return null
|
|
44
|
+
const trimmed = value.trim()
|
|
45
|
+
if (!trimmed) return null
|
|
46
|
+
const cleaned = trimmed.replace(/[$,]/g, "")
|
|
47
|
+
const n = Number(cleaned)
|
|
48
|
+
return Number.isFinite(n) ? n : null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readMoneyLike(value) {
|
|
52
|
+
const direct = parseMoneyNumber(value)
|
|
53
|
+
if (direct !== null) return direct
|
|
54
|
+
if (!value || typeof value !== "object") return null
|
|
55
|
+
|
|
56
|
+
const cents = readNumberField(value, ["cents", "amount_cents", "amountCents", "value_cents", "valueCents"])
|
|
57
|
+
if (cents !== null) {
|
|
58
|
+
const dollars = cents / 100
|
|
59
|
+
return Number.isFinite(dollars) ? dollars : null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
readNumberField(value, ["usd", "amount_usd", "amountUsd", "value_usd", "valueUsd"]) ??
|
|
64
|
+
readNumberField(value, ["amount", "value", "balance", "remaining", "available"])
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isAllowedAuthByte(byte) {
|
|
69
|
+
return (
|
|
70
|
+
(byte >= 0x30 && byte <= 0x39) ||
|
|
71
|
+
(byte >= 0x41 && byte <= 0x5a) ||
|
|
72
|
+
(byte >= 0x61 && byte <= 0x7a) ||
|
|
73
|
+
byte === 0x2e ||
|
|
74
|
+
byte === 0x2d ||
|
|
75
|
+
byte === 0x5f
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function extractAuthToken(requestHex) {
|
|
80
|
+
if (typeof requestHex !== "string") return null
|
|
81
|
+
const upper = requestHex.trim().toUpperCase()
|
|
82
|
+
if (!upper) return null
|
|
83
|
+
const idx = upper.indexOf(BEARER_HEX_PREFIX)
|
|
84
|
+
if (idx === -1) return null
|
|
85
|
+
const start = idx + BEARER_HEX_PREFIX.length
|
|
86
|
+
let token = ""
|
|
87
|
+
for (let i = start; i + 1 < upper.length; i += 2) {
|
|
88
|
+
const byte = parseInt(upper.slice(i, i + 2), 16)
|
|
89
|
+
if (!Number.isFinite(byte)) break
|
|
90
|
+
if (!isAllowedAuthByte(byte)) break
|
|
91
|
+
if (byte === 0x5f) {
|
|
92
|
+
// Stop before bplist marker bytes; avoids capturing '_' that precedes plist int markers.
|
|
93
|
+
const next = i + 3 < upper.length ? parseInt(upper.slice(i + 2, i + 4), 16) : NaN
|
|
94
|
+
if (next === 0x10 || next === 0x11 || next === 0x12 || next === 0x13 || next === 0x14) break
|
|
95
|
+
}
|
|
96
|
+
token += String.fromCharCode(byte)
|
|
97
|
+
}
|
|
98
|
+
const dots = (token.match(/\./g) || []).length
|
|
99
|
+
return dots >= 2 ? token : null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isPrintableAscii(byte) {
|
|
103
|
+
return byte >= 0x20 && byte <= 0x7e
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function extractPrintableField(requestHex, prefixHex) {
|
|
107
|
+
if (typeof requestHex !== "string") return null
|
|
108
|
+
const upper = requestHex.trim().toUpperCase()
|
|
109
|
+
if (!upper) return null
|
|
110
|
+
const idx = upper.indexOf(prefixHex)
|
|
111
|
+
if (idx === -1) return null
|
|
112
|
+
let out = ""
|
|
113
|
+
for (let i = idx; i + 1 < upper.length && out.length < MAX_REQUEST_FIELD_LENGTH; i += 2) {
|
|
114
|
+
const byte = parseInt(upper.slice(i, i + 2), 16)
|
|
115
|
+
if (!Number.isFinite(byte) || !isPrintableAscii(byte)) break
|
|
116
|
+
out += String.fromCharCode(byte)
|
|
117
|
+
}
|
|
118
|
+
return out ? out : null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function askAppVersionFromUserAgent(userAgent) {
|
|
122
|
+
if (typeof userAgent !== "string") return null
|
|
123
|
+
const m = /^Ask\/([^/]+)/.exec(userAgent.trim())
|
|
124
|
+
return m && m[1] ? m[1] : null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function makeRestHeaderOverrides(session) {
|
|
128
|
+
const headers = {
|
|
129
|
+
Accept: "*/*",
|
|
130
|
+
"User-Agent": (session && session.userAgent) || "Ask/0 (macOS) isiOSOnMac/false",
|
|
131
|
+
"X-Client-Name": "Perplexity-Mac",
|
|
132
|
+
"X-App-ApiVersion": "2.17",
|
|
133
|
+
"X-App-ApiClient": "macos",
|
|
134
|
+
"X-Client-Env": "production",
|
|
135
|
+
}
|
|
136
|
+
if (session && session.appVersion) headers["X-App-Version"] = session.appVersion
|
|
137
|
+
if (session && session.deviceId) headers["X-Device-ID"] = session.deviceId
|
|
138
|
+
return headers
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function fetchJsonOptional(ctx, url, authToken, extraHeaders) {
|
|
142
|
+
if (!authToken) return null
|
|
143
|
+
let resp
|
|
144
|
+
try {
|
|
145
|
+
const headers = {
|
|
146
|
+
Authorization: "Bearer " + authToken,
|
|
147
|
+
Accept: "application/json",
|
|
148
|
+
"User-Agent": "OpenUsage",
|
|
149
|
+
}
|
|
150
|
+
if (extraHeaders && typeof extraHeaders === "object") for (const k in extraHeaders) headers[k] = extraHeaders[k]
|
|
151
|
+
resp = ctx.util.request({ method: "GET", url: url, headers: headers, timeoutMs: 10000 })
|
|
152
|
+
} catch (e) {
|
|
153
|
+
ctx.host.log.warn("request failed (" + url + "): " + String(e))
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
if (ctx.util.isAuthStatus(resp.status)) {
|
|
157
|
+
if (resp.status === 403 && typeof resp.bodyText === "string" && resp.bodyText.indexOf("Just a moment") !== -1) {
|
|
158
|
+
ctx.host.log.warn("cloudflare challenge (try opening perplexity.ai in a browser once)")
|
|
159
|
+
}
|
|
160
|
+
ctx.host.log.warn("request unauthorized (" + url + "): status=" + String(resp.status))
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
164
|
+
ctx.host.log.warn("request returned status " + String(resp.status) + " (" + url + ")")
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
const parsed = ctx.util.tryParseJson(resp.bodyText)
|
|
168
|
+
if (!parsed || typeof parsed !== "object") {
|
|
169
|
+
ctx.host.log.warn("request returned invalid JSON (" + url + ")")
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
return parsed
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function readGroupId(value) {
|
|
176
|
+
if (value === null || value === undefined) return null
|
|
177
|
+
if (typeof value === "string") {
|
|
178
|
+
const trimmed = value.trim()
|
|
179
|
+
return trimmed ? trimmed : null
|
|
180
|
+
}
|
|
181
|
+
const n = Number(value)
|
|
182
|
+
if (Number.isFinite(n)) return String(Math.floor(n))
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function pickGroupId(groupsJson) {
|
|
187
|
+
const tryFromObj = (obj) => {
|
|
188
|
+
if (!obj || typeof obj !== "object") return null
|
|
189
|
+
return (
|
|
190
|
+
readGroupId(obj.api_org_id) ||
|
|
191
|
+
readGroupId(obj.apiOrgId) ||
|
|
192
|
+
readGroupId(obj.org_id) ||
|
|
193
|
+
readGroupId(obj.orgId) ||
|
|
194
|
+
readGroupId(obj.id) ||
|
|
195
|
+
readGroupId(obj.group_id) ||
|
|
196
|
+
readGroupId(obj.groupId)
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const tryFromArray = (arr) => {
|
|
201
|
+
if (!Array.isArray(arr)) return null
|
|
202
|
+
let first = null
|
|
203
|
+
for (let i = 0; i < arr.length; i += 1) {
|
|
204
|
+
const item = arr[i]
|
|
205
|
+
const id = tryFromObj(item)
|
|
206
|
+
if (!id) continue
|
|
207
|
+
if (!first) first = id
|
|
208
|
+
if (item && (item.is_default_org === true || item.isDefaultOrg === true)) return id
|
|
209
|
+
}
|
|
210
|
+
return first
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (Array.isArray(groupsJson)) return tryFromArray(groupsJson)
|
|
214
|
+
if (!groupsJson || typeof groupsJson !== "object") return null
|
|
215
|
+
|
|
216
|
+
const direct = tryFromObj(groupsJson)
|
|
217
|
+
if (direct) return direct
|
|
218
|
+
|
|
219
|
+
const keys = ["orgs", "groups", "results", "items", "data"]
|
|
220
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
221
|
+
const id = tryFromArray(groupsJson[keys[i]])
|
|
222
|
+
if (id) return id
|
|
223
|
+
}
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function readBalanceUsd(group) {
|
|
228
|
+
const wrappers = ["apiOrganization", "api_organization", "group", "org", "organization", "data", "result", "item"]
|
|
229
|
+
const keys = ["balance_usd", "balanceUsd", "balance", "pending_balance", "pendingBalance"]
|
|
230
|
+
|
|
231
|
+
const tryNode = (node) => {
|
|
232
|
+
if (!node) return null
|
|
233
|
+
if (Array.isArray(node)) {
|
|
234
|
+
for (let i = 0; i < node.length; i += 1) {
|
|
235
|
+
const n = tryNode(node[i])
|
|
236
|
+
if (n !== null) return n
|
|
237
|
+
}
|
|
238
|
+
return null
|
|
239
|
+
}
|
|
240
|
+
if (typeof node !== "object") return null
|
|
241
|
+
|
|
242
|
+
for (let i = 0; i < wrappers.length; i += 1) {
|
|
243
|
+
const n = tryNode(node[wrappers[i]])
|
|
244
|
+
if (n !== null) return n
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let n = readNumberField(node, keys)
|
|
248
|
+
if (n !== null) return n
|
|
249
|
+
|
|
250
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
251
|
+
n = readMoneyLike(node[keys[i]])
|
|
252
|
+
if (n !== null) return n
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const nested = [node.customerInfo, node.wallet, node.billing, node.usage, node.account, node.balances]
|
|
256
|
+
for (let i = 0; i < nested.length; i += 1) {
|
|
257
|
+
n = tryNode(nested[i])
|
|
258
|
+
if (n !== null) return n
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
for (const k in node) if (/(balance|credit|wallet|prepaid|available)/i.test(k)) {
|
|
262
|
+
n = readMoneyLike(node[k])
|
|
263
|
+
if (n !== null) return n
|
|
264
|
+
}
|
|
265
|
+
return null
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return tryNode(group)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function sumUsageCostUsd(usageAnalytics) {
|
|
272
|
+
if (!Array.isArray(usageAnalytics)) return null
|
|
273
|
+
let costSum = 0
|
|
274
|
+
let hasCost = false
|
|
275
|
+
let hasValidMeter = usageAnalytics.length === 0
|
|
276
|
+
for (let i = 0; i < usageAnalytics.length; i += 1) {
|
|
277
|
+
const meter = usageAnalytics[i]
|
|
278
|
+
if (!meter || typeof meter !== "object") continue
|
|
279
|
+
const summaries = meter.meter_event_summaries || meter.meterEventSummaries
|
|
280
|
+
if (!Array.isArray(summaries)) continue
|
|
281
|
+
if (summaries.length === 0) hasValidMeter = true
|
|
282
|
+
for (let j = 0; j < summaries.length; j += 1) {
|
|
283
|
+
const s = summaries[j]
|
|
284
|
+
if (!s || typeof s !== "object") continue
|
|
285
|
+
const c = Number(s.cost)
|
|
286
|
+
if (Number.isFinite(c)) {
|
|
287
|
+
costSum += c
|
|
288
|
+
hasCost = true
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return (hasCost || hasValidMeter) ? costSum : null
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function detectPlanLabel(customerInfo) {
|
|
296
|
+
if (!customerInfo || typeof customerInfo !== "object") return null
|
|
297
|
+
if (customerInfo.is_max === true || customerInfo.subscription_tier === "max") return "Max"
|
|
298
|
+
if (customerInfo.is_pro === true) return "Pro"
|
|
299
|
+
return null
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function buildRateLimitLines(ctx, rateLimits) {
|
|
303
|
+
if (!rateLimits || typeof rateLimits !== "object") return []
|
|
304
|
+
const lines = []
|
|
305
|
+
for (let i = 0; i < RATE_LIMIT_CATEGORIES.length; i += 1) {
|
|
306
|
+
const category = RATE_LIMIT_CATEGORIES[i]
|
|
307
|
+
const val = rateLimits[category.key]
|
|
308
|
+
if (val === undefined || val === null) continue
|
|
309
|
+
const n = Number(val)
|
|
310
|
+
if (!Number.isFinite(n)) continue
|
|
311
|
+
lines.push(ctx.line.text({
|
|
312
|
+
label: category.label,
|
|
313
|
+
value: String(Math.max(0, Math.floor(n))) + " remaining",
|
|
314
|
+
}))
|
|
315
|
+
}
|
|
316
|
+
return lines
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function queryLocalSessionFromCache(ctx, dbPath) {
|
|
320
|
+
let rows
|
|
321
|
+
try {
|
|
322
|
+
const json = ctx.host.sqlite.query(dbPath, LOCAL_SESSION_SQL)
|
|
323
|
+
rows = ctx.util.tryParseJson(json)
|
|
324
|
+
} catch (e) {
|
|
325
|
+
ctx.host.log.warn("local sqlite read failed (" + dbPath + "): " + String(e))
|
|
326
|
+
return null
|
|
327
|
+
}
|
|
328
|
+
if (!Array.isArray(rows) || rows.length === 0) return null
|
|
329
|
+
|
|
330
|
+
const row = rows[0] || {}
|
|
331
|
+
const requestHex = typeof row.requestHex === "string" ? row.requestHex : null
|
|
332
|
+
if (!requestHex) return null
|
|
333
|
+
|
|
334
|
+
const authToken = extractAuthToken(requestHex)
|
|
335
|
+
if (!authToken) return null
|
|
336
|
+
|
|
337
|
+
const userAgent = extractPrintableField(requestHex, ASK_UA_HEX_PREFIX)
|
|
338
|
+
const appVersion = askAppVersionFromUserAgent(userAgent)
|
|
339
|
+
const deviceId = extractPrintableField(requestHex, MACOS_DEVICE_ID_HEX_PREFIX)
|
|
340
|
+
|
|
341
|
+
return { authToken: authToken, userAgent: userAgent, appVersion: appVersion, deviceId: deviceId, sourcePath: dbPath }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function loadLocalSession(ctx) {
|
|
345
|
+
if (ctx.app.platform !== "macos") throw {code: "unsupported", message: "Perplexity app-cache authentication requires the legacy macOS CFNetwork cache. A separate supported web/API method is required; this cache reader cannot import browser cookies."}
|
|
346
|
+
var explicit = ctx.provider && ctx.provider.settings && ctx.provider.settings.cacheDbPath
|
|
347
|
+
var paths = explicit ? [explicit] : LOCAL_CACHE_DB_PATHS
|
|
348
|
+
for (let i = 0; i < paths.length; i += 1) {
|
|
349
|
+
const dbPath = paths[i]
|
|
350
|
+
try {
|
|
351
|
+
if (!ctx.host.fs.exists(dbPath)) continue
|
|
352
|
+
} catch (e) {
|
|
353
|
+
ctx.host.log.warn("local cache exists check failed (" + dbPath + "): " + String(e))
|
|
354
|
+
continue
|
|
355
|
+
}
|
|
356
|
+
const found = queryLocalSessionFromCache(ctx, dbPath)
|
|
357
|
+
if (found) return found
|
|
358
|
+
}
|
|
359
|
+
return null
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function fetchRestState(ctx, session) {
|
|
363
|
+
const authToken = session && session.authToken
|
|
364
|
+
if (!authToken) return null
|
|
365
|
+
const restHeaders = makeRestHeaderOverrides(session)
|
|
366
|
+
const groups =
|
|
367
|
+
fetchJsonOptional(ctx, REST_GROUPS_ENDPOINT, authToken, restHeaders) ||
|
|
368
|
+
fetchJsonOptional(ctx, REST_GROUPS_ENDPOINT + "/", authToken, restHeaders)
|
|
369
|
+
const groupId = pickGroupId(groups)
|
|
370
|
+
if (!groupId) return null
|
|
371
|
+
const base = REST_API_BASE + "/groups/" + encodeURIComponent(groupId)
|
|
372
|
+
const group =
|
|
373
|
+
fetchJsonOptional(ctx, base, authToken, restHeaders) ||
|
|
374
|
+
fetchJsonOptional(ctx, base + "/", authToken, restHeaders)
|
|
375
|
+
const usageUrl = base + "/usage-analytics"
|
|
376
|
+
const usageAnalytics =
|
|
377
|
+
fetchJsonOptional(ctx, usageUrl, authToken, restHeaders) ||
|
|
378
|
+
fetchJsonOptional(ctx, usageUrl + "/", authToken, restHeaders)
|
|
379
|
+
const rateLimits = fetchJsonOptional(ctx, RATE_LIMIT_ENDPOINT, authToken, restHeaders)
|
|
380
|
+
return { groupId: groupId, group: group, usageAnalytics: usageAnalytics, rateLimits: rateLimits }
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function probe(ctx) {
|
|
384
|
+
const session = loadLocalSession(ctx)
|
|
385
|
+
if (!session) throw "Not logged in. Sign in via Perplexity app."
|
|
386
|
+
if (session.sourcePath) ctx.host.log.info("using cache db: " + session.sourcePath)
|
|
387
|
+
|
|
388
|
+
const restState = fetchRestState(ctx, session)
|
|
389
|
+
if (!restState || !restState.group) throw "Unable to connect. Try again later."
|
|
390
|
+
|
|
391
|
+
const customerInfo = restState.group && restState.group.customerInfo
|
|
392
|
+
const plan = detectPlanLabel(customerInfo)
|
|
393
|
+
|
|
394
|
+
const dollarLines = []
|
|
395
|
+
const balanceUsd = readBalanceUsd(restState.group)
|
|
396
|
+
if (balanceUsd !== null && balanceUsd > 0) {
|
|
397
|
+
const usedUsd = sumUsageCostUsd(restState.usageAnalytics)
|
|
398
|
+
if (usedUsd !== null) {
|
|
399
|
+
const usedCents = Math.max(0, Math.round(usedUsd * 100))
|
|
400
|
+
const limitCents = Math.max(0, Math.round(balanceUsd * 100))
|
|
401
|
+
if (Number.isFinite(limitCents) && limitCents > 0) {
|
|
402
|
+
dollarLines.push(ctx.line.progress({
|
|
403
|
+
label: "API credits",
|
|
404
|
+
used: usedCents / 100,
|
|
405
|
+
limit: limitCents / 100,
|
|
406
|
+
format: { kind: "dollars" },
|
|
407
|
+
}))
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const rateLines = buildRateLimitLines(ctx, restState.rateLimits)
|
|
413
|
+
|
|
414
|
+
const lines = dollarLines.concat(rateLines)
|
|
415
|
+
if (lines.length === 0) {
|
|
416
|
+
if (balanceUsd !== null && balanceUsd > 0) throw "Usage data unavailable. Try again later."
|
|
417
|
+
if (plan) throw "Rate limits unavailable. Try again later."
|
|
418
|
+
throw "Balance unavailable. Try again later."
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return plan ? { plan: plan, lines: lines } : { lines: lines }
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
globalThis.__openusage_plugin = { id: "perplexity", probe: probe }
|
|
425
|
+
})()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "perplexity",
|
|
4
|
+
"name": "Perplexity",
|
|
5
|
+
"version": "0.0.2",
|
|
6
|
+
"entry": "plugin.js",
|
|
7
|
+
"icon": "icon.svg",
|
|
8
|
+
"brandColor": "#20808D",
|
|
9
|
+
"supportedModes": ["oauth", "local", "web"],
|
|
10
|
+
"autoMode": "web",
|
|
11
|
+
"webUrl": "https://www.perplexity.ai",
|
|
12
|
+
"lines": [
|
|
13
|
+
{ "type": "progress", "label": "API credits", "scope": "overview", "primaryOrder": 1 },
|
|
14
|
+
{ "type": "text", "label": "Queries", "scope": "overview" },
|
|
15
|
+
{ "type": "text", "label": "Deep Research", "scope": "overview" },
|
|
16
|
+
{ "type": "text", "label": "Labs", "scope": "detail" },
|
|
17
|
+
{ "type": "text", "label": "Agentic Research", "scope": "detail" }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="#26AD61" fill-rule="evenodd" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Poe</title><path d="M12.003 2C6.48 2 2 6.48 2 12s4.48 10 10.003 10 10.003-4.48 10.003-10S17.526 2 12.003 2zm4.45 13.897c-.397.6-.968.963-1.71.963-.742 0-1.313-.363-1.71-.963-.445-.668-.58-1.457-.41-2.347.172-.892.628-1.605 1.25-2.07.624-.462 1.34-.65 2.124-.65.79 0 1.53.216 2.13.65.64.494 1.11 1.232 1.29 2.14.18.91.04 1.72-.43 2.43-.42.64-1.05 1.03-1.88 1.03-.74 0-1.34-.36-1.75-1.03v2.85H12.003v-3.01l1.67.002c.59.002 1.01.27 1.25.8.26.57.21 1.23-.18 1.95-.4.71-1.07 1.07-2.06 1.07-1 0-1.69-.36-2.1-1.07-.4-.72-.51-1.52-.31-2.39.19-.87.64-1.54 1.34-2.01.7-.47 1.55-.7 2.52-.7.84 0 1.63.18 2.32.52.7.34 1.24.8 1.61 1.34l-1.24.8c-.56-.8-1.5-1.2-2.82-1.2-1.1 0-1.91.35-2.4 1.05-.49.7-.58 1.55-.27 2.57.32 1 .95 1.5 1.88 1.5.93 0 1.61-.4 2.05-1.2.27-.47.37-.97.3-1.5h-3.14v-1.2h4.45z"/></svg>
|