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 CHANGED
@@ -1,3 +1,336 @@
1
- # Temporary Holding Version
1
+ # dingtalk-pingo
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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.