dsh-dingtalk 0.1.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 ADDED
@@ -0,0 +1,114 @@
1
+ # dsh-dingtalk
2
+
3
+ DeepSeek Harness 钉钉群机器人通知插件:让 agent 能**单向推送 Markdown / 纯文本消息到钉钉群**。纯插件实现,零核心改动,安装即可用。
4
+
5
+ 纯 Node 实现,**全平台通用**(Windows / macOS / Linux 同一份代码),只依赖 `node:crypto` 与内置 `fetch`,无运行时依赖、无原生二进制。
6
+
7
+ ## 工具一览
8
+
9
+ | 工具 | 作用 |
10
+ |---|---|
11
+ | `dingtalk_notify` | 向配置的钉钉群发一条 **Markdown** 消息(`title` 标题 + `text` Markdown 正文) |
12
+ | `dingtalk_text` | 向配置的钉钉群发一条 **纯文本** 消息(`content`) |
13
+
14
+ 示例对话:
15
+
16
+ > 帮我给钉钉群发一条消息:标题「构建完成」,正文「流水线 #123 已通过 ✅」。
17
+
18
+ ## 安装
19
+
20
+ ```sh
21
+ dsh plugin --profile web add dsh-dingtalk
22
+ ```
23
+
24
+ 装好后重启 `dsh web`。插件自带空配置,**不会弄崩启动**;配置前调用任何 `dingtalk_*` 工具都会返回明确的中文配置提示。
25
+
26
+ ## 第一步:拿到 webhook 与加签密钥
27
+
28
+ 用钉钉【自定义机器人】方式,**不需要企业应用、不需要管理员权限**:
29
+
30
+ 1. 打开目标钉钉群 → **群设置** → **智能群助手** → **添加机器人** → **自定义(通过 Webhook 接入自定义服务)**
31
+ 2. 机器人名称随意,**安全设置勾选「加签」**,完成
32
+ 3. 复制两样东西:
33
+ - **Webhook 地址**:形如 `https://oapi.dingtalk.com/robot/send?access_token=xxxx`
34
+ - **加签密钥(secret)**:形如 `SECxxxx`(勾选「加签」后才会出现)
35
+
36
+ > 安全设置推荐只勾选「加签」。如果同时勾选了「自定义关键词」,机器人会校验消息里是否包含关键词——那是钉钉服务端行为,本插件不处理(见「已知限制」)。
37
+
38
+ ## 配置
39
+
40
+ 在你 profile 的 `cordis.patch.yml` 里覆盖 `tool-dingtalk` 行(在 `$DSH_HOME/profiles/<name>/` 下),然后重启:
41
+
42
+ ```yaml
43
+ - id: tool-dingtalk
44
+ config:
45
+ webhook: https://oapi.dingtalk.com/robot/send?access_token=你的token
46
+ secret: SEC你的加签密钥 # 可选,但强烈建议;也可用环境变量 DSH_DINGTALK_SECRET
47
+ ```
48
+
49
+ ### 完整配置项
50
+
51
+ | 字段 | 默认 | 说明 |
52
+ |---|---|---|
53
+ | `webhook` | 必填 | 机器人完整 Webhook 地址(`?access_token=...`) |
54
+ | `secret` | 空 | 加签密钥;省略时回退环境变量 `DSH_DINGTALK_SECRET`。不填=不加签 |
55
+ | `timeoutMs` | `10000` | 请求超时毫秒数(1000–60000) |
56
+
57
+ ### 用环境变量存 secret
58
+
59
+ 不想把密钥写进 YAML 的话,可以只写 `webhook`,然后设置环境变量:
60
+
61
+ ```sh
62
+ # Windows PowerShell
63
+ $env:DSH_DINGTALK_SECRET = "SEC你的加签密钥"
64
+ # 或系统环境变量里加一条 DSH_DINGTALK_SECRET
65
+ ```
66
+
67
+ 显式写在 YAML 里的 `secret` 优先于环境变量。
68
+
69
+ ## 安全须知
70
+
71
+ - **webhook 就是群通知的钥匙**:知道它的人可以往群里发消息。请勿把 `cordis.patch.yml` 提交到任何 Git 仓库;`secret` 更推荐用环境变量 `DSH_DINGTALK_SECRET`。
72
+ - 本插件**只做单向通知**(agent → 钉钉群),不会读取群里任何消息,也没有双向机器人能力。
73
+ - 本插件不做任何联网上报;`secret` 只在内存中用于计算 HMAC 签名,签名拼进 URL 发给钉钉官方接口。
74
+
75
+ ## 加签算法(对照官方)
76
+
77
+ `secret` 存在时,请求 URL 追加 `timestamp`(毫秒时间戳)与 `sign`:
78
+
79
+ ```text
80
+ stringToSign = timestamp + "
81
+ " + secret
82
+ sign = urlencode(base64(HmacSHA256(secret, stringToSign)))
83
+ ```
84
+
85
+ 与本插件 `computeDingTalkSign` 一致,等价于官方 Python 示例的 `urllib.parse.quote_plus(base64(...))`。
86
+
87
+ ## 已知限制(v0.1)
88
+
89
+ - **仅单向通知**:不支持企业自建应用、不支持接收群消息 / 双向机器人(@机器人 回复等),这些是 v0.2+ 的方向。
90
+ - **不支持自定义关键词校验的自动规避**:如果机器人在钉钉侧勾选了「自定义关键词」,消息必须包含关键词,否则钉钉返回错误(errcode 310000 也可能因此出现)。本插件把 310000 归因于加签,请同时检查关键词设置。
91
+ - **无设置页**:v0.1 是「node 半身」,配置只能走 `cordis.patch.yml`(+ 环境变量)。Web 设置页在 v0.2+。
92
+ - **消息类型**:目前只支持 Markdown(`dingtalk_notify`)与纯文本(`dingtalk_text`)两种;图片/Link/FeedCard/ActionCard 等类型后续再加。
93
+ - **Markdown 渲染**:钉钉机器人 Markdown 语法与标准 Markdown 略有差异(如标题、链接),请按钉钉语法书写。
94
+
95
+ ## 错误码速查
96
+
97
+ | errcode | 含义 | 建议 |
98
+ |---|---|---|
99
+ | `0` | 成功 | — |
100
+ | `310000` | 加签校验失败(也见上面关键词说明) | 检查 `secret` 是否填对、是否与机器人「加签」设置一致 |
101
+ | `120001` | `access_token` 失效 | 到钉钉群重新复制 webhook 地址,覆盖 `webhook` |
102
+ | 其他 | 服务端错误 | 看返回的 `errmsg` |
103
+
104
+ ## 开发
105
+
106
+ ```sh
107
+ pnpm install
108
+ pnpm run build # tsc → lib/
109
+ pnpm test # 构建 + node --test(加签算法/配置解析/客户端封装/注册与中文报错)
110
+ ```
111
+
112
+ ## 协议
113
+
114
+ MIT。这是一个社区插件,与 DeepSeek 官方及钉钉官方无关;`@deepseek-ai/*` 为官方保留命名空间。
@@ -0,0 +1,17 @@
1
+ # dsh-dingtalk: 钉钉群机器人单向通知工具(DeepSeek Harness 插件)。
2
+ #
3
+ # 本行随插件附带【空配置】安装,绝不弄崩启动;配置前调用任何 dingtalk_*
4
+ # 工具都会返回明确的中文配置提示。请在你的 profile 的 cordis.patch.yml 里
5
+ # 覆盖 tool-dingtalk 行(后层覆盖前层),例如:
6
+ #
7
+ # - id: tool-dingtalk
8
+ # config:
9
+ # webhook: https://oapi.dingtalk.com/robot/send?access_token=你的token
10
+ # secret: SEC你的加签密钥 # 可选;也可用环境变量 DSH_DINGTALK_SECRET
11
+ #
12
+ # 获取方式:钉钉群 → 群设置 → 智能群助手 → 添加机器人 → 自定义(webhook),
13
+ # 复制 webhook 地址;安全设置勾选「加签」,复制加签密钥(secret)。
14
+ - insert:
15
+ - id: tool-dingtalk
16
+ name: dsh-dingtalk
17
+ config: {}
@@ -0,0 +1,34 @@
1
+ import type { DingTalkSendResult } from './types.js';
2
+ export interface FetchLikeResponse {
3
+ ok: boolean;
4
+ status: number;
5
+ json(): Promise<unknown>;
6
+ text(): Promise<string>;
7
+ }
8
+ export type FetchLike = (url: string, init?: Record<string, unknown>) => Promise<FetchLikeResponse>;
9
+ export declare class DingTalkError extends Error {
10
+ readonly errcode: number | undefined;
11
+ constructor(message: string, errcode?: number);
12
+ }
13
+ /** 把钉钉 errcode 映射为中文指引(模型与用户都读)。 */
14
+ export declare function dingTalkErrorMessage(errcode: number, errmsg: string): string;
15
+ export interface DingTalkClientOptions {
16
+ webhook: string;
17
+ secret?: string;
18
+ timeoutMs?: number;
19
+ /** 可注入的 fetch 实现(默认用全局 fetch),便于测试。 */
20
+ fetchImpl?: FetchLike;
21
+ }
22
+ /** 钉钉自定义机器人 webhook 客户端(单向发送,加签模式)。 */
23
+ export declare class DingTalkClient {
24
+ private readonly webhook;
25
+ private readonly secret;
26
+ private readonly timeoutMs;
27
+ private readonly fetchImpl;
28
+ constructor(options: DingTalkClientOptions);
29
+ /** 发送 Markdown 消息:{ msgtype: 'markdown', markdown: { title, text } } */
30
+ sendMarkdown(title: string, text: string): Promise<DingTalkSendResult>;
31
+ /** 发送纯文本消息:{ msgtype: 'text', text: { content } } */
32
+ sendText(content: string): Promise<DingTalkSendResult>;
33
+ private post;
34
+ }
package/lib/client.js ADDED
@@ -0,0 +1,82 @@
1
+ import { signedUrl } from './sign.js';
2
+ const defaultFetch = (url, init) => fetch(url, init);
3
+ export class DingTalkError extends Error {
4
+ errcode;
5
+ constructor(message, errcode) {
6
+ super(message);
7
+ this.name = 'DingTalkError';
8
+ this.errcode = errcode;
9
+ }
10
+ }
11
+ /** 把钉钉 errcode 映射为中文指引(模型与用户都读)。 */
12
+ export function dingTalkErrorMessage(errcode, errmsg) {
13
+ if (errcode === 310000) {
14
+ return '钉钉返回 310000(加签校验失败):请检查 secret 加签密钥是否填写正确、是否与机器人「安全设置-加签」一致;若同时勾选了「自定义关键词」,也请确认消息包含关键词。';
15
+ }
16
+ if (errcode === 120001) {
17
+ return '钉钉返回 120001(access_token 失效):webhook 里的 token 已过期或被重置,请到钉钉群重新复制机器人 webhook 地址,覆盖配置里的 webhook。';
18
+ }
19
+ return '钉钉返回错误 ' + errcode + ':' + (errmsg || '未知错误');
20
+ }
21
+ /** 钉钉自定义机器人 webhook 客户端(单向发送,加签模式)。 */
22
+ export class DingTalkClient {
23
+ webhook;
24
+ secret;
25
+ timeoutMs;
26
+ fetchImpl;
27
+ constructor(options) {
28
+ this.webhook = options.webhook;
29
+ this.secret = options.secret ?? '';
30
+ this.timeoutMs = options.timeoutMs ?? 10000;
31
+ this.fetchImpl = options.fetchImpl ?? defaultFetch;
32
+ }
33
+ /** 发送 Markdown 消息:{ msgtype: 'markdown', markdown: { title, text } } */
34
+ async sendMarkdown(title, text) {
35
+ return this.post({ msgtype: 'markdown', markdown: { title, text } });
36
+ }
37
+ /** 发送纯文本消息:{ msgtype: 'text', text: { content } } */
38
+ async sendText(content) {
39
+ return this.post({ msgtype: 'text', text: { content } });
40
+ }
41
+ async post(payload) {
42
+ const url = signedUrl(this.webhook, this.secret || undefined);
43
+ const controller = new AbortController();
44
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
45
+ let response;
46
+ try {
47
+ response = await this.fetchImpl(url, {
48
+ method: 'POST',
49
+ headers: { 'Content-Type': 'application/json' },
50
+ body: JSON.stringify(payload),
51
+ signal: controller.signal,
52
+ });
53
+ }
54
+ catch (error) {
55
+ const message = error instanceof Error ? error.message : String(error);
56
+ const hint = /abort/i.test(message) ? '(请求超时)' : '';
57
+ throw new DingTalkError('钉钉机器人请求失败' + hint + ':' + message);
58
+ }
59
+ finally {
60
+ clearTimeout(timer);
61
+ }
62
+ if (!response.ok) {
63
+ const body = await response.text().catch(() => '');
64
+ throw new DingTalkError('钉钉机器人返回 HTTP ' + response.status + ':' + body.slice(0, 200));
65
+ }
66
+ let body;
67
+ try {
68
+ body = await response.json();
69
+ }
70
+ catch {
71
+ throw new DingTalkError('钉钉机器人返回非 JSON 响应(HTTP ' + response.status + ')');
72
+ }
73
+ const result = body;
74
+ if (typeof result?.errcode !== 'number') {
75
+ throw new DingTalkError('钉钉机器人返回格式异常:缺少 errcode');
76
+ }
77
+ if (result.errcode !== 0) {
78
+ throw new DingTalkError(dingTalkErrorMessage(result.errcode, result.errmsg ?? ''), result.errcode);
79
+ }
80
+ return result;
81
+ }
82
+ }
@@ -0,0 +1,21 @@
1
+ export declare const DINGTALK_SECRET_ENV = "DSH_DINGTALK_SECRET";
2
+ export declare const DEFAULT_TIMEOUT_MS = 10000;
3
+ export interface DingTalkConfig {
4
+ /** 完整 webhook URL(必填),形如 https://oapi.dingtalk.com/robot/send?access_token=XXX */
5
+ webhook?: string;
6
+ /** 加签密钥(可选,推荐)。省略时回退环境变量 DSH_DINGTALK_SECRET */
7
+ secret?: string;
8
+ /** 请求超时毫秒数,默认 10000(1000–60000) */
9
+ timeoutMs?: number;
10
+ }
11
+ export interface ResolvedDingTalkConfig {
12
+ webhook: string;
13
+ secret: string;
14
+ timeoutMs: number;
15
+ }
16
+ /**
17
+ * 解析并校验原始行配置。webhook 缺失时抛出带中文指引的错误(模型与用户都读)。
18
+ * 只解析、不失败:secret 可选,未配置加签则不加签。
19
+ */
20
+ export declare function resolveDingTalkConfig(config: DingTalkConfig | undefined): ResolvedDingTalkConfig;
21
+ export declare function clampInt(value: unknown, fallback: number, min: number, max: number): number;
package/lib/config.js ADDED
@@ -0,0 +1,25 @@
1
+ export const DINGTALK_SECRET_ENV = 'DSH_DINGTALK_SECRET';
2
+ export const DEFAULT_TIMEOUT_MS = 10000;
3
+ /**
4
+ * 解析并校验原始行配置。webhook 缺失时抛出带中文指引的错误(模型与用户都读)。
5
+ * 只解析、不失败:secret 可选,未配置加签则不加签。
6
+ */
7
+ export function resolveDingTalkConfig(config) {
8
+ const raw = config ?? {};
9
+ const webhook = (typeof raw.webhook === 'string' ? raw.webhook : '').trim();
10
+ const secret = (typeof raw.secret === 'string' ? raw.secret : '').trim() || (process.env[DINGTALK_SECRET_ENV] ?? '').trim();
11
+ if (webhook === '') {
12
+ throw new Error('dsh-dingtalk 未配置:webhook(钉钉机器人完整地址)未填写。请在 profile 的 cordis.patch.yml 中覆盖 tool-dingtalk 行并重启(见插件 README)');
13
+ }
14
+ return {
15
+ webhook,
16
+ secret,
17
+ timeoutMs: clampInt(raw.timeoutMs, DEFAULT_TIMEOUT_MS, 1000, 60000),
18
+ };
19
+ }
20
+ export function clampInt(value, fallback, min, max) {
21
+ const n = typeof value === 'number' ? Math.trunc(value) : fallback;
22
+ if (!Number.isFinite(n))
23
+ return fallback;
24
+ return Math.min(max, Math.max(min, n));
25
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { type DingTalkConfig } from './config.js';
2
+ export declare const name = "tool-dingtalk";
3
+ export declare const inject: string[];
4
+ export type Config = DingTalkConfig;
5
+ export declare function apply(ctx: any, config?: Config): void;
6
+ export { resolveDingTalkConfig, DINGTALK_SECRET_ENV, DEFAULT_TIMEOUT_MS, clampInt } from './config.js';
7
+ export { DingTalkClient, DingTalkError, dingTalkErrorMessage } from './client.js';
8
+ export type { FetchLike, FetchLikeResponse, DingTalkClientOptions } from './client.js';
9
+ export { computeDingTalkSign, signedUrl, dingTalkTimestamp } from './sign.js';
package/lib/index.js ADDED
@@ -0,0 +1,106 @@
1
+ import { resolveDingTalkConfig } from './config.js';
2
+ import { DingTalkClient } from './client.js';
3
+ export const name = 'tool-dingtalk';
4
+ export const inject = ['tools'];
5
+ /**
6
+ * 把作者 DSL 映射编译成原生 JSON Schema 对象,作为 defineTool 的
7
+ * definition.parameters 原样下发。原生 wire 请求会逐字携带该值,塞原始 DSL
8
+ * 会被模型 API 拒绝("schema must be a JSON Schema of 'type: object'")。
9
+ */
10
+ function compileParameters(spec) {
11
+ const properties = {};
12
+ const required = [];
13
+ for (const [key, prop] of Object.entries(spec)) {
14
+ if (prop?.required === true)
15
+ required.push(key);
16
+ const node = {};
17
+ if (typeof prop?.type === 'string')
18
+ node.type = prop.type;
19
+ if (typeof prop?.description === 'string')
20
+ node.description = prop.description;
21
+ properties[key] = node;
22
+ }
23
+ return { type: 'object', properties, ...(required.length > 0 ? { required } : {}) };
24
+ }
25
+ const notifySchema = {
26
+ type: 'object',
27
+ properties: {
28
+ ok: { type: 'boolean' },
29
+ msgtype: { type: 'string' },
30
+ title: { type: 'string' },
31
+ errcode: { type: 'integer' },
32
+ errmsg: { type: 'string' },
33
+ },
34
+ additionalProperties: true,
35
+ };
36
+ function oneText(text) {
37
+ return [{ type: 'text', text }];
38
+ }
39
+ function renderNotify(value) {
40
+ if (value.msgtype === 'markdown') {
41
+ return oneText('已通过钉钉机器人发送 Markdown 消息(标题「' + value.title + '」),钉钉返回 errcode=' + value.errcode + ',errmsg=' + value.errmsg);
42
+ }
43
+ return oneText('已通过钉钉机器人发送文本消息,钉钉返回 errcode=' + value.errcode + ',errmsg=' + value.errmsg);
44
+ }
45
+ export function apply(ctx, config = {}) {
46
+ let client = null;
47
+ let fingerprint = '';
48
+ const getClient = () => {
49
+ const resolved = resolveDingTalkConfig(config);
50
+ const fp = JSON.stringify(resolved);
51
+ if (client === null || fp !== fingerprint) {
52
+ client = new DingTalkClient(resolved);
53
+ fingerprint = fp;
54
+ }
55
+ return client;
56
+ };
57
+ // 加载期仅提示,绝不弄崩启动;具体细节由各工具的 execute 抛出中文指引。
58
+ try {
59
+ getClient();
60
+ }
61
+ catch (error) {
62
+ ctx.logger?.warn?.('[dsh-dingtalk] ' + (error instanceof Error ? error.message : '未配置钉钉机器人'));
63
+ }
64
+ ctx.tools.register({
65
+ name: 'dingtalk_notify',
66
+ description: '向配置的钉钉群发送一条 Markdown 消息(单向通知)。title 为标题,text 为 Markdown 正文;需先在 profile 的 cordis.patch.yml 配置 webhook(与可选 secret 加签密钥)。',
67
+ parameters: compileParameters({
68
+ title: { type: 'string', required: true, description: '消息标题(显示在钉钉卡片顶部)' },
69
+ text: { type: 'string', required: true, description: 'Markdown 正文内容' },
70
+ }),
71
+ output: {
72
+ schema: notifySchema,
73
+ render: (_args, value) => renderNotify(value),
74
+ },
75
+ async execute(rawArgs) {
76
+ const args = rawArgs;
77
+ if (typeof args.title !== 'string' || args.title.trim() === '')
78
+ throw new Error('title 不能为空');
79
+ if (typeof args.text !== 'string' || args.text.trim() === '')
80
+ throw new Error('text 不能为空');
81
+ const result = await getClient().sendMarkdown(args.title, args.text);
82
+ return { ok: true, msgtype: 'markdown', title: args.title, errcode: result.errcode, errmsg: result.errmsg };
83
+ },
84
+ });
85
+ ctx.tools.register({
86
+ name: 'dingtalk_text',
87
+ description: '向配置的钉钉群发送一条纯文本消息(单向通知)。',
88
+ parameters: compileParameters({
89
+ content: { type: 'string', required: true, description: '纯文本消息内容' },
90
+ }),
91
+ output: {
92
+ schema: notifySchema,
93
+ render: (_args, value) => renderNotify(value),
94
+ },
95
+ async execute(rawArgs) {
96
+ const args = rawArgs;
97
+ if (typeof args.content !== 'string' || args.content.trim() === '')
98
+ throw new Error('content 不能为空');
99
+ const result = await getClient().sendText(args.content);
100
+ return { ok: true, msgtype: 'text', title: '', errcode: result.errcode, errmsg: result.errmsg };
101
+ },
102
+ });
103
+ }
104
+ export { resolveDingTalkConfig, DINGTALK_SECRET_ENV, DEFAULT_TIMEOUT_MS, clampInt } from './config.js';
105
+ export { DingTalkClient, DingTalkError, dingTalkErrorMessage } from './client.js';
106
+ export { computeDingTalkSign, signedUrl, dingTalkTimestamp } from './sign.js';
package/lib/sign.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ /** 当前毫秒时间戳(字符串)。 */
2
+ export declare function dingTalkTimestamp(now?: number): string;
3
+ /**
4
+ * 钉钉自定义机器人「加签」安全模式的签名。
5
+ *
6
+ * 官方算法:
7
+ * stringToSign = timestamp + "\n" + secret
8
+ * sign = urlencode(base64(HmacSHA256(secret, stringToSign)))
9
+ *
10
+ * 与官方 Python 示例的 urllib.parse.quote_plus(base64(...)) 等价:base64 输出
11
+ * 不含空格,只需编码 '+' '/' '=' 三个字符,encodeURIComponent 正好覆盖。
12
+ */
13
+ export declare function computeDingTalkSign(secret: string, timestamp: string): string;
14
+ /** 在 webhook 地址上追加 timestamp 与 sign 参数(secret 为空则原样返回)。 */
15
+ export declare function signedUrl(webhook: string, secret: string | undefined, timestamp?: string): string;
package/lib/sign.js ADDED
@@ -0,0 +1,29 @@
1
+ import { createHmac } from 'node:crypto';
2
+ /** 当前毫秒时间戳(字符串)。 */
3
+ export function dingTalkTimestamp(now = Date.now()) {
4
+ return String(Math.floor(now));
5
+ }
6
+ /**
7
+ * 钉钉自定义机器人「加签」安全模式的签名。
8
+ *
9
+ * 官方算法:
10
+ * stringToSign = timestamp + "\n" + secret
11
+ * sign = urlencode(base64(HmacSHA256(secret, stringToSign)))
12
+ *
13
+ * 与官方 Python 示例的 urllib.parse.quote_plus(base64(...)) 等价:base64 输出
14
+ * 不含空格,只需编码 '+' '/' '=' 三个字符,encodeURIComponent 正好覆盖。
15
+ */
16
+ export function computeDingTalkSign(secret, timestamp) {
17
+ const stringToSign = timestamp + '\n' + secret;
18
+ const hmac = createHmac('sha256', secret).update(stringToSign, 'utf8').digest();
19
+ return encodeURIComponent(hmac.toString('base64'));
20
+ }
21
+ /** 在 webhook 地址上追加 timestamp 与 sign 参数(secret 为空则原样返回)。 */
22
+ export function signedUrl(webhook, secret, timestamp) {
23
+ if (!secret)
24
+ return webhook;
25
+ const ts = timestamp ?? dingTalkTimestamp();
26
+ const sign = computeDingTalkSign(secret, ts);
27
+ const sep = webhook.includes('?') ? '&' : '?';
28
+ return webhook + sep + 'timestamp=' + ts + '&sign=' + sign;
29
+ }
package/lib/types.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ export interface DingTalkNotifyArgs {
2
+ title: string;
3
+ text: string;
4
+ }
5
+ export interface DingTalkTextArgs {
6
+ content: string;
7
+ }
8
+ export interface DingTalkNotifyResult {
9
+ ok: boolean;
10
+ msgtype: 'markdown' | 'text';
11
+ title: string;
12
+ errcode: number;
13
+ errmsg: string;
14
+ }
15
+ /** 钉钉 send 接口的原始返回(成功时 errcode === 0)。 */
16
+ export interface DingTalkSendResult {
17
+ errcode: number;
18
+ errmsg: string;
19
+ }
package/lib/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "dsh-dingtalk",
3
+ "version": "0.1.0",
4
+ "description": "钉钉群机器人单向通知工具(DeepSeek Harness 插件):agent 推送 Markdown / 纯文本消息到钉钉群,支持加签安全模式。",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./client": "./lib/client.js",
14
+ "./cordis.patch.yml": "./cordis.patch.yml",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": [
18
+ "lib",
19
+ "cordis.patch.yml",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "prepare": "tsc",
25
+ "prepublishOnly": "pnpm run build",
26
+ "test": "pnpm run build && node --test \"test/*.test.mjs\""
27
+ },
28
+ "keywords": [
29
+ "dsh-plugin",
30
+ "deepseek-harness",
31
+ "dsh",
32
+ "dingtalk",
33
+ "dingtalk-robot",
34
+ "webhook"
35
+ ],
36
+ "author": "dsh-community",
37
+ "license": "MIT",
38
+ "packageManager": "pnpm@11.7.0",
39
+ "engines": {
40
+ "node": ">=20"
41
+ },
42
+ "dsh": {
43
+ "bundle": {
44
+ "patch": "./cordis.patch.yml"
45
+ }
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^26.2.0",
49
+ "typescript": "^5.7.3"
50
+ },
51
+ "repository": {
52
+ "url": "https://github.com/STARDUSTLC666/dsh-dingtalk",
53
+ "type": "git"
54
+ },
55
+ "bugs": {
56
+ "url": "https://github.com/STARDUSTLC666/dsh-dingtalk/issues"
57
+ },
58
+ "homepage": "https://github.com/STARDUSTLC666/dsh-dingtalk#readme"
59
+ }