geekbot-cli 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/LICENSE +21 -0
- package/README.md +517 -0
- package/package.json +50 -0
- package/scripts/postinstall.mjs +27 -0
- package/skills/geekbot/SKILL.md +281 -0
- package/skills/geekbot/check-cli.sh +36 -0
- package/skills/geekbot/cli-commands.md +382 -0
- package/skills/geekbot/error-recovery.md +95 -0
- package/skills/geekbot/manager-workflows.md +408 -0
- package/skills/geekbot/reporter-workflows.md +275 -0
- package/skills/geekbot/standup-templates.json +244 -0
- package/src/auth/keychain.ts +38 -0
- package/src/auth/resolver.ts +44 -0
- package/src/cli/commands/auth.ts +56 -0
- package/src/cli/commands/me.ts +34 -0
- package/src/cli/commands/poll.ts +91 -0
- package/src/cli/commands/report.ts +66 -0
- package/src/cli/commands/standup.ts +234 -0
- package/src/cli/commands/team.ts +40 -0
- package/src/cli/globals.ts +31 -0
- package/src/cli/index.ts +94 -0
- package/src/errors/cli-error.ts +16 -0
- package/src/errors/error-handler.ts +63 -0
- package/src/errors/exit-codes.ts +14 -0
- package/src/errors/not-found-helper.ts +86 -0
- package/src/handlers/auth-handlers.ts +152 -0
- package/src/handlers/me-handlers.ts +27 -0
- package/src/handlers/poll-handlers.ts +187 -0
- package/src/handlers/report-handlers.ts +87 -0
- package/src/handlers/standup-handlers.ts +534 -0
- package/src/handlers/team-handlers.ts +38 -0
- package/src/http/authenticated-client.ts +17 -0
- package/src/http/client.ts +138 -0
- package/src/http/errors.ts +134 -0
- package/src/output/envelope.ts +50 -0
- package/src/output/formatter.ts +12 -0
- package/src/schemas/common.ts +13 -0
- package/src/schemas/poll.ts +89 -0
- package/src/schemas/report.ts +124 -0
- package/src/schemas/standup.ts +64 -0
- package/src/schemas/team.ts +11 -0
- package/src/schemas/user.ts +70 -0
- package/src/types.ts +30 -0
- package/src/utils/constants.ts +24 -0
- package/src/utils/input-parsers.ts +234 -0
- package/src/utils/receipt.ts +94 -0
- package/src/utils/validation.ts +128 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Reporter Workflows
|
|
2
|
+
|
|
3
|
+
Detailed guide for the AI-assisted reporting experience. The SKILL.md
|
|
4
|
+
covers the 5-step pipeline at a high level; this document covers the
|
|
5
|
+
full flow including tone calibration, blocker carry-over, and edge cases.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Report Drafting Pipeline (detailed)](#report-drafting-pipeline)
|
|
10
|
+
2. [Tone Calibration](#tone-calibration)
|
|
11
|
+
3. [Blocker Carry-Over Logic](#blocker-carry-over-logic)
|
|
12
|
+
4. [Edge Cases](#edge-cases)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Report Drafting Pipeline
|
|
17
|
+
|
|
18
|
+
### Step 1: Identify the standup
|
|
19
|
+
|
|
20
|
+
Run `geekbot standup list` to get the user's standups.
|
|
21
|
+
|
|
22
|
+
- **Single standup**: Use it automatically. Mention which one: "I see you're
|
|
23
|
+
in the 'Daily Standup' — let's draft your report for that."
|
|
24
|
+
- **Multiple standups**: Present a short list (ID, name, channel) and ask
|
|
25
|
+
which one. If the user's request hints at one ("my daily"), match on name.
|
|
26
|
+
- **No standups**: The user might not be a participant in any standup.
|
|
27
|
+
Suggest they check with their manager or run `geekbot standup list --admin`
|
|
28
|
+
if they're an admin.
|
|
29
|
+
|
|
30
|
+
### Step 2: Fetch questions
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
geekbot standup get <standup_id>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Extract from the response:
|
|
37
|
+
- `questions[].id` — you need these for the report submission
|
|
38
|
+
- `questions[].text` — show these to the user and use as draft structure
|
|
39
|
+
|
|
40
|
+
Store the question ID ↔ text mapping for use in step 5.
|
|
41
|
+
|
|
42
|
+
### Step 3: Gather context
|
|
43
|
+
|
|
44
|
+
Context comes from three sources. Check them in this order — each one makes
|
|
45
|
+
the draft richer.
|
|
46
|
+
|
|
47
|
+
#### Source A: Connected MCP servers (opportunistic enrichment)
|
|
48
|
+
|
|
49
|
+
Check what MCP servers are available in the current session. Use whatever
|
|
50
|
+
is connected to pull the user's recent activity. This is what transforms
|
|
51
|
+
"help me write my standup" from a Q&A session into a one-click draft.
|
|
52
|
+
|
|
53
|
+
The key concept: map MCP entities to standup questions — what they *did*,
|
|
54
|
+
what they *will do*, and what's *blocking* them.
|
|
55
|
+
|
|
56
|
+
| Source | Entity | Maps to |
|
|
57
|
+
|--------|--------|---------|
|
|
58
|
+
| GitHub/GitLab | Merged PRs, closed issues, review comments | "What did you do?" |
|
|
59
|
+
| GitHub/GitLab | Open/draft PRs (authored) | "What will you do?" |
|
|
60
|
+
| GitHub/GitLab | PRs with changes requested | "Blockers?" |
|
|
61
|
+
| Jira/Linear/Asana | Tickets moved to Done | "What did you do?" |
|
|
62
|
+
| Jira/Linear/Asana | In Progress tickets | "What will you do?" |
|
|
63
|
+
| Jira/Linear/Asana | Blocked/On Hold tickets | "Blockers?" |
|
|
64
|
+
| Calendar | Meetings attended since last report | "What did you do?" |
|
|
65
|
+
| Calendar | Upcoming meetings today | "What will you do?" |
|
|
66
|
+
| Slack | Threads participated in, announcements | Any question |
|
|
67
|
+
|
|
68
|
+
Filter calendar noise: skip the standup itself, recurring 1:1s, and
|
|
69
|
+
all-day events unless meaningful (offsites, deadlines).
|
|
70
|
+
|
|
71
|
+
**Deduplication:** Group related entities across tools — "Merged PR #342,
|
|
72
|
+
closes PROJ-89" is better than listing each separately.
|
|
73
|
+
|
|
74
|
+
**Identity matching:** MCP servers use different identifiers (Slack IDs,
|
|
75
|
+
GitHub usernames, emails). If they don't match, ask the user once and
|
|
76
|
+
reuse for the conversation.
|
|
77
|
+
|
|
78
|
+
**Enrichment flow:**
|
|
79
|
+
1. Silently check which MCP servers are connected
|
|
80
|
+
2. Pull recent activity from each (since last report date)
|
|
81
|
+
3. Deduplicate across tools, group by standup question
|
|
82
|
+
4. Present: "I pulled your recent activity — here's what I found"
|
|
83
|
+
|
|
84
|
+
If no MCP servers are connected, skip silently. Don't mention missing
|
|
85
|
+
access unprompted — explain only if the user asks why the draft isn't
|
|
86
|
+
auto-populated.
|
|
87
|
+
|
|
88
|
+
#### Source B: Previous Geekbot reports (always available)
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
geekbot report list --standup-id <id> --user-id <uid> --limit 5
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Where `<uid>` comes from `geekbot me show` → `data.id`. Extract: typical
|
|
95
|
+
answer length, writing style (bullets vs prose), recurring themes, and
|
|
96
|
+
any unresolved blockers for carry-over.
|
|
97
|
+
|
|
98
|
+
#### Source C: The user's direct input
|
|
99
|
+
|
|
100
|
+
**With MCP context:** Show what you found, ask "Anything to add, correct,
|
|
101
|
+
or remove?" — the user validates instead of recalling from scratch.
|
|
102
|
+
|
|
103
|
+
**Without MCP:** This is the primary source. Let the user dump context
|
|
104
|
+
freely ("What have you been working on?") — you structure it into answers.
|
|
105
|
+
|
|
106
|
+
#### Combining sources
|
|
107
|
+
|
|
108
|
+
Merge priority: user corrections > MCP specifics (PR numbers, ticket IDs) >
|
|
109
|
+
previous reports (style/tone) > user narrative input (the "why").
|
|
110
|
+
|
|
111
|
+
### Step 4: Draft answers
|
|
112
|
+
|
|
113
|
+
For each question in the standup, draft an answer that:
|
|
114
|
+
|
|
115
|
+
1. **Addresses the question directly** — don't go off-topic
|
|
116
|
+
2. **Matches the user's historical style** (see Tone Calibration below)
|
|
117
|
+
3. **Leads with specifics from MCP data** — PR numbers, ticket IDs,
|
|
118
|
+
meeting names add credibility and save the user from remembering details
|
|
119
|
+
4. **Incorporates their direct input** — the user's words provide narrative
|
|
120
|
+
and context that tools can't capture
|
|
121
|
+
5. **Includes blocker carry-over** — surface unresolved blockers from
|
|
122
|
+
previous reports (see Blocker Carry-Over below)
|
|
123
|
+
|
|
124
|
+
**Example with enrichment:**
|
|
125
|
+
```
|
|
126
|
+
Q: What have you done since yesterday?
|
|
127
|
+
A: Merged PR #342 (auth module refactor), closed PROJ-89 (payment webhook
|
|
128
|
+
bug). Had a sync with the API team about token rotation strategy.
|
|
129
|
+
|
|
130
|
+
Q: What will you do today?
|
|
131
|
+
A: Starting on the new billing API (PROJ-102). PR #345 is waiting for
|
|
132
|
+
review from @alice.
|
|
133
|
+
|
|
134
|
+
Q: Any blockers?
|
|
135
|
+
A: Still waiting on the staging deploy from DevOps (carrying over from
|
|
136
|
+
Tuesday). Also blocked on PROJ-98 — needs design sign-off.
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
vs. **without enrichment (user input only):**
|
|
140
|
+
```
|
|
141
|
+
Q: What have you done since yesterday?
|
|
142
|
+
A: Fixed the payment webhook bug and worked on the auth refactor.
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The enriched version is more useful for the team reading the report.
|
|
146
|
+
|
|
147
|
+
If the user didn't provide enough context for a specific question, ask
|
|
148
|
+
about that question specifically rather than inventing content.
|
|
149
|
+
|
|
150
|
+
### Step 5: Review and post
|
|
151
|
+
|
|
152
|
+
Present the complete draft clearly:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
Here's your draft for "Daily Standup":
|
|
156
|
+
|
|
157
|
+
Q: What did you work on yesterday?
|
|
158
|
+
A: Finished the auth module refactor and opened PR #342 for review.
|
|
159
|
+
|
|
160
|
+
Q: What are you working on today?
|
|
161
|
+
A: Starting integration tests for the new auth flow. Meeting with
|
|
162
|
+
the API team at 14:00 to discuss token rotation.
|
|
163
|
+
|
|
164
|
+
Q: Any blockers?
|
|
165
|
+
A: Still waiting on the staging environment deploy from DevOps
|
|
166
|
+
(carried over from Tuesday).
|
|
167
|
+
|
|
168
|
+
Ready to submit?
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
On explicit approval, build and execute:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
geekbot report create \
|
|
175
|
+
--standup-id 123 \
|
|
176
|
+
--answers '{"101":"Finished the auth module refactor and opened PR #342 for review.","102":"Starting integration tests for the new auth flow. Meeting with the API team at 14:00 to discuss token rotation.","103":"Still waiting on the staging environment deploy from DevOps (carried over from Tuesday)."}'
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Confirm success with the report ID and a brief summary.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Tone Calibration
|
|
184
|
+
|
|
185
|
+
Match the user's existing reporting style. This matters — people notice
|
|
186
|
+
when their standup answers suddenly sound different.
|
|
187
|
+
|
|
188
|
+
### What to analyse from historical reports
|
|
189
|
+
|
|
190
|
+
| Signal | How to detect | How to match |
|
|
191
|
+
|--------|--------------|--------------|
|
|
192
|
+
| **Length** | Average character count per answer | Keep within ±20% of their norm |
|
|
193
|
+
| **Structure** | Bullets vs prose vs numbered lists | Mirror the format |
|
|
194
|
+
| **Formality** | "Completed authentication module" vs "wrapped up auth stuff" | Match register |
|
|
195
|
+
| **Detail level** | PR numbers, ticket refs vs high-level summaries | Include same specificity |
|
|
196
|
+
| **Emoji / voice** | Present or absent; "I did X" vs "Did X" vs "Completed X" | Mirror exactly |
|
|
197
|
+
|
|
198
|
+
**If terse bullet points historically** → draft terse bullets, don't expand.
|
|
199
|
+
**If narrative with context** → draft with similar detail and flow.
|
|
200
|
+
**First-time reporter** → default to concise prose, 1–3 sentences per question.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Blocker Carry-Over Logic
|
|
205
|
+
|
|
206
|
+
One of the most valuable parts of AI-assisted reporting: automatically
|
|
207
|
+
surfacing unresolved blockers so they don't silently disappear.
|
|
208
|
+
|
|
209
|
+
### Detection
|
|
210
|
+
|
|
211
|
+
Scan last 3–5 reports for blocker-related questions (keywords: "block",
|
|
212
|
+
"stuck", "impediment", "waiting", "depend"). Classify answers as **no
|
|
213
|
+
blocker** ("None", "All clear", "N/A", short negatives) or **has blocker**
|
|
214
|
+
(anything else).
|
|
215
|
+
|
|
216
|
+
### Resolution detection
|
|
217
|
+
|
|
218
|
+
- Later report's blocker answer is "None" or similar → resolved
|
|
219
|
+
- Later report mentions same topic in progress answer → resolved
|
|
220
|
+
- Same blocker in most recent report → still active
|
|
221
|
+
|
|
222
|
+
### Carry-over presentation
|
|
223
|
+
|
|
224
|
+
Surface active blockers during step 4: *"In your last report (Tuesday),
|
|
225
|
+
you mentioned waiting on the staging deploy. Is that still blocking you?"*
|
|
226
|
+
|
|
227
|
+
- **Still blocked** → include, note it's a carry-over
|
|
228
|
+
- **Resolved** → omit (optionally mention resolution in progress answer)
|
|
229
|
+
- **Changed** → update the description
|
|
230
|
+
|
|
231
|
+
### Don't over-carry
|
|
232
|
+
|
|
233
|
+
Only carry over from last 3–5 reports. If a blocker persists 2+ weeks,
|
|
234
|
+
still surface but don't belabour it. If the user says "no blockers" after
|
|
235
|
+
you surface a carry-over, respect that.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Edge Cases
|
|
240
|
+
|
|
241
|
+
### User in multiple standups with overlapping questions
|
|
242
|
+
|
|
243
|
+
If multiple standups have similar questions (common with "blockers" across
|
|
244
|
+
a daily standup and a weekly sync), draft each report independently. A
|
|
245
|
+
blocker reported in one standup should be surfaced for the other if it's
|
|
246
|
+
the same blocker — but the framing may differ (daily: tactical, weekly:
|
|
247
|
+
strategic).
|
|
248
|
+
|
|
249
|
+
### Standup with many questions (>5)
|
|
250
|
+
|
|
251
|
+
Some standups (e.g., Sales Report, Well-being Check-in) have 5+ questions.
|
|
252
|
+
Don't try to gather context for all at once. Process in batches:
|
|
253
|
+
1. Draft the first 3 answers based on initial context
|
|
254
|
+
2. Ask about the remaining questions specifically
|
|
255
|
+
3. Complete the draft
|
|
256
|
+
|
|
257
|
+
### User provides minimal input
|
|
258
|
+
|
|
259
|
+
If the user says something like "nothing much today" or "same as yesterday":
|
|
260
|
+
- For progress questions: pull from the last report and frame as continuation
|
|
261
|
+
("Still working on the auth refactor from yesterday?")
|
|
262
|
+
- For blocker questions: check carry-over, default to "No blockers" if clear
|
|
263
|
+
- Don't pad — if they want a short report, draft a short report
|
|
264
|
+
|
|
265
|
+
### Report already submitted
|
|
266
|
+
|
|
267
|
+
If the user tries to submit a report and the CLI returns a conflict (exit
|
|
268
|
+
code 8), they may have already reported today. Inform them and ask if they
|
|
269
|
+
want to view their existing report instead.
|
|
270
|
+
|
|
271
|
+
### Standup is inactive or paused
|
|
272
|
+
|
|
273
|
+
If `geekbot standup get` shows the standup isn't currently active, let the
|
|
274
|
+
user know. They can still submit a report manually, but the standup won't
|
|
275
|
+
trigger automatically until reactivated.
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
{
|
|
2
|
+
"standup_templates": [
|
|
3
|
+
{
|
|
4
|
+
"id": "daily-standup",
|
|
5
|
+
"name": "Daily Standup",
|
|
6
|
+
"description": "Track daily updates and blockers on autopilot. The classic async standup for engineering and general teams.",
|
|
7
|
+
"categories": ["daily-syncs", "team-updates", "remote-collaboration"],
|
|
8
|
+
"roles": ["team-leads", "project-managers", "product-managers", "agile-team-members"],
|
|
9
|
+
"questions": [
|
|
10
|
+
{ "text": "What have you done since yesterday?" },
|
|
11
|
+
{ "text": "What will you do today?" },
|
|
12
|
+
{ "text": "Anything blocking your progress?" },
|
|
13
|
+
{ "text": "How do you feel today?" }
|
|
14
|
+
],
|
|
15
|
+
"defaults": {
|
|
16
|
+
"days": "Mon,Tue,Wed,Thu,Fri",
|
|
17
|
+
"time": "10:00"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "retrospective",
|
|
22
|
+
"name": "Retrospective",
|
|
23
|
+
"description": "Reflect on what went well and what could go better. Designed for sprint or project retrospectives.",
|
|
24
|
+
"categories": ["continuous-improvement", "remote-collaboration"],
|
|
25
|
+
"roles": ["agile-team-members", "product-managers", "team-leads"],
|
|
26
|
+
"questions": [
|
|
27
|
+
{ "text": "What went well?" },
|
|
28
|
+
{ "text": "What didn't go so well?" },
|
|
29
|
+
{ "text": "What have you learned?" },
|
|
30
|
+
{ "text": "What still puzzles you?" }
|
|
31
|
+
],
|
|
32
|
+
"defaults": {
|
|
33
|
+
"days": "Fri",
|
|
34
|
+
"time": "16:30",
|
|
35
|
+
"note": "Typically bi-weekly, aligned with sprint cadence"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "sales-report",
|
|
40
|
+
"name": "Sales Report",
|
|
41
|
+
"description": "Report sales progress and track team performance. End-of-day sales metrics and pipeline updates.",
|
|
42
|
+
"categories": ["daily-syncs", "team-updates"],
|
|
43
|
+
"roles": ["team-leads"],
|
|
44
|
+
"questions": [
|
|
45
|
+
{ "text": "How many meetings did you setup today?" },
|
|
46
|
+
{ "text": "How many client conversations did you have today?" },
|
|
47
|
+
{ "text": "How many new leads did you create today?" },
|
|
48
|
+
{ "text": "Anything you'd like to add?" },
|
|
49
|
+
{ "text": "Anything blocking your progress?" }
|
|
50
|
+
],
|
|
51
|
+
"defaults": {
|
|
52
|
+
"days": "Mon,Tue,Wed,Thu,Fri",
|
|
53
|
+
"time": "16:30"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "quick-check-in",
|
|
58
|
+
"name": "Quick Check-in",
|
|
59
|
+
"description": "Keep everyone in the loop with 3 quick questions. Lighter than a full standup, good for small teams.",
|
|
60
|
+
"categories": ["daily-syncs", "team-updates", "remote-collaboration"],
|
|
61
|
+
"roles": ["team-leads", "project-managers"],
|
|
62
|
+
"questions": [
|
|
63
|
+
{ "text": "What's your main focus today?" },
|
|
64
|
+
{ "text": "How are you feeling?" },
|
|
65
|
+
{ "text": "Anything blocking your progress?" }
|
|
66
|
+
],
|
|
67
|
+
"defaults": {
|
|
68
|
+
"days": "Mon,Tue,Wed,Thu,Fri",
|
|
69
|
+
"time": "09:00"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "well-being-check-in",
|
|
74
|
+
"name": "Well-being Check-in",
|
|
75
|
+
"description": "Check in on how your team is really doing. Covers productivity, stress, and satisfaction. Best run anonymously.",
|
|
76
|
+
"categories": ["team-well-being", "remote-collaboration"],
|
|
77
|
+
"roles": ["people-managers"],
|
|
78
|
+
"questions": [
|
|
79
|
+
{ "text": "Thinking about this past week: how productive do you feel you were?" },
|
|
80
|
+
{ "text": "Do you feel you've had more wins or roadblocks this past week?" },
|
|
81
|
+
{ "text": "How stressful did you find work in general this week?" },
|
|
82
|
+
{ "text": "How satisfied do you feel about the work you've completed this week?" },
|
|
83
|
+
{ "text": "Do you feel satisfied with your work-life balance?" },
|
|
84
|
+
{ "text": "Overall, how do you feel about your level of work-related stress?" },
|
|
85
|
+
{ "text": "Do you feel you can discuss concerns with your manager as often as needed?" }
|
|
86
|
+
],
|
|
87
|
+
"defaults": {
|
|
88
|
+
"days": "Fri",
|
|
89
|
+
"time": "16:00",
|
|
90
|
+
"note": "Typically bi-weekly, anonymous"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "confidential-check-in",
|
|
95
|
+
"name": "Confidential Check-in",
|
|
96
|
+
"description": "Run multiple 1-on-1s with your team all in one go. Replies stay private between manager and each participant.",
|
|
97
|
+
"categories": ["team-updates", "remote-collaboration"],
|
|
98
|
+
"roles": ["project-managers", "team-leads"],
|
|
99
|
+
"questions": [
|
|
100
|
+
{ "text": "Was anything a particular struggle?" },
|
|
101
|
+
{ "text": "If there was one thing I could do to help you, what would it be?" },
|
|
102
|
+
{ "text": "On a scale of 1-10, how happy are you with your work-life balance?" },
|
|
103
|
+
{ "text": "Growth-wise, where do you want to focus next month?" },
|
|
104
|
+
{ "text": "Anything you'd like to add?" }
|
|
105
|
+
],
|
|
106
|
+
"defaults": {
|
|
107
|
+
"days": "Fri",
|
|
108
|
+
"time": "15:00",
|
|
109
|
+
"note": "Weekly or bi-weekly, private channel between manager and participant"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"id": "meeting-notes",
|
|
114
|
+
"name": "Meeting Notes",
|
|
115
|
+
"description": "Sum up the talking points of every meeting. Structured capture of decisions and action items.",
|
|
116
|
+
"categories": ["team-updates"],
|
|
117
|
+
"roles": ["project-managers", "product-managers"],
|
|
118
|
+
"questions": [
|
|
119
|
+
{ "text": "What were the main talking points?" },
|
|
120
|
+
{ "text": "What decisions were made?" },
|
|
121
|
+
{ "text": "What are the action items and who owns them?" }
|
|
122
|
+
],
|
|
123
|
+
"defaults": {
|
|
124
|
+
"days": "Mon,Tue,Wed,Thu,Fri",
|
|
125
|
+
"time": "17:00",
|
|
126
|
+
"note": "Trigger on-demand after meetings rather than on a fixed schedule"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"id": "today-i-learned",
|
|
131
|
+
"name": "Today I Learned",
|
|
132
|
+
"description": "Inspire your team to learn something new. A lightweight knowledge-sharing ritual.",
|
|
133
|
+
"categories": ["continuous-improvement", "team-bonding"],
|
|
134
|
+
"roles": ["team-leads"],
|
|
135
|
+
"questions": [
|
|
136
|
+
{ "text": "What did you learn today?" },
|
|
137
|
+
{ "text": "How might this be useful for the team?" }
|
|
138
|
+
],
|
|
139
|
+
"defaults": {
|
|
140
|
+
"days": "Mon,Tue,Wed,Thu,Fri",
|
|
141
|
+
"time": "17:00"
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"id": "product-changelog",
|
|
146
|
+
"name": "Product Changelog",
|
|
147
|
+
"description": "Keep track of changes in product development. Structured release and change tracking.",
|
|
148
|
+
"categories": ["team-updates", "continuous-improvement"],
|
|
149
|
+
"roles": ["product-managers"],
|
|
150
|
+
"questions": [
|
|
151
|
+
{ "text": "What changed or shipped today?" },
|
|
152
|
+
{ "text": "What's the user impact?" },
|
|
153
|
+
{ "text": "Any follow-up items or known issues?" }
|
|
154
|
+
],
|
|
155
|
+
"defaults": {
|
|
156
|
+
"days": "Mon,Tue,Wed,Thu,Fri",
|
|
157
|
+
"time": "17:00"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "incident-log",
|
|
162
|
+
"name": "Incident Log",
|
|
163
|
+
"description": "Log incidents to share with the team. Structured incident reporting and post-mortem capture.",
|
|
164
|
+
"categories": ["team-updates"],
|
|
165
|
+
"roles": ["team-leads"],
|
|
166
|
+
"questions": [
|
|
167
|
+
{ "text": "What happened?" },
|
|
168
|
+
{ "text": "What was the impact?" },
|
|
169
|
+
{ "text": "What was the root cause?" },
|
|
170
|
+
{ "text": "What are the follow-up actions?" }
|
|
171
|
+
],
|
|
172
|
+
"defaults": {
|
|
173
|
+
"days": "Mon,Tue,Wed,Thu,Fri",
|
|
174
|
+
"time": "09:00",
|
|
175
|
+
"note": "Trigger on-demand when incidents occur"
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"id": "monday-watercooler",
|
|
180
|
+
"name": "Monday Water Cooler",
|
|
181
|
+
"description": "Spark Monday morning chitchat with a friendly question. Team bonding for remote teams.",
|
|
182
|
+
"categories": ["team-bonding"],
|
|
183
|
+
"roles": ["team-leads", "people-managers"],
|
|
184
|
+
"questions": [
|
|
185
|
+
{ "text": "What was the highlight of your weekend?" },
|
|
186
|
+
{ "text": "What are you looking forward to this week?" }
|
|
187
|
+
],
|
|
188
|
+
"defaults": {
|
|
189
|
+
"days": "Mon",
|
|
190
|
+
"time": "09:30"
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"id": "team-feedback",
|
|
195
|
+
"name": "Team Feedback",
|
|
196
|
+
"description": "Let your teammates know how they're doing. Structured peer feedback collection.",
|
|
197
|
+
"categories": ["continuous-improvement", "team-bonding"],
|
|
198
|
+
"roles": ["team-leads", "people-managers"],
|
|
199
|
+
"questions": [
|
|
200
|
+
{ "text": "Who on the team did great work recently and what did they do?" },
|
|
201
|
+
{ "text": "What's one thing we could do better as a team?" },
|
|
202
|
+
{ "text": "Any shout-outs or kudos?" }
|
|
203
|
+
],
|
|
204
|
+
"defaults": {
|
|
205
|
+
"days": "Fri",
|
|
206
|
+
"time": "16:00"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
],
|
|
210
|
+
"poll_templates": [
|
|
211
|
+
{
|
|
212
|
+
"id": "yes-no",
|
|
213
|
+
"name": "Yes / No",
|
|
214
|
+
"description": "Make fast decisions with simple Yes/No votes.",
|
|
215
|
+
"question": "Do you agree with the proposal?",
|
|
216
|
+
"choices": ["Yes", "No"],
|
|
217
|
+
"note": "Replace the question with your actual decision topic"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"id": "agree-disagree",
|
|
221
|
+
"name": "Agree / Disagree",
|
|
222
|
+
"description": "Quickly gauge sentiment on a topic with a Likert-style scale.",
|
|
223
|
+
"question": "How do you feel about this proposal?",
|
|
224
|
+
"choices": ["Strongly Agree", "Agree", "Neutral", "Disagree", "Strongly Disagree"],
|
|
225
|
+
"note": "Replace the question with your actual topic"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"id": "employee-nps",
|
|
229
|
+
"name": "Employee NPS Survey",
|
|
230
|
+
"description": "Evaluate and track employee commitment and loyalty with a standard NPS question.",
|
|
231
|
+
"question": "On a scale of 0-10, how likely are you to recommend this company as a place to work?",
|
|
232
|
+
"choices": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
|
|
233
|
+
"note": "NPS = % Promoters (9-10) minus % Detractors (0-6)"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"id": "team-feedback-weekly",
|
|
237
|
+
"name": "Team Feedback Weekly",
|
|
238
|
+
"description": "Gather weekly insights on team performance and morale.",
|
|
239
|
+
"question": "How would you rate this week's team performance?",
|
|
240
|
+
"choices": ["Excellent", "Good", "Average", "Below Average", "Poor"],
|
|
241
|
+
"note": "Good for tracking team health trends over time"
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Entry } from "@napi-rs/keyring";
|
|
2
|
+
|
|
3
|
+
const SERVICE = "geekbot-cli";
|
|
4
|
+
const ACCOUNT = "api-key";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Retrieve API key from OS keychain.
|
|
8
|
+
* Returns null if keychain is unavailable or no entry exists.
|
|
9
|
+
*/
|
|
10
|
+
export function getKeychainKey(): string | null {
|
|
11
|
+
try {
|
|
12
|
+
const entry = new Entry(SERVICE, ACCOUNT);
|
|
13
|
+
return entry.getPassword();
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Store API key in OS keychain.
|
|
21
|
+
* Throws if keychain is unavailable (caller should catch and suggest env var).
|
|
22
|
+
*/
|
|
23
|
+
export function setKeychainKey(apiKey: string): void {
|
|
24
|
+
if (!apiKey.trim()) {
|
|
25
|
+
throw new Error("API key must not be empty");
|
|
26
|
+
}
|
|
27
|
+
const entry = new Entry(SERVICE, ACCOUNT);
|
|
28
|
+
entry.setPassword(apiKey);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Delete API key from OS keychain.
|
|
33
|
+
* Throws if keychain is unavailable or no entry exists.
|
|
34
|
+
*/
|
|
35
|
+
export function deleteKeychainKey(): void {
|
|
36
|
+
const entry = new Entry(SERVICE, ACCOUNT);
|
|
37
|
+
entry.deletePassword();
|
|
38
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CliError } from "../errors/cli-error.ts";
|
|
2
|
+
import { ExitCode } from "../errors/exit-codes.ts";
|
|
3
|
+
import { getKeychainKey as _getKeychainKey } from "./keychain.ts";
|
|
4
|
+
|
|
5
|
+
export interface CredentialResult {
|
|
6
|
+
apiKey: string;
|
|
7
|
+
source: "flag" | "env" | "keychain";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function resolveCredential(
|
|
11
|
+
options: { apiKeyFlag?: string },
|
|
12
|
+
getKeychainKeyImpl?: typeof _getKeychainKey,
|
|
13
|
+
): Promise<CredentialResult> {
|
|
14
|
+
// Priority 1: --api-key flag
|
|
15
|
+
if (options.apiKeyFlag) {
|
|
16
|
+
return { apiKey: options.apiKeyFlag.trim(), source: "flag" };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Priority 2: GEEKBOT_API_KEY env var
|
|
20
|
+
const envKey = process.env.GEEKBOT_API_KEY;
|
|
21
|
+
if (envKey) {
|
|
22
|
+
return { apiKey: envKey.trim(), source: "env" };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Priority 3: OS keychain
|
|
26
|
+
const getKey = getKeychainKeyImpl ?? _getKeychainKey;
|
|
27
|
+
try {
|
|
28
|
+
const keychainKey = getKey();
|
|
29
|
+
if (keychainKey) {
|
|
30
|
+
return { apiKey: keychainKey.trim(), source: "keychain" };
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// Keychain unavailable (headless, CI) -- fall through to error
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// No credential found -- list all sources checked
|
|
37
|
+
throw new CliError(
|
|
38
|
+
"No API key found. Checked: --api-key flag, GEEKBOT_API_KEY environment variable, OS keychain.",
|
|
39
|
+
"auth_missing",
|
|
40
|
+
ExitCode.AUTH,
|
|
41
|
+
false,
|
|
42
|
+
"Set GEEKBOT_API_KEY environment variable or run: geekbot auth setup",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { handleError } from "../../errors/error-handler.ts";
|
|
3
|
+
import {
|
|
4
|
+
handleAuthRemove,
|
|
5
|
+
handleAuthSetup,
|
|
6
|
+
handleAuthStatus,
|
|
7
|
+
} from "../../handlers/auth-handlers.ts";
|
|
8
|
+
import { getGlobalOptions } from "../globals.ts";
|
|
9
|
+
|
|
10
|
+
export function createAuthCommand(): Command {
|
|
11
|
+
const auth = new Command("auth").description("Manage authentication");
|
|
12
|
+
|
|
13
|
+
auth
|
|
14
|
+
.command("setup")
|
|
15
|
+
.description("Interactively configure and store API key")
|
|
16
|
+
.addHelpText(
|
|
17
|
+
"after",
|
|
18
|
+
"\nExamples:\n geekbot auth setup\n geekbot --api-key YOUR_KEY auth setup",
|
|
19
|
+
)
|
|
20
|
+
.action(async function (this: Command) {
|
|
21
|
+
const globalOpts = getGlobalOptions(this);
|
|
22
|
+
try {
|
|
23
|
+
await handleAuthSetup({ apiKey: globalOpts.apiKey }, globalOpts);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
handleError(error, globalOpts.debug);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
auth
|
|
30
|
+
.command("status")
|
|
31
|
+
.description("Verify stored credentials work")
|
|
32
|
+
.addHelpText("after", "\nExamples:\n geekbot auth status")
|
|
33
|
+
.action(async function (this: Command) {
|
|
34
|
+
const globalOpts = getGlobalOptions(this);
|
|
35
|
+
try {
|
|
36
|
+
await handleAuthStatus(globalOpts);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
handleError(error, globalOpts.debug);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
auth
|
|
43
|
+
.command("remove")
|
|
44
|
+
.description("Remove stored API key from OS keychain")
|
|
45
|
+
.addHelpText("after", "\nExamples:\n geekbot auth remove")
|
|
46
|
+
.action(async function (this: Command) {
|
|
47
|
+
const globalOpts = getGlobalOptions(this);
|
|
48
|
+
try {
|
|
49
|
+
await handleAuthRemove(globalOpts);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
handleError(error, globalOpts.debug);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return auth;
|
|
56
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { handleError } from "../../errors/error-handler.ts";
|
|
3
|
+
import { handleMeShow, handleMeTeams } from "../../handlers/me-handlers.ts";
|
|
4
|
+
import { getGlobalOptions } from "../globals.ts";
|
|
5
|
+
|
|
6
|
+
export function createMeCommand(): Command {
|
|
7
|
+
const me = new Command("me").description("View your profile and teams");
|
|
8
|
+
|
|
9
|
+
me.command("show")
|
|
10
|
+
.description("Show your Geekbot profile")
|
|
11
|
+
.addHelpText("after", "\nExamples:\n geekbot me show")
|
|
12
|
+
.action(async function (this: Command) {
|
|
13
|
+
const globalOpts = getGlobalOptions(this);
|
|
14
|
+
try {
|
|
15
|
+
await handleMeShow(globalOpts);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
handleError(error, globalOpts.debug);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
me.command("teams")
|
|
22
|
+
.description("List teams you belong to")
|
|
23
|
+
.addHelpText("after", "\nExamples:\n geekbot me teams")
|
|
24
|
+
.action(async function (this: Command) {
|
|
25
|
+
const globalOpts = getGlobalOptions(this);
|
|
26
|
+
try {
|
|
27
|
+
await handleMeTeams(globalOpts);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
handleError(error, globalOpts.debug);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return me;
|
|
34
|
+
}
|