pi-telegram-notifier 0.1.4 → 0.1.6
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/README.md +19 -1
- package/docs/providers.md +1 -0
- package/extensions/index.ts +27 -11
- package/package.json +40 -10
- package/src/quota.ts +4 -2
package/README.md
CHANGED
|
@@ -12,7 +12,24 @@ export PI_TELEGRAM_BOT_TOKEN='your-bot-token'
|
|
|
12
12
|
pi
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
### Update or reload Pi
|
|
16
|
+
|
|
17
|
+
If Pi is already running, run `/reload` after installing the package (restart Pi if you just set the token). To update a published install later, run `pi update npm:pi-telegram-notifier`. If you previously installed a local checkout, run `pi list`, then `pi remove /absolute/path/to/pi-telegram-notifier` to avoid loading both copies.
|
|
18
|
+
|
|
19
|
+
### Pair a private chat
|
|
20
|
+
|
|
21
|
+
1. In Pi, run `/telegram-pair`.
|
|
22
|
+
2. In a private chat, send the bot the displayed **`/start <code>`** within five minutes.
|
|
23
|
+
|
|
24
|
+
During pairing, the bot must receive updates through `getUpdates`; disconnect any webhook or other updates consumer first. If pairing fails, the existing chat remains paired.
|
|
25
|
+
|
|
26
|
+
### Check pairing status
|
|
27
|
+
|
|
28
|
+
In Pi, run `/telegram-status` to see whether the bot token is configured and a private chat is paired. This reports saved local state; it does not check live Telegram connectivity.
|
|
29
|
+
|
|
30
|
+
### Switch chats and pairing storage
|
|
31
|
+
|
|
32
|
+
Run `/telegram-pair` again to switch chats. The extension stores only the numeric chat ID in `~/.pi/agent/pi-telegram-notifier.json` (or `$PI_CODING_AGENT_DIR/pi-telegram-notifier.json`), with mode 0600. Pairing uses `getUpdates` only during setup; there is no always-on polling afterward.
|
|
16
33
|
|
|
17
34
|
**Privacy:** final agent responses and the device hostname are sent to Telegram. Responses can contain sensitive text; only use this package if that is acceptable for your projects and Telegram chat. Bot and provider tokens are not stored in the package or sent as message text. Telegram Bot API uses the bot token in the request URL; do not log outgoing URLs. Keep your shell environment and Pi credentials private. A lost or compromised Telegram bot token must be revoked with BotFather.
|
|
18
35
|
|
|
@@ -24,6 +41,7 @@ The **quota/credit** field supports these Pi provider IDs (using the credential
|
|
|
24
41
|
| --- | --- | --- | --- |
|
|
25
42
|
| `openai-codex` | Subscription | ChatGPT OAuth | Remaining 5h / 7d usage-window percentages; [official Codex client endpoint](https://github.com/openai/codex/blob/main/codex-rs/backend-client/src/client/rate_limit_resets.rs). |
|
|
26
43
|
| `anthropic` | Subscription | Claude OAuth | Remaining 5h / 7d usage-window percentages; [private OAuth endpoint](https://github.com/openclaw/openclaw/blob/main/src/infra/provider-usage.fetch.claude.ts). |
|
|
44
|
+
| `claude-bridge` ([pi-claude-bridge](https://www.npmjs.com/package/pi-claude-bridge)) | Subscription | Pi's Claude OAuth (`/login` → Anthropic) | Same 5h / 7d windows as `anthropic`: the bridge has no credential of its own, so the matching `anthropic` model's OAuth login is used. Log Pi and Claude Code into the **same** Anthropic account, otherwise Pi's account quota is shown. Without the login, `Quota unavailable`. |
|
|
27
45
|
| `xai` | Subscription | Grok OAuth | Remaining weekly shared Grok credits percentage, **not** prepaid API credit; [official Grok client endpoint](https://github.com/xai-org/grok-build/blob/main/crates/codegen/xai-grok-shell/src/extensions/billing.rs). |
|
|
28
46
|
| `zai` | Subscription (Coding Plan) | Z.ai Coding Plan key | Remaining 5h **model** quota percentage; [Z.ai usage plugin endpoint](https://github.com/zai-org/zai-coding-plugins/blob/main/plugins/glm-plan-usage/skills/usage-query-skill/scripts/query-usage.mjs). Not the separate MCP tool quota. |
|
|
29
47
|
| `zai-coding-cn` | Subscription (Coding Plan) | China Coding Plan key | Same model quota on `open.bigmodel.cn`. |
|
package/docs/providers.md
CHANGED
|
@@ -12,6 +12,7 @@ Inventory of the 41 built-in provider IDs in the installed Pi model catalog (`@e
|
|
|
12
12
|
| `zai` | Remaining 5-hour Coding Plan model quota from `100 - percentage` for `TOKENS_LIMIT` only; pay-as-you-go keys or unknown plan data show unavailable. | [Official Z.ai usage query script](https://github.com/zai-org/zai-coding-plugins/blob/main/plugins/glm-plan-usage/skills/usage-query-skill/scripts/query-usage.mjs), `GET https://api.z.ai/api/monitor/usage/quota/limit` (not a stable public API) |
|
|
13
13
|
| `zai-coding-cn` | Same Coding Plan model quota for the China host; not the MCP tool quota. | [Official Z.ai usage query script](https://github.com/zai-org/zai-coding-plugins/blob/main/plugins/glm-plan-usage/skills/usage-query-skill/scripts/query-usage.mjs), `GET https://open.bigmodel.cn/api/monitor/usage/quota/limit` (not a stable public API) |
|
|
14
14
|
| `anthropic` | Remaining 5h and/or 7d Claude **subscription** allowance with Pi OAuth only; regular API keys show unavailable. | [OAuth usage client](https://github.com/openclaw/openclaw/blob/main/src/infra/provider-usage.fetch.claude.ts), `GET https://api.anthropic.com/api/oauth/usage` (private endpoint) |
|
|
15
|
+
| `claude-bridge` (extension provider from [pi-claude-bridge](https://github.com/elidickinson/pi-claude-bridge)) | Same Claude subscription windows as `anthropic`. The bridge registers a placeholder key and runs Claude Code with its own login, so the quota is read with Pi's `anthropic` OAuth credential for the same model ID; without that login it shows unavailable. Claude Code's own credential store is deliberately not read, so the quota belongs to Pi's Anthropic account: it matches the bridge turn only when Pi and Claude Code are logged into the same account (the usage response does not identify the account). | Same endpoint as `anthropic` |
|
|
15
16
|
| `xai` | Remaining **weekly Grok subscription** credits with Pi OAuth only. The per-product percentages are not the shared pool; xAI API prepaid credit is a different quantity requiring a management key. | [Official Grok billing client](https://github.com/xai-org/grok-build/blob/main/crates/codegen/xai-grok-shell/src/extensions/billing.rs), `GET https://cli-chat-proxy.grok.com/v1/billing?format=credits` (private endpoint); [API management billing](https://docs.x.ai/developers/rest-api-reference/management/billing) |
|
|
16
17
|
| `amazon-bedrock`, `azure-openai-responses`, `cloudflare-ai-gateway`, `cloudflare-workers-ai`, `google-vertex`, `radius`, `vercel-ai-gateway` | Cloud/gateway billing belongs to separate accounts/credentials; no verified remaining plan balance endpoint with Pi's inference credential. | Provider-specific cloud billing tools, not Pi model API keys. |
|
|
17
18
|
| `openai`, `github-copilot` | Subscription balance not exposed through a verified quota endpoint accessible with Pi's resolved inference credential. Admin usage/spending APIs are **not** plan remainder. | [OpenAI organization costs](https://platform.openai.com/docs/api-reference/organization/costs) |
|
package/extensions/index.ts
CHANGED
|
@@ -15,13 +15,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
15
15
|
const tokenConfigured = !!process.env.PI_TELEGRAM_BOT_TOKEN;
|
|
16
16
|
const paired = loadChatId(agentDir()) !== undefined;
|
|
17
17
|
ctx.ui.notify([
|
|
18
|
-
'Telegram
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
'Telegram connection',
|
|
19
|
+
'',
|
|
20
|
+
'Configuration',
|
|
21
|
+
`Bot token: ${tokenConfigured ? 'Configured' : 'Not configured'}`,
|
|
22
|
+
`Private chat: ${paired ? 'Paired' : 'Not paired'}`,
|
|
23
|
+
'',
|
|
24
|
+
'Connectivity',
|
|
25
|
+
'Live check: Not performed',
|
|
26
|
+
...(!tokenConfigured || !paired ? [
|
|
27
|
+
'',
|
|
28
|
+
'Next steps',
|
|
29
|
+
...(!tokenConfigured ? ['Set PI_TELEGRAM_BOT_TOKEN and restart Pi.'] : []),
|
|
30
|
+
...(!paired ? ['Run /telegram-pair to pair a private chat.'] : []),
|
|
31
|
+
] : []),
|
|
32
|
+
].join('\n'), 'info');
|
|
25
33
|
},
|
|
26
34
|
});
|
|
27
35
|
pi.registerCommand('telegram-pair', {
|
|
@@ -29,16 +37,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
29
37
|
handler: async (_args, ctx) => {
|
|
30
38
|
const token = process.env.PI_TELEGRAM_BOT_TOKEN;
|
|
31
39
|
if (!token) {
|
|
32
|
-
ctx.ui.notify('
|
|
40
|
+
ctx.ui.notify('Telegram pairing unavailable\n\nSet PI_TELEGRAM_BOT_TOKEN before starting Pi.', 'error');
|
|
33
41
|
return;
|
|
34
42
|
}
|
|
35
43
|
const code = randomBytes(16).toString('hex');
|
|
36
|
-
ctx.ui.notify(
|
|
44
|
+
ctx.ui.notify([
|
|
45
|
+
'Pair Telegram',
|
|
46
|
+
'',
|
|
47
|
+
'In your private chat with the bot, send:',
|
|
48
|
+
'',
|
|
49
|
+
`/start ${code}`,
|
|
50
|
+
'',
|
|
51
|
+
'Code expires in 5 minutes.',
|
|
52
|
+
].join('\n'), 'info');
|
|
37
53
|
try {
|
|
38
54
|
saveChatId(agentDir(), await pairChat(token, code));
|
|
39
|
-
ctx.ui.notify('
|
|
55
|
+
ctx.ui.notify('Telegram paired\n\nPrivate chat linked successfully.', 'info');
|
|
40
56
|
} catch (error) {
|
|
41
|
-
ctx.ui.notify(error instanceof Error ? error.message : 'Telegram pairing failed'
|
|
57
|
+
ctx.ui.notify(`Telegram pairing failed\n\n${error instanceof Error ? error.message : 'Telegram pairing failed'}`, 'error');
|
|
42
58
|
}
|
|
43
59
|
},
|
|
44
60
|
});
|
package/package.json
CHANGED
|
@@ -1,16 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-telegram-notifier",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Telegram notifications for final Pi agent responses and provider balances",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"engines": {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.19.0"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"pi-package",
|
|
12
|
+
"pi",
|
|
13
|
+
"telegram",
|
|
14
|
+
"notifications"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
17
|
+
"extensions",
|
|
18
|
+
"src",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"docs/providers.md"
|
|
22
|
+
],
|
|
23
|
+
"imports": {
|
|
24
|
+
"#src/*": "./src/*.ts"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "node --test",
|
|
28
|
+
"typecheck": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"pi": {
|
|
31
|
+
"extensions": [
|
|
32
|
+
"./extensions/index.ts"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "22.20.4",
|
|
40
|
+
"typescript": "5.9.3"
|
|
41
|
+
},
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/Gybra/pi-telegram-notifier.git"
|
|
45
|
+
}
|
|
16
46
|
}
|
package/src/quota.ts
CHANGED
|
@@ -20,9 +20,11 @@ function remainingPercent(used: unknown): number | undefined {
|
|
|
20
20
|
return typeof used === 'number' && Number.isFinite(used) && used >= 0 && used <= 100 ? 100 - used : undefined;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export async function getQuota(model: Model | undefined, modelRegistry: Pick<ModelRegistry, 'getApiKeyAndHeaders' | 'isUsingOAuth'> | undefined, fetchImpl: typeof fetch = fetch): Promise<string> {
|
|
23
|
+
export async function getQuota(model: Model | undefined, modelRegistry: Pick<ModelRegistry, 'find' | 'getApiKeyAndHeaders' | 'isUsingOAuth'> | undefined, fetchImpl: typeof fetch = fetch): Promise<string> {
|
|
24
|
+
// pi-claude-bridge has no credential of its own; it draws on the same Claude subscription as Pi's Anthropic OAuth login.
|
|
25
|
+
if (model?.provider === 'claude-bridge') model = modelRegistry?.find('anthropic', model.id);
|
|
24
26
|
const provider = model && providers[model.provider];
|
|
25
|
-
if (!provider) return UNAVAILABLE;
|
|
27
|
+
if (!model || !provider) return UNAVAILABLE;
|
|
26
28
|
try {
|
|
27
29
|
if (new URL(model.baseUrl).origin !== provider.origin) return UNAVAILABLE;
|
|
28
30
|
if (['anthropic', 'xai'].includes(model.provider) && !modelRegistry?.isUsingOAuth(model)) return UNAVAILABLE;
|