pm-slack-standup 2026.6.2 → 2026.6.4-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/CHANGELOG.md CHANGED
@@ -1,6 +1,35 @@
1
1
  # Changelog
2
2
 
3
- ## 2026.6.2 - 2026-06-02
3
+ ## 2026.6.4-1 - 2026-06-04
4
+
5
+ ### Added
6
+
7
+ - preflight: fail-fast Slack-credential gate for standup post path ([pm-slack-standup-qnkp](https://github.com/unbraind/pm-slack-standup/blob/main/.agents/pm/features/pm-slack-standup-qnkp.toon))
8
+
9
+ ## 2026.06.04 - 2026-06-04
10
+
11
+ ### Added
12
+
13
+ - Blocker inference, yesterday/today split, multi-channel, fallback-to-stdout, custom section labels ([pm-slack-standup-8s6b](https://github.com/unbraind/pm-slack-standup/blob/main/.agents/pm/features/pm-slack-standup-8s6b.toon))
14
+
15
+ ## 2026.06.03 - 2026-06-02
16
+
17
+ ### Added
18
+
19
+ - Deep feature expansion: multi-format output, grouping, sections, date windows ([pm-slack-standup-h64p](https://github.com/unbraind/pm-slack-standup/blob/main/.agents/pm/features/pm-slack-standup-h64p.toon))
20
+ - Add --days relative date window alongside --since ([pm-slack-standup-5sow](https://github.com/unbraind/pm-slack-standup/blob/main/.agents/pm/tasks/pm-slack-standup-5sow.toon))
21
+ - Add --sections selection/ordering with dedicated Blocked section ([pm-slack-standup-jimx](https://github.com/unbraind/pm-slack-standup/blob/main/.agents/pm/tasks/pm-slack-standup-jimx.toon))
22
+ - Add --group-by sprint and type \(extend assignee/status\) ([pm-slack-standup-f4m6](https://github.com/unbraind/pm-slack-standup/blob/main/.agents/pm/tasks/pm-slack-standup-f4m6.toon))
23
+ - Add --format slack\|blockkit\|markdown\|plain unified output ([pm-slack-standup-dgyy](https://github.com/unbraind/pm-slack-standup/blob/main/.agents/pm/tasks/pm-slack-standup-dgyy.toon))
24
+
25
+ ### Other
26
+
27
+ - Keep slack message text byte-identical; --format governs printed output only \(blockkit JSON no longer always dumped in dry-run\) ([pm-slack-standup-rttw](https://github.com/unbraind/pm-slack-standup/blob/main/.agents/pm/decisions/pm-slack-standup-rttw.toon))
28
+ - Block Kit schema: single header \(150ch\), one mrkdwn section per bucket \(3000ch truncation\), divider + context footer ([pm-slack-standup-rpty](https://github.com/unbraind/pm-slack-standup/blob/main/.agents/pm/decisions/pm-slack-standup-rpty.toon))
29
+ - Export pure helpers + node:test unit suite \(formatters/grouping/window\) ([pm-slack-standup-uf4j](https://github.com/unbraind/pm-slack-standup/blob/main/.agents/pm/tasks/pm-slack-standup-uf4j.toon))
30
+ - Missing-creds real post -\> CommandError \(exit 1\); no network in dry-run ([pm-slack-standup-m9ze](https://github.com/unbraind/pm-slack-standup/blob/main/.agents/pm/tasks/pm-slack-standup-m9ze.toon))
31
+
32
+ ## 2026.06.02 - 2026-06-02
4
33
 
5
34
  ### Added
6
35
 
package/README.md CHANGED
@@ -5,12 +5,20 @@ A [pm-cli](https://github.com/unbraind/pm-cli) extension that posts your current
5
5
  ## Features
6
6
 
7
7
  - Posts WIP, Blocked, Up Next (and optional Done) items to Slack as a rich **Block Kit** message (header, one section per bucket, context footer) with an automatic plain-text fallback
8
- - `pm standup export` write the standup to a file as Markdown or JSON (the JSON form includes the full Block Kit payload for archiving or re-posting)
9
- - Group section items by `status` (default) or `assignee` (`--group-by`)
8
+ - **Four output formats** (`--format`): `slack` (Slack mrkdwn, the default), `blockkit` (the raw Block Kit `blocks` JSON), `markdown` (GitHub/CommonMark), and `plain` text
9
+ - **`--dry-run`** build and print the message in the chosen format **without** posting to Slack (no network call is made)
10
+ - Group section items by `status` (default), `assignee`, `sprint`, or `type` (`--group-by`)
11
+ - Choose which sections to render and in what order with `--sections` (`in_progress`, `blocked`, `done`, `up_next`); a dedicated **Blocked** section always surfaces blocked items
12
+ - **Impediment inference** — the Blocked section surfaces not only `status=blocked` items but any open/in-progress item carrying a `blocked_by` dependency (top-level or in `dependencies[]`), so dependency-blocked work is never hidden
13
+ - **Yesterday/Today split** (`--yesterday`) — split the Done section into **Done Yesterday** / **Done Today** by the local-day boundary (implies `--include-done`)
14
+ - **Multi-channel posting** (`--channels #a,#b`) — post the same standup to several channel names and/or webhook URLs in one run, each message labelled with its own channel
15
+ - **`--fallback-to-stdout`** — if a Slack post fails, print the rendered standup to stdout (exit 0) instead of erroring out, so the work isn't lost on a transport failure
16
+ - **Custom section labels** (`--section-labels`) — override any section's title and/or emoji, e.g. `in_progress=Rolling,blocked=🔥 On Fire`
17
+ - Scope the "recently closed / Done" window with `--since <iso>` **or** `--days <n>` (relative)
10
18
  - Map pm authors to Slack handles so they get mentioned (`--mention-map`)
11
- - Restrict the Done section to a recent window (`--since <iso>`, with `--include-done`)
12
- - Dry-run mode to preview both the rendered message and the Block Kit JSON
13
- - Webhook URL configurable via flag or environment variable; missing webhook is a graceful no-op (never blocks a workflow)
19
+ - `pm standup export` — write the standup to a file as Markdown or JSON (the JSON form includes the full Block Kit payload for archiving or re-posting)
20
+ - `--channel` override; webhook URL configurable via flag or environment variable
21
+ - **Fail-fast credential preflight** when you actually request a Slack post (anything other than `--dry-run`) but no webhook is configured, the command aborts **immediately** with a clear, actionable, non-zero error (exit 2) **before** reading any pm data or rendering anything — no half-built message, no crash. The non-posting `--dry-run` preview path is never gated, so previewing without credentials keeps working. See [Credential preflight](#credential-preflight).
14
22
 
15
23
  ## Installation
16
24
 
@@ -58,12 +66,35 @@ pm standup [flags]
58
66
  |------|------|---------|-------------|
59
67
  | `--webhook <url>` | string | — | Slack incoming webhook URL (overrides `PM_SLACK_WEBHOOK`) |
60
68
  | `--channel <name>` | string | — | Channel name shown in the message (e.g. `#team-eng`) |
61
- | `--dry-run` | boolean | `false` | Print the rendered message and the Block Kit JSON without posting |
69
+ | `--dry-run` | boolean | `false` | Build and print the message in the chosen format **without** posting |
70
+ | `--format <fmt>` | string | `slack` | Output format: `slack` (mrkdwn) \| `blockkit` (JSON) \| `markdown` \| `plain` |
62
71
  | `--include-done` | boolean | `false` | Include recently-closed items in a Done section |
63
- | `--since <iso>` | string | — | ISO date/time window; filters the Done section to items updated since then |
64
- | `--group-by <status\|assignee>` | string | `status` | Group section items by status (default) or assignee |
72
+ | `--since <iso>` | string | — | ISO date/time window; scopes the Done section to items updated since then |
73
+ | `--days <n>` | number | | Relative window: scope Done to items updated in the last N days (combines with `--since`, more restrictive bound wins) |
74
+ | `--group-by <field>` | string | `status` | Group section items by `status` \| `assignee` \| `sprint` \| `type` |
75
+ | `--sections <list>` | string | all | Comma list of sections to render/order: `in_progress`, `blocked`, `done`, `up_next` |
65
76
  | `--mention-map <map>` | string | — | Map pm authors to Slack handles, e.g. `alice=@alice,bob=@bob` |
66
- | `--format <slack\|text>` | string | `slack` | Plain-text rendering used for the fallback / preview |
77
+ | `--yesterday` | boolean | `false` | Split Done into **Done Yesterday** / **Done Today** by local day (implies `--include-done`) |
78
+ | `--channels <list>` | string | — | Post the same standup to multiple targets: comma list of `#channel` names and/or full webhook URLs |
79
+ | `--fallback-to-stdout` | boolean | `false` | If the Slack post fails, print the rendered standup to stdout (exit 0) instead of erroring |
80
+ | `--section-labels <map>` | string | — | Override section titles/emoji, e.g. `in_progress=Rolling,blocked=🔥 On Fire` |
81
+
82
+ > **Note on `text`:** the legacy `--format text` value is still accepted as an alias for `plain`.
83
+
84
+ ### Output formats
85
+
86
+ `--format` selects what the command prints (in `--dry-run`) and how it renders the Slack message text:
87
+
88
+ - **`slack`** (default) — Slack mrkdwn (`*bold*`, `_italic_`). The message posted to Slack is unchanged from previous versions.
89
+ - **`blockkit`** — the raw Slack Block Kit `{ "blocks": [...] }` JSON, ready to POST to `chat.postMessage` or paste into Block Kit Builder.
90
+ - **`markdown`** — GitHub/CommonMark with `#`/`##` headings and `-` bullets, for issues, wikis, or PR comments.
91
+ - **`plain`** — emphasis-free plain text for email or terminals.
92
+
93
+ ```bash
94
+ pm standup --dry-run --format blockkit # raw Block Kit JSON
95
+ pm standup --dry-run --format markdown --include-done # CommonMark
96
+ pm standup --dry-run --format plain --days 7 # plain text, last 7 days
97
+ ```
67
98
 
68
99
  ## Export
69
100
 
@@ -96,7 +127,33 @@ pm standup --channel '#team-eng' --dry-run
96
127
 
97
128
  **Include done items and use plain text format:**
98
129
  ```bash
99
- pm standup --include-done --format text
130
+ pm standup --include-done --format plain
131
+ ```
132
+
133
+ **Group by sprint, only In Progress + Blocked:**
134
+ ```bash
135
+ pm standup --dry-run --group-by sprint --sections in_progress,blocked
136
+ ```
137
+
138
+ **Split Done by yesterday/today:**
139
+ ```bash
140
+ pm standup --dry-run --yesterday --format plain
141
+ ```
142
+
143
+ **Post to multiple channels at once:**
144
+ ```bash
145
+ pm standup --channels '#team-eng,#standups'
146
+ pm standup --channels 'https://hooks.slack.com/services/AAA,https://hooks.slack.com/services/BBB'
147
+ ```
148
+
149
+ **Never lose the standup if Slack is down:**
150
+ ```bash
151
+ pm standup --fallback-to-stdout # prints the message to stdout (exit 0) if the post fails
152
+ ```
153
+
154
+ **Rename sections / change emoji:**
155
+ ```bash
156
+ pm standup --dry-run --section-labels 'in_progress=Rolling,blocked=🔥 On Fire'
100
157
  ```
101
158
 
102
159
  **Full explicit invocation:**
@@ -132,7 +189,7 @@ pm standup \
132
189
  • [Chore] Update dependencies (priority 3)
133
190
  ```
134
191
 
135
- ### Plain Text (`--format text`)
192
+ ### Plain Text (`--format plain`)
136
193
 
137
194
  ```
138
195
  📊 pm standup — 2026-05-09
@@ -150,6 +207,31 @@ pm standup \
150
207
  • [Chore] Update dependencies (priority 3)
151
208
  ```
152
209
 
210
+ ## Credential preflight
211
+
212
+ Before any post is attempted, `pm standup` runs a **fail-fast credential gate**:
213
+
214
+ - **A post is "requested"** whenever you do *not* pass `--dry-run`. (`--fallback-to-stdout` still counts as a post — it only changes how a *transport* failure is handled; it still needs a webhook to attempt delivery.)
215
+ - **The gate fires only when** a post is requested **and** no usable webhook is configured. A webhook is "configured" if `--webhook` or `PM_SLACK_WEBHOOK` is set, **or** every `--channels` target is a full webhook URL (bare `#name` channels still need the base webhook).
216
+ - **When it fires**, the command aborts **immediately** — before reading any pm items or rendering anything — with a clear, actionable error and a **non-zero exit (2 / usage)**. Nothing is posted.
217
+ - **When it does not fire** (a webhook is present, *or* you used `--dry-run`), the command proceeds exactly as before.
218
+
219
+ ```bash
220
+ # Post requested, no webhook → immediate abort (exit 2), nothing posted:
221
+ pm standup --channel '#team-eng'
222
+ # Slack post requested but no webhook is configured. Set PM_SLACK_WEBHOOK or
223
+ # pass --webhook <url> (or provide full webhook URLs via --channels).
224
+ # To preview without posting, use --dry-run.
225
+
226
+ # Preview without credentials → always allowed (exit 0):
227
+ pm standup --dry-run
228
+
229
+ # With a webhook → preflight passes, the post proceeds:
230
+ PM_SLACK_WEBHOOK=https://hooks.slack.com/services/... pm standup --channel '#team-eng'
231
+ ```
232
+
233
+ This is implemented in the `standup` command handler (where a thrown error reliably aborts with a non-zero exit); the package also declares the `preflight` capability and registers a scoped pass-through preflight hook for the standup command.
234
+
153
235
  ## Environment Variables
154
236
 
155
237
  | Variable | Description |
@@ -173,10 +255,10 @@ npm run dev # watch mode
173
255
 
174
256
  ## How It Works
175
257
 
176
- 1. Reads every item once via `pm --path <root> list-all --json --include-body` and buckets them locally into In Progress, Blocked, open (Up Next) and optionally Done.
258
+ 1. Reads every item once via `pm --path <root> list-all --json --include-body` and buckets them locally into In Progress, Blocked, open (Up Next) and optionally Done. Any open/in-progress item with a `blocked_by` dependency (top-level or in `dependencies[]`) is re-bucketed into Blocked; closed items are never re-surfaced as blocked.
177
259
  2. Sorts open items by priority (ascending) and takes the top 3 as "Up Next".
178
- 3. Builds a Slack Block Kit `blocks` array (header + a section per bucket + context footer) plus a plain-text `fallback`, optionally grouped by assignee and annotated with Slack mentions.
179
- 4. Posts `{ text, blocks }` to the configured Slack webhook using Node.js native `https`. A missing webhook or a network failure is a non-blocking no-op (warns and exits 0).
260
+ 3. Builds a Slack Block Kit `blocks` array (header + a section per bucket + context footer) plus a plain-text `fallback`, optionally grouped by assignee and annotated with Slack mentions. Section titles/emoji can be overridden with `--section-labels`, and `--yesterday` expands Done into Done Yesterday / Done Today.
261
+ 4. In `--dry-run`, prints the message in the chosen `--format` and exits without any network call. Otherwise posts `{ text, blocks }` to each target (the base webhook plus any `--channels`) using Node.js native `https`. A missing webhook (on a real post) raises a structured `CommandError`; a failed post does too **unless** `--fallback-to-stdout` is set, in which case the rendered standup is printed to stdout and the command exits 0.
180
262
 
181
263
  ## License
182
264
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,271 @@
1
+ export declare class CommandError extends Error {
2
+ exitCode: number;
3
+ constructor(message: string, exitCode?: number);
4
+ }
5
+ export interface PmDependency {
6
+ id?: string;
7
+ kind?: string;
8
+ [key: string]: unknown;
9
+ }
10
+ export interface PmItem {
11
+ id: string;
12
+ title: string;
13
+ status: string;
14
+ type?: string;
15
+ priority?: number;
16
+ tags?: string[];
17
+ milestone?: string;
18
+ release?: string;
19
+ sprint?: string;
20
+ assignee?: string;
21
+ author?: string;
22
+ body?: string;
23
+ created_at?: string;
24
+ updated_at?: string;
25
+ blocked_by?: string;
26
+ dependencies?: PmDependency[];
27
+ }
28
+ export type Format = "slack" | "blockkit" | "markdown" | "plain";
29
+ export type GroupBy = "status" | "assignee" | "sprint" | "type";
30
+ export type SectionKey = "in_progress" | "blocked" | "done" | "up_next";
31
+ export declare const ALL_SECTIONS: readonly SectionKey[];
32
+ export interface StandupData {
33
+ wip: PmItem[];
34
+ blocked: PmItem[];
35
+ done: PmItem[];
36
+ upNext: PmItem[];
37
+ total: number;
38
+ doneYesterday?: PmItem[];
39
+ doneToday?: PmItem[];
40
+ }
41
+ export interface StandupOptions {
42
+ channel?: string;
43
+ format: Format;
44
+ includeDone: boolean;
45
+ since?: string;
46
+ groupBy: GroupBy;
47
+ sections: SectionKey[];
48
+ mentionMap: Record<string, string>;
49
+ splitYesterday: boolean;
50
+ sectionLabels: Partial<Record<SectionKey, SectionLabelOverride>>;
51
+ }
52
+ export interface SectionLabelOverride {
53
+ emoji?: string;
54
+ title?: string;
55
+ }
56
+ export declare function readBoolOption(options: Record<string, unknown>, key: string): boolean;
57
+ export declare function readStrOption(options: Record<string, unknown>, key: string): string | undefined;
58
+ /**
59
+ * Parse a `--mention-map` spec mapping pm authors to Slack handles.
60
+ * Accepts `author=@handle,other=@h2` (commas) or semicolon separators.
61
+ * A leading `@` on the handle is optional and normalized on.
62
+ */
63
+ export declare function parseMentionMap(spec: string | undefined): Record<string, string>;
64
+ /**
65
+ * Normalize a `--format` value. Accepts the four public formats plus the
66
+ * legacy `text` alias (== `plain`). Unknown values raise a USAGE CommandError.
67
+ */
68
+ export declare function parseFormat(raw: string | undefined): Format;
69
+ export declare function parseGroupBy(raw: string | undefined): GroupBy;
70
+ /**
71
+ * Parse a `--sections` spec (comma/semicolon list) into an ordered, de-duped
72
+ * list of section keys. Empty spec → all sections in default order. An
73
+ * unknown token is a USAGE error rather than a silent drop.
74
+ */
75
+ export declare function parseSections(spec: string | undefined): SectionKey[];
76
+ /**
77
+ * Parse a `--section-labels` spec overriding section titles (and optionally
78
+ * an emoji). Accepts `key=Label,other=Label2` (comma/semicolon separated).
79
+ * The label value may itself lead with an emoji + space, e.g.
80
+ * `blocked=🔥 On Fire` sets emoji "🔥" and title "On Fire"; a label with no
81
+ * leading emoji keeps the section's default emoji and only changes the title.
82
+ * Keys use the same aliases as `--sections` (wip→in_progress, etc.).
83
+ * Unknown keys are a USAGE error rather than a silent drop.
84
+ */
85
+ export declare function parseSectionLabels(spec: string | undefined): Partial<Record<SectionKey, SectionLabelOverride>>;
86
+ /**
87
+ * Parse a `--channels` spec (comma/semicolon list) into an ordered, de-duped
88
+ * list of channel targets. Each target is either a Slack channel name
89
+ * (e.g. `#team-eng`) or a full webhook URL — multi-channel posting accepts
90
+ * both (a name is shown in the message; a URL is POSTed to). Empty → [].
91
+ */
92
+ export declare function parseChannels(spec: string | undefined): string[];
93
+ /** True when a channel token is a full webhook URL rather than a name. */
94
+ export declare function isWebhookUrl(token: string): boolean;
95
+ /**
96
+ * Resolve the "recently closed" window start (ms epoch) from `--since` and/or
97
+ * `--days`. `--since` is an explicit ISO date/time; `--days <n>` is N days
98
+ * before now. If both are given the *later* (more restrictive) bound wins.
99
+ * Returns NaN when neither is set (no windowing). Invalid input → USAGE error.
100
+ */
101
+ export declare function resolveSinceMs(since: string | undefined, days: number | undefined, now?: number): number;
102
+ export declare function parseDays(raw: string | undefined): number | undefined;
103
+ /**
104
+ * Read every item once via `list-all --json --include-body`, then bucket by
105
+ * status locally. This is a single pm invocation (vs. four list-by-status
106
+ * calls) and gives us bodies + assignee + timestamps for grouping/windowing.
107
+ */
108
+ export declare function fetchAllItems(pmRoot: string): PmItem[];
109
+ /**
110
+ * True when an item's last activity falls within the [sinceMs, now] window.
111
+ * NaN sinceMs means "no window" → always true.
112
+ */
113
+ export declare function withinWindow(item: PmItem, sinceMs: number): boolean;
114
+ /**
115
+ * True when an item carries a `blocked_by` dependency, regardless of its
116
+ * status. pm surfaces this either as a top-level `blocked_by` string (item ID
117
+ * or free-text reason) or as one/more `dependencies` entries with
118
+ * `kind: "blocked_by"`. Used to surface impediments that are NOT explicitly
119
+ * status=blocked under the Blocked section.
120
+ */
121
+ export declare function hasBlockedByDep(item: PmItem): boolean;
122
+ /**
123
+ * Local-day key (YYYY-MM-DD in the host's local timezone) for an item's last
124
+ * activity. Used by the `--yesterday` split. Falls back to created_at, then
125
+ * to the empty string when no timestamp is parseable.
126
+ */
127
+ export declare function localDayKey(item: PmItem): string;
128
+ /** Local-day key (YYYY-MM-DD) for a given epoch-ms instant. */
129
+ export declare function localDayKeyOf(ms: number): string;
130
+ /**
131
+ * Bucket items into standup sections.
132
+ * `sinceMs` (epoch ms, NaN = no window) filters the Done section to items
133
+ * updated within the window; WIP/blocked/up-next always reflect current state.
134
+ */
135
+ export declare function buildStandupData(items: PmItem[], opts: StandupOptions, sinceMs?: number, now?: number): StandupData;
136
+ interface SectionDef {
137
+ key: SectionKey;
138
+ emoji: string;
139
+ title: string;
140
+ items: PmItem[];
141
+ emptyNote: string | null;
142
+ withPriority: boolean;
143
+ }
144
+ /**
145
+ * Resolve the ordered, selected section definitions for the given data.
146
+ * `in_progress` and `blocked` always render (even empty, with their note);
147
+ * `done` and `up_next` only render when they hold items — preserving the
148
+ * historical message shape. `--sections` filters which keys are eligible.
149
+ *
150
+ * When `--yesterday` is active and Done has items, the single Done section is
151
+ * expanded into "Done Yesterday" + "Done Today" (only the non-empty subsets
152
+ * render), preserving the section's position in the ordering.
153
+ */
154
+ export declare function resolveSections(data: StandupData, opts: StandupOptions): SectionDef[];
155
+ export declare function itemText(item: PmItem, mentionMap: Record<string, string>, withPriority?: boolean): string;
156
+ /**
157
+ * Group a list of items by the configured field (assignee, sprint or type).
158
+ * Items missing the field bucket under a synthetic "_none" key (rendered as a
159
+ * friendly label). Returns entries sorted by group key for stable output.
160
+ */
161
+ export declare function groupItems(items: PmItem[], groupBy: GroupBy): Array<[string, PmItem[]]>;
162
+ /**
163
+ * Render the standup as a single text blob for the chosen non-Block-Kit
164
+ * format. `slack` is byte-identical to the historical output (mrkdwn);
165
+ * `plain` drops emphasis punctuation; `markdown` uses `#`/`**`/`-`.
166
+ */
167
+ export declare function buildTextMessage(data: StandupData, opts: StandupOptions): string;
168
+ export interface SlackBlock {
169
+ type: string;
170
+ [key: string]: unknown;
171
+ }
172
+ /**
173
+ * Build a Slack Block Kit `blocks` array: a header, a section per selected
174
+ * standup bucket and a context footer. Returns the blocks plus a plain-text
175
+ * `fallback` Slack renders in notifications and old clients.
176
+ *
177
+ * Block Kit schema choices: a single `header` block (plain_text, capped at
178
+ * Slack's 150-char limit), one `section`/`mrkdwn` block per bucket (Slack
179
+ * caps section text at 3000 chars — long buckets are truncated with an
180
+ * ellipsis to stay valid), a `divider`, then a `context` footer summarizing
181
+ * counts / window / grouping.
182
+ */
183
+ export declare function buildBlockKit(data: StandupData, opts: StandupOptions): {
184
+ blocks: SlackBlock[];
185
+ fallback: string;
186
+ };
187
+ /**
188
+ * Render the standup in whichever `--format` was selected, as the string the
189
+ * command prints (dry-run) or the exporter writes. `blockkit` returns the
190
+ * pretty-printed `{ blocks }` JSON; everything else returns text.
191
+ */
192
+ export declare function renderStandup(data: StandupData, opts: StandupOptions): string;
193
+ /** One resolved post target: the webhook URL to POST to + the channel name. */
194
+ export interface PostTarget {
195
+ webhookUrl: string;
196
+ /** Channel name shown in the message (may differ per target). */
197
+ channel?: string;
198
+ }
199
+ export interface PostResultEntry {
200
+ channel?: string;
201
+ ok: boolean;
202
+ error?: string;
203
+ }
204
+ /** A poster sends one payload to one webhook. Injectable for testing. */
205
+ export type Poster = (webhookUrl: string, payload: Record<string, unknown>) => Promise<void>;
206
+ /**
207
+ * Resolve the ordered list of post targets from `--webhook`/env + `--channel`
208
+ * + `--channels`. Each `--channels` token is either a `#name` (posted to the
209
+ * base webhook, just changing the displayed channel) or a full webhook URL
210
+ * (posted to that URL). When no `--channels` is given, a single target using
211
+ * the base webhook + `--channel` is returned. De-dupes (webhook,channel) pairs.
212
+ */
213
+ export declare function resolvePostTargets(baseWebhook: string, baseChannel: string | undefined, channels: string[]): PostTarget[];
214
+ /**
215
+ * Post the standup to every resolved target, re-rendering per target so each
216
+ * channel's message shows its own channel name. Returns a per-target result;
217
+ * never throws — the caller decides how to treat failures (e.g. fallback to
218
+ * stdout). The `poster` is injectable so this is testable without a network.
219
+ */
220
+ export declare function postStandupTargets(targets: PostTarget[], data: StandupData, baseOpts: StandupOptions, poster: Poster): Promise<PostResultEntry[]>;
221
+ /**
222
+ * Decide whether the `standup` invocation is actually going to *post* to Slack.
223
+ *
224
+ * The command has two non-posting shapes that must NOT be gated:
225
+ * - `--dry-run` : build + print the message, never touches Slack.
226
+ * Every other shape is a real post attempt (the default), including
227
+ * `--fallback-to-stdout` — that flag only means "print instead of erroring *if
228
+ * the network post fails*", it still REQUIRES a webhook to attempt the post.
229
+ */
230
+ export declare function isPostRequested(options: Record<string, unknown>): boolean;
231
+ /**
232
+ * Resolve whether a *base* Slack webhook is required for this post. `--channels`
233
+ * may carry full webhook URLs that are self-sufficient targets; but if there is
234
+ * no `--channels` at all, or any `--channels` entry is a bare `#name`, the base
235
+ * webhook (`--webhook` / `PM_SLACK_WEBHOOK`) is needed to actually deliver.
236
+ */
237
+ export declare function needsBaseWebhook(channels: string[]): boolean;
238
+ /**
239
+ * Fail-fast credential preflight for the standup *post* path.
240
+ *
241
+ * Fires ONLY when a Slack post is actually requested (not `--dry-run`) AND the
242
+ * credentials needed to deliver it are missing. In that case it throws a
243
+ * structured {@link CommandError} (USAGE / exit 2) BEFORE any pm data is read
244
+ * or any message is rendered — a clean, actionable, non-zero abort.
245
+ *
246
+ * It deliberately does NOT block the legitimate non-posting shapes:
247
+ * - `--dry-run` (preview to stdout) is never gated.
248
+ * This keeps the existing stdout-fallback behaviour intact while turning a
249
+ * "we got all the way to the transport layer and then discovered there's no
250
+ * webhook" failure into an immediate, obvious one.
251
+ *
252
+ * NOTE: this is invoked from the command HANDLER (not from `registerPreflight`)
253
+ * on purpose. pm's runtime wraps `registerPreflight` overrides in a try/catch
254
+ * and downgrades any thrown error to a non-fatal warning, so a throw there does
255
+ * NOT abort the command. Throwing from the handler is the only reliable way to
256
+ * fail-fast with a non-zero exit. The `registerPreflight` registration below is
257
+ * a scoped pass-through that exists to surface the `preflight` capability.
258
+ */
259
+ export declare function preflightSlackCredentials(options: Record<string, unknown>): void;
260
+ /**
261
+ * Resolve every standup option except the render `format`, which differs
262
+ * between the command (slack|blockkit|markdown|plain) and the exporter
263
+ * (md|json file format). Callers supply the format they want.
264
+ */
265
+ export declare function resolveStandupOptions(options: Record<string, unknown>, format: Format): {
266
+ opts: StandupOptions;
267
+ sinceMs: number;
268
+ };
1
269
  declare const _default: {
2
270
  name: string;
3
271
  version: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;AA2aA,wBAuJG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}