dingtalk-pingo 0.0.0-stage → 0.2.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.
- package/README.md +335 -2
- package/dist/pingo.js +9506 -0
- package/package.json +36 -4
- package/skills/pingo/SKILL.md +130 -0
- package/skills/pingo-follow/SKILL.md +132 -0
package/README.md
CHANGED
|
@@ -1,3 +1,336 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dingtalk-pingo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Send text and Markdown messages from your terminal, scripts, or AI coding
|
|
4
|
+
agents straight to your phone through a DingTalk robot — no server, no
|
|
5
|
+
polling, no runtime dependencies beyond Node itself.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pingo send --type success --message "Build finished, 42 tests passing."
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Why
|
|
12
|
+
|
|
13
|
+
You're running a long task over SSH/tmux, or an AI coding agent (Kiro,
|
|
14
|
+
Codex, Claude Code, ...) is working unattended, and you want to know when
|
|
15
|
+
it's actually done — without watching a terminal. `pingo` is a single CLI
|
|
16
|
+
command any script or agent can call to push a one-off notification to
|
|
17
|
+
your phone via DingTalk's one-to-one robot messaging API.
|
|
18
|
+
|
|
19
|
+
It is deliberately one-directional and stateless: it sends a message and
|
|
20
|
+
exits. It does not receive messages, does not run as a background
|
|
21
|
+
service, and does not poll anything.
|
|
22
|
+
|
|
23
|
+
## Who this is for
|
|
24
|
+
|
|
25
|
+
- Developers who use DingTalk and want build/deploy/test notifications
|
|
26
|
+
pushed to their phone from shell scripts, CI, or cron.
|
|
27
|
+
- People running AI coding agents unattended who want a reliable "it's
|
|
28
|
+
done" / "it needs your input" signal, without a chat-based control
|
|
29
|
+
loop.
|
|
30
|
+
- Anyone who wants a minimal, auditable CLI for DingTalk robot messaging
|
|
31
|
+
instead of a heavier notification framework.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
Requires Node.js 18+.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install -g dingtalk-pingo
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This installs a single `pingo` command. The package ships as one bundled
|
|
42
|
+
file with no runtime dependencies.
|
|
43
|
+
|
|
44
|
+
## Set up your own DingTalk robot
|
|
45
|
+
|
|
46
|
+
Pingo sends through **your own** DingTalk enterprise-internal app and
|
|
47
|
+
robot — there is no shared or hosted robot. You need:
|
|
48
|
+
|
|
49
|
+
1. A DingTalk enterprise account at <https://open.dingtalk.com/>.
|
|
50
|
+
2. An enterprise-internal application, which gives you an **AppKey**
|
|
51
|
+
(`clientId`) and **AppSecret** (`clientSecret`).
|
|
52
|
+
3. A robot created under that application, which gives you a
|
|
53
|
+
**robotCode**.
|
|
54
|
+
4. Your own DingTalk **userId** (the recipient) — the person the robot
|
|
55
|
+
will message.
|
|
56
|
+
5. Robot permission to send one-to-one ("private chat") messages, granted
|
|
57
|
+
to the application in the DingTalk admin console.
|
|
58
|
+
|
|
59
|
+
Exact steps change over time; follow DingTalk's current documentation at
|
|
60
|
+
<https://open.dingtalk.com/> and verify the "send one-to-one messages in
|
|
61
|
+
batch" API (`robot/oToMessages/batchSend`) works for your app before
|
|
62
|
+
relying on it.
|
|
63
|
+
|
|
64
|
+
## Configure
|
|
65
|
+
|
|
66
|
+
Create `~/.pingo/config.yaml` (on Windows: `%USERPROFILE%\.pingo\config.yaml`)
|
|
67
|
+
and restrict its permissions, since it holds a secret:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
mkdir -p ~/.pingo
|
|
71
|
+
chmod 700 ~/.pingo
|
|
72
|
+
touch ~/.pingo/config.yaml
|
|
73
|
+
chmod 600 ~/.pingo/config.yaml
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
device:
|
|
78
|
+
id: work-mac
|
|
79
|
+
name: Work Mac
|
|
80
|
+
icon: "🏢" # shown in Markdown/text messages
|
|
81
|
+
avatar: "https://example.com/work-mac.png" # optional, card messages only (http(s) URL)
|
|
82
|
+
|
|
83
|
+
card:
|
|
84
|
+
templateId: "<your card template id>" # optional; enables card messages (see "Card messages")
|
|
85
|
+
|
|
86
|
+
dingtalk:
|
|
87
|
+
clientId: <your AppKey>
|
|
88
|
+
clientSecret: <your AppSecret> # or: clientSecretEnv: DINGTALK_CLIENT_SECRET
|
|
89
|
+
robotCode: <your robotCode>
|
|
90
|
+
userId: <your DingTalk userId>
|
|
91
|
+
|
|
92
|
+
message:
|
|
93
|
+
defaultType: info
|
|
94
|
+
preferredFormat: card # card | markdown | text (default: card if card.templateId is set, else markdown)
|
|
95
|
+
fallbackFormat: text
|
|
96
|
+
timeoutMs: 10000
|
|
97
|
+
|
|
98
|
+
templates: # optional: override the built-in templates
|
|
99
|
+
success:
|
|
100
|
+
title: "{typeIcon} {deviceIcon} {device} · done"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Credentials can also come from environment variables, which take
|
|
104
|
+
precedence over the file: `DINGTALK_CLIENT_ID`, `DINGTALK_CLIENT_SECRET`,
|
|
105
|
+
`DINGTALK_ROBOT_CODE`, `DINGTALK_USER_ID`, plus `PINGO_DEVICE_ID`,
|
|
106
|
+
`PINGO_DEVICE_NAME`, `PINGO_DEVICE_ICON`, `PINGO_DEVICE_AVATAR` for device
|
|
107
|
+
identity and `PINGO_CARD_TEMPLATE_ID` for the card template. This is
|
|
108
|
+
useful in CI or containers where you don't want a config file at all.
|
|
109
|
+
|
|
110
|
+
If you run multiple machines against the same robot (e.g. a work laptop
|
|
111
|
+
and a home desktop), give each one a distinct `device` block so messages
|
|
112
|
+
on your phone are easy to tell apart.
|
|
113
|
+
|
|
114
|
+
> All machines sharing one DingTalk app share one AppSecret — there is no
|
|
115
|
+
> per-device key isolation in this tool. Treat the secret accordingly and
|
|
116
|
+
> rotate it if it may have leaked.
|
|
117
|
+
|
|
118
|
+
## Usage
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pingo doctor # self-check: config, permissions, can we get a token?
|
|
122
|
+
pingo config validate # validate config and show where it was loaded from
|
|
123
|
+
pingo config show # print merged config (secrets masked)
|
|
124
|
+
|
|
125
|
+
pingo send --type info --message "Stage 1 complete"
|
|
126
|
+
pingo send --type success --title "Deploy finished" \
|
|
127
|
+
--message "Build and tests passed" --agent kiro --project payment-sdk
|
|
128
|
+
pingo send --type warning --action-required --message "Please review the token permission change"
|
|
129
|
+
pingo send --markdown ./report.md --type success # body from a Markdown file
|
|
130
|
+
pingo send --message "preview this" --dry-run # preview only, no credentials needed
|
|
131
|
+
pingo send --message "x" --json # machine-readable output
|
|
132
|
+
|
|
133
|
+
pingo send --title "Checkout page" --message "After the fix" --image ./shot.png
|
|
134
|
+
pingo send --message "Repro recording" --video ./repro.mp4
|
|
135
|
+
pingo send --message "PR is ready" --link "Open PR=https://github.com/org/repo/pull/1"
|
|
136
|
+
pingo update <cardId> --type success --title "Tests passing" --message "195/195"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Run `pingo --help` for the full option list.
|
|
140
|
+
|
|
141
|
+
## Card messages
|
|
142
|
+
|
|
143
|
+
With a card template configured, every message is a DingTalk card whose
|
|
144
|
+
header shows the sender — the machine's avatar and name, plus the agent and
|
|
145
|
+
project — so messages from several machines sharing one robot are easy to
|
|
146
|
+
tell apart. Below the header come the title, the Markdown body, any images
|
|
147
|
+
and optional link buttons.
|
|
148
|
+
|
|
149
|
+
To enable cards:
|
|
150
|
+
|
|
151
|
+
1. Grant your app the **`Card.Instance.Write`** permission in the DingTalk
|
|
152
|
+
developer console.
|
|
153
|
+
2. Create a message card template in the card editor at
|
|
154
|
+
<https://open-dev.dingtalk.com/fe/card>, linked to your app. There is no
|
|
155
|
+
public API for creating templates; it is done in the web editor. Lay it out
|
|
156
|
+
as "avatar + sender name" on the first row, the title below, then a
|
|
157
|
+
Markdown/rich-text block and a button group. Bind these variables (names and
|
|
158
|
+
types must match exactly):
|
|
159
|
+
|
|
160
|
+
- `avatar` — plain text (image URL)
|
|
161
|
+
- `senderName` — plain text
|
|
162
|
+
- `title` — plain text
|
|
163
|
+
- `content` — rich text (Markdown)
|
|
164
|
+
- `btns` — button group
|
|
165
|
+
- optional: `date` — plain text (shown when `--date` is used)
|
|
166
|
+
- optional: `chart` — chart variable (shown when `--chart` is used)
|
|
167
|
+
|
|
168
|
+
3. Publish the template, then set `card.templateId` (and optionally
|
|
169
|
+
`device.avatar`) in `~/.pingo/config.yaml`.
|
|
170
|
+
|
|
171
|
+
Card-only options:
|
|
172
|
+
|
|
173
|
+
- `--date "<text>"` fills the `date` variable (Pingo does not format it).
|
|
174
|
+
- `--chart <file.json>` fills the `chart` variable. The file holds a chart
|
|
175
|
+
object: `type` is `lineChart`, `histogram` or `pieChart`, `data` is a
|
|
176
|
+
non-empty array, `config` is optional. See DingTalk's
|
|
177
|
+
[chart variable](https://open.dingtalk.com/document/isvapp/chart-variable)
|
|
178
|
+
protocol. Example: `{"type":"lineChart","data":[{"x":"N0","type":"line","y":48}]}`.
|
|
179
|
+
- Both are rejected with `--format markdown|text`, and both work with
|
|
180
|
+
`pingo update`.
|
|
181
|
+
- Tables: put a Markdown table in `--message`/`--markdown`. It renders only if
|
|
182
|
+
the template's `content` component uses DingTalk's newer Markdown variable;
|
|
183
|
+
the older one has no table support.
|
|
184
|
+
|
|
185
|
+
Templates belong to your own DingTalk app; Pingo does not ship one. If the
|
|
186
|
+
card is rejected (wrong template ID, missing permission), Pingo falls back to
|
|
187
|
+
a Markdown message and says why in its output. Timeouts are never retried or
|
|
188
|
+
downgraded, to avoid duplicate messages.
|
|
189
|
+
|
|
190
|
+
`pingo send --json` returns a `cardId` for card messages. `pingo update
|
|
191
|
+
<cardId>` changes that card in place: `--type`/`--title` regenerate the title;
|
|
192
|
+
`--message`/`--markdown`/`--image`/`--action-required` replace the whole body
|
|
193
|
+
(pass the full text); `--agent`/`--project` update the sender line; `--link`
|
|
194
|
+
replaces the buttons. Unspecified fields are left as they were. An update never
|
|
195
|
+
falls back to sending a new message.
|
|
196
|
+
|
|
197
|
+
## Sending images and videos
|
|
198
|
+
|
|
199
|
+
- `--image <file>`: png, jpg, gif or bmp; up to 5 per message, 10 MiB each;
|
|
200
|
+
the file content must match the extension. In a card, images appear inside
|
|
201
|
+
the card; in Markdown/text messages they follow as separate image messages.
|
|
202
|
+
- `--video <file.mp4>`: one mp4 per message, up to 19 MiB (DingTalk's upload
|
|
203
|
+
limit is just under 20 MiB). It is sent as a separate video message right
|
|
204
|
+
after the main message. The duration is read from the mp4 itself.
|
|
205
|
+
- `--cover <image>`: video cover. Without it, Pingo grabs a frame with `ffmpeg`
|
|
206
|
+
if it is installed (optional, not a dependency); otherwise it generates a
|
|
207
|
+
plain gradient cover whose colour is derived from `device.id`.
|
|
208
|
+
- `--image` / `--video` always come with `--message` or `--markdown`.
|
|
209
|
+
- Files are uploaded to DingTalk's media service before anything is sent; if an
|
|
210
|
+
upload fails, nothing is sent. If a later part of a multi-part send fails,
|
|
211
|
+
the output lists what was and was not delivered, and nothing is resent.
|
|
212
|
+
|
|
213
|
+
Screenshots and screen recordings can capture more than intended — check them
|
|
214
|
+
for secrets or private data before sending.
|
|
215
|
+
|
|
216
|
+
## AI agent skills
|
|
217
|
+
|
|
218
|
+
The package ships two [Agent Skills](https://developers.openai.com/codex/skills/create-skill)
|
|
219
|
+
(a folder with a `SKILL.md`) that teach an AI coding agent when and how
|
|
220
|
+
to call `pingo`:
|
|
221
|
+
|
|
222
|
+
- **`pingo`** — send a one-off message, report, or "done" summary when you
|
|
223
|
+
explicitly ask the agent to ("send me this via pingo", "notify me on
|
|
224
|
+
DingTalk when finished"). It never notifies on its own.
|
|
225
|
+
- **`pingo-follow`** — opt-in, session-scoped follow-up. After you turn it
|
|
226
|
+
on (for example `/pingo-follow`, or `/pingo-follow only notify me when
|
|
227
|
+
you need my review`), the agent notifies you only at major milestones,
|
|
228
|
+
blockers, or when your approval is needed, and stays silent otherwise.
|
|
229
|
+
Turning it on does not itself send a DingTalk message.
|
|
230
|
+
|
|
231
|
+
Install them into the tools you use:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pingo skills install # interactive: choose Kiro / Claude Code / Codex
|
|
235
|
+
pingo skills install --tool kiro,claude # non-interactive (required when there is no terminal)
|
|
236
|
+
pingo skills uninstall # removes only links created by this package
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
`install` creates symlinks `~/.kiro/skills/<name>`, `~/.claude/skills/<name>`,
|
|
240
|
+
and `~/.codex/skills/<name>` that point at the skills shipped inside this
|
|
241
|
+
package. It never overwrites or deletes anything it did not create: if a
|
|
242
|
+
path already exists and is not one of its own links, it is reported as a
|
|
243
|
+
conflict and skipped (exit code 2). Re-running `install` is safe, and it
|
|
244
|
+
repairs links that went stale after you changed Node versions or moved the
|
|
245
|
+
global `node_modules` directory. Start a new session in the tool afterwards
|
|
246
|
+
so it discovers the skills.
|
|
247
|
+
|
|
248
|
+
How to invoke them explicitly: type `/pingo-follow` in Kiro and Claude
|
|
249
|
+
Code, or `$pingo-follow` (or `/skills`) in Codex.
|
|
250
|
+
|
|
251
|
+
## Exit codes
|
|
252
|
+
|
|
253
|
+
| Code | Meaning |
|
|
254
|
+
|---|---|
|
|
255
|
+
| 0 | DingTalk accepted the request (not the same as the user having read it) |
|
|
256
|
+
| 1 | Usage error |
|
|
257
|
+
| 2 | Missing or invalid configuration, or a skill-install conflict |
|
|
258
|
+
| 3 | Authentication failed |
|
|
259
|
+
| 4 | Invalid content (missing required fields, or too long) |
|
|
260
|
+
| 5 | DingTalk temporarily unavailable or rate-limited (retried twice) |
|
|
261
|
+
| 6 | Result unknown — **the message may have already been sent**; don't resend |
|
|
262
|
+
|
|
263
|
+
## Message format limits (measured against the live API)
|
|
264
|
+
|
|
265
|
+
- Markdown does **not** support tables or task-list checkboxes
|
|
266
|
+
(`- [ ]`); use plain lists instead.
|
|
267
|
+
- The request body limit is roughly 20000 bytes; Pingo enforces an 8000-byte
|
|
268
|
+
local limit on `msgParam` and rejects oversized content up front rather
|
|
269
|
+
than letting DingTalk reject it (no automatic splitting yet). Each CJK
|
|
270
|
+
character costs roughly 3 bytes.
|
|
271
|
+
- DingTalk accepts a request with a missing title/body without erroring —
|
|
272
|
+
it sends the literal placeholder text `#title#` / `#text#` to the
|
|
273
|
+
phone. Pingo validates required fields locally so that can't happen.
|
|
274
|
+
- Markdown is only downgraded to plain text when DingTalk rejects it as
|
|
275
|
+
an invalid format; a request whose result is unknown (e.g. a timeout)
|
|
276
|
+
is never retried, to avoid duplicate messages.
|
|
277
|
+
|
|
278
|
+
## Development
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
npm test # unit tests (requires Node ≥ 24 to run .ts directly)
|
|
282
|
+
npm run typecheck
|
|
283
|
+
npm run build # produce dist/pingo.js
|
|
284
|
+
npm run check:dist # verify dist/pingo.js matches the current source
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
If you change anything under `src/`, run `npm run build` and commit the
|
|
288
|
+
updated `dist/pingo.js` — `npm run check:dist` verifies they match, and
|
|
289
|
+
`prepublishOnly` runs the full check before `npm publish`.
|
|
290
|
+
|
|
291
|
+
## Status
|
|
292
|
+
|
|
293
|
+
Version 0.2 adds card messages, in-place card updates, images, mp4 videos and
|
|
294
|
+
link buttons. Text/Markdown sending, cards, card updates, image upload and
|
|
295
|
+
sending, and video sending were each verified against a live DingTalk robot;
|
|
296
|
+
everything is covered by unit tests. How reliably each AI tool follows
|
|
297
|
+
`pingo-follow` has not been verified end to end yet (see below). File
|
|
298
|
+
attachments are not supported.
|
|
299
|
+
|
|
300
|
+
## Known limitations
|
|
301
|
+
|
|
302
|
+
- **Skill directories.** `~/.kiro/skills`, `~/.claude/skills` and
|
|
303
|
+
`~/.codex/skills` are the locations `pingo skills install` uses. Kiro
|
|
304
|
+
and Claude Code document their skill folders; for Codex the official
|
|
305
|
+
pages describe the `SKILL.md` format and `$skill` / `/skills`
|
|
306
|
+
invocation but `~/.codex/skills` could not be confirmed as the default
|
|
307
|
+
directory from documentation — it matches the directory Codex uses on
|
|
308
|
+
the author's machine. If your Codex setup reads skills from somewhere
|
|
309
|
+
else, link `skills/pingo*` there yourself.
|
|
310
|
+
- **`pingo-follow` is advisory.** It is plain instructions, not a
|
|
311
|
+
background service. Its on/off state lives only in the agent's
|
|
312
|
+
conversation context, so it can be lost after a context compaction,
|
|
313
|
+
a crash, or a new session (a new session always starts with it off).
|
|
314
|
+
Delivery is best-effort. How strictly each tool honors "only when
|
|
315
|
+
explicitly enabled" is unverified.
|
|
316
|
+
- **Explicit-only invocation is not enforced by the tool.** Claude Code
|
|
317
|
+
has a `disable-model-invocation` frontmatter field that would stop the
|
|
318
|
+
model from loading a skill on its own, but it also hides the skill's
|
|
319
|
+
description from the model, which would break natural-language
|
|
320
|
+
activation such as "follow this task and notify me when done". It is
|
|
321
|
+
therefore not used here, and it could not be confirmed that Kiro or
|
|
322
|
+
Codex honor it. The
|
|
323
|
+
skill's description and body tell the agent to activate only on an
|
|
324
|
+
explicit request; that is guidance, not a hard guarantee.
|
|
325
|
+
- **Skill links follow the package location.** The links point at the
|
|
326
|
+
globally installed package. After changing Node version or moving the
|
|
327
|
+
global `node_modules` directory, re-run `pingo skills install` to
|
|
328
|
+
repair them.
|
|
329
|
+
- **Cards and notifications.** Whether updating a card in place re-notifies
|
|
330
|
+
the phone, and what the chat list shows as the preview of a card message,
|
|
331
|
+
are not yet verified; `pingo-follow` therefore sends approval requests and
|
|
332
|
+
final reports as new messages.
|
|
333
|
+
- **Card body limit.** The card body uses the same conservative 8000-byte local
|
|
334
|
+
limit as Markdown; DingTalk's real limit for cards has not been measured.
|
|
335
|
+
- **Videos.** Only mp4 has been tested. Videos are never embedded in cards.
|
|
336
|
+
- **No file attachments** yet.
|