@docyt/panda-cli 0.1.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/QUICKSTART.md ADDED
@@ -0,0 +1,153 @@
1
+ # Quick Start Guide
2
+
3
+ ## For Package Maintainers (Publishing)
4
+
5
+ ### First-time Setup
6
+
7
+ 1. **Navigate to the CLI directory**:
8
+ ```bash
9
+ cd ~/Project/docyt/bb/reports-auto-test/docyt-panda-oncall-cli
10
+ ```
11
+
12
+ 2. **Install dependencies**:
13
+ ```bash
14
+ npm install
15
+ ```
16
+
17
+ 3. **Test locally** (optional):
18
+ ```bash
19
+ npm link
20
+ cd ~/Project/docyt/bb
21
+ docyt-oncall list
22
+ ```
23
+
24
+ 4. **Login to npm** (one-time):
25
+ ```bash
26
+ npm login
27
+ ```
28
+
29
+ 5. **Publish to npm**:
30
+ ```bash
31
+ npm publish --access public
32
+ ```
33
+
34
+ ### Publishing Updates
35
+
36
+ 1. **Update assets** (add/modify files in `assets/`):
37
+ ```bash
38
+ # Add new command
39
+ cp ~/.cursor/commands/new-command.md assets/commands/
40
+
41
+ # Or update existing
42
+ cp ~/.cursor/commands/docyt-panda-oncall-issues.md assets/commands/
43
+ ```
44
+
45
+ 2. **Bump version**:
46
+ ```bash
47
+ npm version patch # for bug fixes: 1.0.0 -> 1.0.1
48
+ npm version minor # for new features: 1.0.1 -> 1.1.0
49
+ npm version major # for breaking changes: 1.1.0 -> 2.0.0
50
+ ```
51
+
52
+ 3. **Publish**:
53
+ ```bash
54
+ npm publish
55
+ ```
56
+
57
+ ---
58
+
59
+ ## For End Users (Installing/Using)
60
+
61
+ ### Installation
62
+
63
+ From your project workspace (e.g., `~/Project/docyt/bb`):
64
+
65
+ ```bash
66
+ # Install all tools
67
+ npx @docyt/panda-oncall-cli install
68
+
69
+ # Or install specific types
70
+ npx @docyt/panda-oncall-cli install --commands
71
+ ```
72
+
73
+ ### Checking for Updates
74
+
75
+ ```bash
76
+ # See what's available
77
+ npx @docyt/panda-oncall-cli list
78
+
79
+ # Check differences
80
+ npx @docyt/panda-oncall-cli diff
81
+
82
+ # Preview upgrade
83
+ npx @docyt/panda-oncall-cli upgrade --preview
84
+
85
+ # Upgrade
86
+ npx @docyt/panda-oncall-cli upgrade
87
+ ```
88
+
89
+ ### Daily Usage
90
+
91
+ After installation, the Cursor commands are available in Cursor:
92
+ - `/docyt-panda-oncall-issues` - Scan Slack for on-call issues
93
+
94
+ ---
95
+
96
+ ## Troubleshooting
97
+
98
+ ### "Could not find workspace root"
99
+ Make sure you run the command from within a Git repository or Cursor workspace.
100
+
101
+ ### "Command not found: docyt-oncall"
102
+ Use `npx @docyt/panda-oncall-cli` instead, or run `npm link` if testing locally.
103
+
104
+ ### Permission Issues
105
+ The CLI needs write access to `.cursor/` directory in your workspace.
106
+
107
+ ---
108
+
109
+ ## Development Workflow
110
+
111
+ ### Adding a New Command
112
+
113
+ 1. **Create/update command in your workspace**:
114
+ ```bash
115
+ # Edit in Cursor or any editor
116
+ vim ~/.cursor/commands/my-new-command.md
117
+ ```
118
+
119
+ 2. **Copy to CLI assets**:
120
+ ```bash
121
+ cp ~/.cursor/commands/my-new-command.md ~/Project/docyt/bb/reports-auto-test/docyt-panda-oncall-cli/assets/commands/
122
+ ```
123
+
124
+ 3. **Bump version and publish**:
125
+ ```bash
126
+ cd ~/Project/docyt/bb/reports-auto-test/docyt-panda-oncall-cli
127
+ npm version minor
128
+ npm publish
129
+ ```
130
+
131
+ 4. **Users upgrade**:
132
+ ```bash
133
+ npx @docyt/panda-oncall-cli upgrade
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Testing Before Publishing
139
+
140
+ ```bash
141
+ # Link for local testing
142
+ cd ~/Project/docyt/bb/reports-auto-test/docyt-panda-oncall-cli
143
+ npm link
144
+
145
+ # Test in another project
146
+ cd ~/Project/docyt/bb
147
+ docyt-oncall install --commands
148
+ docyt-oncall list
149
+ docyt-oncall upgrade --preview
150
+
151
+ # Unlink when done
152
+ npm unlink -g @docyt/panda-oncall-cli
153
+ ```
package/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # Docyt Panda CLI
2
+
3
+ A CLI tool for installing and managing Docyt Team Panda development tools, including Cursor commands, skills, and rules.
4
+
5
+ ## Quick Start
6
+
7
+ ### Install Globally (Recommended)
8
+
9
+ ```bash
10
+ npm install -g @docyt/panda-cli
11
+ ```
12
+
13
+ Now you can run `panda-cli` from anywhere!
14
+
15
+ ### Usage
16
+
17
+ ```bash
18
+ # Go to your project
19
+ cd ~/Project/docyt/bb
20
+
21
+ # Install to current directory
22
+ panda-cli install
23
+ ```
24
+
25
+ **That's it!** The command will be installed to `.cursor/commands/` in your current directory.
26
+
27
+ ## Installation Location
28
+
29
+ Files are installed to your **current directory** (or specified path):
30
+ - **Commands**: `[current-dir]/.cursor/commands/`
31
+ - **Skills**: `[current-dir]/.cursor/skills/`
32
+ - **Rules**: `[current-dir]/.cursor/rules/`
33
+ - **Manifest**: `[current-dir]/.cursor/.panda-cli-manifest.json`
34
+
35
+ Example: If you run from `/Users/sheng/Project/docyt/bb`:
36
+ ```
37
+ /Users/sheng/Project/docyt/bb/.cursor/commands/docyt-panda-oncall-issues.md
38
+ ```
39
+
40
+ ## Commands
41
+
42
+ ### `install` - Install Tools
43
+
44
+ ```bash
45
+ # Install to current directory
46
+ panda-cli install
47
+
48
+ # Install to specific directory
49
+ panda-cli install /path/to/project
50
+
51
+ # Install only commands
52
+ panda-cli install --commands
53
+
54
+ # Install only skills
55
+ panda-cli install --skills
56
+
57
+ # Install only rules
58
+ panda-cli install --rules
59
+
60
+ # Install everything (default)
61
+ panda-cli install --all
62
+ ```
63
+
64
+ ### `list` - List Tools
65
+
66
+ List all installed and available tools.
67
+
68
+ ```bash
69
+ panda-cli list
70
+ ```
71
+
72
+ ### `upgrade` - Upgrade Tools
73
+
74
+ Upgrade installed tools to the latest version.
75
+
76
+ ```bash
77
+ # Preview changes without installing
78
+ panda-cli upgrade --preview
79
+
80
+ # Upgrade (will prompt for local changes)
81
+ panda-cli upgrade
82
+
83
+ # Force upgrade (overwrite all local changes)
84
+ panda-cli upgrade --force
85
+ ```
86
+
87
+ ### `diff` - Show Differences
88
+
89
+ Show differences between your local version and the latest available.
90
+
91
+ ```bash
92
+ panda-cli diff
93
+ ```
94
+
95
+ ## What Gets Installed
96
+
97
+ ### Cursor Commands
98
+
99
+ Currently includes:
100
+ - `docyt-panda-oncall-issues.md` - Scan Slack channels for Team Panda on-call issues
101
+
102
+ More commands will be added over time.
103
+
104
+ ### Cursor Skills
105
+
106
+ Coming soon...
107
+
108
+ ### Cursor Rules
109
+
110
+ Coming soon...
111
+
112
+ ## How It Works
113
+
114
+ 1. **Simple Detection**: Uses current directory (or specified path)
115
+ 2. **Installation**: Copies files from the CLI package to your workspace
116
+ 3. **Tracking**: Creates a manifest file (`.cursor/.panda-cli-manifest.json`) to track versions
117
+ 4. **Smart Upgrades**: Detects local changes and prompts before overwriting
118
+
119
+ ## Manifest File
120
+
121
+ The CLI creates a manifest file at `.cursor/.panda-cli-manifest.json` to track:
122
+ - Installed version
123
+ - Installation/upgrade timestamps
124
+ - List of installed assets
125
+ - Local customizations
126
+
127
+ Example manifest:
128
+ ```json
129
+ {
130
+ "version": "0.1.1",
131
+ "installed": "2026-02-01T08:00:00Z",
132
+ "assets": {
133
+ "commands": ["docyt-panda-oncall-issues.md"],
134
+ "skills": [],
135
+ "rules": []
136
+ },
137
+ "customizations": {}
138
+ }
139
+ ```
140
+
141
+ ## Alternative: Use with npx (No Install)
142
+
143
+ You can also use the CLI without installing globally:
144
+
145
+ ```bash
146
+ # Install to current directory
147
+ npx @docyt/panda-cli install
148
+
149
+ # List tools
150
+ npx @docyt/panda-cli list
151
+
152
+ # Upgrade
153
+ npx @docyt/panda-cli upgrade
154
+ ```
155
+
156
+ **Note**: `npx` runs slower than global install because it downloads each time.
157
+
158
+ ## Development
159
+
160
+ ### Local Development
161
+
162
+ ```bash
163
+ # Install dependencies
164
+ cd docyt-panda-oncall-cli
165
+ npm install
166
+
167
+ # Make CLI available globally for testing
168
+ npm link
169
+
170
+ # Now you can run it from anywhere
171
+ panda-cli list
172
+ ```
173
+
174
+ ### Publishing Updates
175
+
176
+ ```bash
177
+ # Bump version
178
+ npm version patch # or minor, major
179
+
180
+ # Publish to npm
181
+ npm publish --access public
182
+ ```
183
+
184
+ ### Adding New Assets
185
+
186
+ 1. Add files to `assets/commands/`, `assets/skills/`, or `assets/rules/`
187
+ 2. Bump the version in `package.json`
188
+ 3. Publish to npm
189
+ 4. Users run `panda-cli upgrade` to get updates
190
+
191
+ ## Requirements
192
+
193
+ - Node.js >= 14.0.0
194
+
195
+ ## License
196
+
197
+ ISC
198
+
199
+ ## Support
200
+
201
+ For issues or questions, contact Team Panda.
@@ -0,0 +1,249 @@
1
+ ---
2
+ name: /docyt-panda-oncall-issues
3
+ id: docyt-panda-oncall-issues
4
+ category: On-call
5
+ description: Retrieve Slack threads from Team Panda and customer-issue channels and highlight unresolved issues
6
+ ---
7
+
8
+ Retrieve threads from Team Panda and customer-issue Slack channels, fetch thread replies, and determine whether each thread has been processed (conclusion/fix/solution reached). Highlight threads with no resolution so on-call can respond quickly. Post the report to **#panda-new-work** with a message starting with "My human asked to generate this report". Designed to be extended later with triage, Jira creation, and assignment.
9
+
10
+ **Input**: Optional time range (e.g. "last 3 days", "last 7 days"). Default: last 3 days. Optionally specify channel(s) to limit scope.
11
+
12
+ ---
13
+
14
+ ## Reference: Channels and Team Panda Scope
15
+
16
+ **Team Panda channels** (scrum team, panda-specific issues):
17
+
18
+ | Channel name (Slack) | Purpose |
19
+ |----------------------|--------|
20
+ | #team-panda-leadership | Leadership / decisions |
21
+ | #team-panda | General team panda |
22
+
23
+ **Customer-issue channels** (figure out Team Panda scope, respond quickly):
24
+
25
+ | Channel name (Slack) | Purpose |
26
+ |---------------------|--------|
27
+ | #product-questions | Product questions from customers/support |
28
+ | #product_feedback | Product feedback |
29
+ | #production-errors | Production errors / incidents |
30
+
31
+ **Team Panda is responsible for** features implemented in these services:
32
+
33
+ - dashboards-service
34
+ - reports-service
35
+ - report-dc-service
36
+ - report-btf-service
37
+ - report-helper-service
38
+
39
+ When reviewing customer-issue channels, treat a thread as **in Team Panda scope** if it clearly relates to: dashboards, reports, report builder, report data, report export, or any of the above services. If unclear, still include the thread in the report but mark scope as "Unclear"; on-call can triage.
40
+
41
+ ## Steps
42
+
43
+ 1. **Ensure Slack MCP is available**
44
+
45
+ - Confirm the Slack MCP server is enabled and tools are listed (e.g. `channels_list`, `conversations_history`, `conversations_replies`).
46
+ - If Slack MCP is not available, report: "Slack MCP is not available. Enable the Slack MCP server in Cursor Settings → MCP and ensure tokens are configured." Then stop.
47
+
48
+ 2. **Resolve channel identifiers**
49
+
50
+ - Channels to query (use these names with `#` for Slack API):
51
+ - Team Panda: `#team-panda-leadership`, `#team-panda`
52
+ - Customer-issue: `#product-questions`, `#product_feedback`, `#production-errors`
53
+ - If needed, use `channels_list` with `channel_types: "public_channel,private_channel"` to map names to channel IDs (e.g. `Cxxxxxxxxxx`). The Slack MCP accepts channel name (e.g. `#product-questions`) or ID.
54
+
55
+ 3. **Fetch recent messages (and thread parents) from each channel**
56
+
57
+ - Parse input for time range; default to **3 days** (e.g. `limit: "3d"`). If user said "last 7 days", use `limit: "7d"`.
58
+ - If the user specified channel(s) (e.g. "only #product-questions"), query only those channels; otherwise query all 5 channels.
59
+ - For each channel to query, call **conversations_history**:
60
+ - `channel_id`: channel name with `#` (e.g. `#product-questions`) or resolved ID
61
+ - `limit`: chosen time range (e.g. `"3d"`)
62
+ - Collect all messages. Note which messages have a **thread_ts** (they are replies) and which are **thread parents** (same message has `ts`; replies will reference this `ts` as `thread_ts`). Build a list of **thread roots**: unique `(channel_id, thread_ts)` where `thread_ts` is the parent message `ts` of a thread (messages that have at least one reply, or that you will fetch replies for).
63
+
64
+ 4. **Fetch full thread for each thread root**
65
+
66
+ - For each thread root `(channel_id, thread_ts)`:
67
+ - Call **conversations_replies** with `channel_id`, `thread_ts`, and same `limit` (e.g. `"3d"`) to get all replies in the thread.
68
+ - If a channel has no threads (only top-level messages), treat each top-level message as a single-message "thread" for consistency (no need to call replies).
69
+
70
+ 5. **Classify each thread: processed vs not processed**
71
+
72
+ For each thread (parent + replies), decide:
73
+
74
+ **Processed (resolution reached)** if any reply or the parent clearly indicates one or more of:
75
+
76
+ - A **fix** (e.g. "fixed", "deployed", "patch applied")
77
+ - A **solution** or **workaround** (e.g. "workaround: ...", "you can do X")
78
+ - A **conclusion** (e.g. "resolved", "closed", "not a bug", "by design")
79
+ - **Ownership/tracking** (e.g. "created Jira X", "assigned to Y", "tracking in Z")
80
+ - **Clear next step with timeline** (e.g. "we'll ship this next week", "engineering is investigating")
81
+
82
+ **Not processed (no resolution)** if:
83
+
84
+ - No replies, or
85
+ - Replies only ask for more info, acknowledge, or discuss without any fix/solution/conclusion/ticket/timeline
86
+
87
+ **Inconclusive** if:
88
+
89
+ - Thread is long or ambiguous and you cannot tell; mark as "Needs review".
90
+
91
+ When in doubt, mark as **not processed** so on-call can triage.
92
+
93
+ 6. **Identify Team Panda scope for customer-issue threads**
94
+
95
+ For threads in **#product-questions**, **#product_feedback**, **#production-errors**:
96
+
97
+ - If the topic clearly involves **dashboards, reports, report builder, report data, report export**, or any of **dashboards-service, reports-service, report-dc-service, report-btf-service, report-helper-service**, mark scope: **Team Panda**.
98
+ - If clearly unrelated (e.g. auth, billing, another team’s service), mark scope: **Other** (still list in report but note).
99
+ - If unclear, mark scope: **Unclear**.
100
+
101
+ Threads in Team Panda channels (#team-panda-leadership, #team-panda) are **Team Panda** by default.
102
+
103
+ 7. **Build and present the report**
104
+
105
+ Use the **Output format** below. Always include:
106
+
107
+ - **Unresolved / not processed** threads first (highlighted).
108
+ - **CRITICAL**: Sort so Team Panda scope issues appear FIRST within each section
109
+ - Then **Needs review** (inconclusive).
110
+ - Also sort Team Panda scope first
111
+ - Then **Processed** (resolved) for reference.
112
+ - For each thread: channel, short summary, scope (Team Panda / Other / Unclear).
113
+ - For **Unresolved/Needs Review**: Add a short "State" field (2-5 words) describing current status: e.g., "No replies yet", "Awaiting confirmation", "Not reproduced", "Checking with team", "FYI only—no action"
114
+ - For **Processed**: Add "Resolution" field describing how it was resolved: e.g., "Ticket created", "Workaround provided", "Fixed in PR", "User found solution", "Assigned to engineer"
115
+ - **Do NOT include thread_ts** in the output (not useful to humans reading the report)
116
+
117
+ 8. **Optional: suggest next actions**
118
+
119
+ For each **Unresolved** thread in Team Panda scope (or Unclear), suggest one of: "Triage", "Reply with ETA", "Create Jira", "Escalate". Do not execute these yet; the command only reports and highlights.
120
+
121
+ 9. **Post the report to #panda-new-work**
122
+
123
+ - Call **conversations_add_message** with:
124
+ - `channel_id`: `#panda-new-work`
125
+ - `payload`: A message that **starts with** "My human asked to generate report" (or "My human asked me to generate report"), then the full report in the Output format below (same content as presented in step 7).
126
+ - `content_type`: `text/plain` (use plain text; Slack will auto-format asterisks, underscores, emojis).
127
+ - **IMPORTANT**: Unicode box-drawing characters like `━` and `└` work in Slack. Emojis (🚨, ⚠️, ✅) work. Use these for visual formatting.
128
+ - If the payload exceeds Slack's message length limit (~40,000 characters), post a shortened version: the opening line, then the **Unresolved** section and suggested next steps, plus a one-line summary of Processed count (e.g. "Processed: N threads."). Optionally post the full Processed/Needs review sections in a follow-up reply in the same thread (using `thread_ts` of the first message if the tool supports replying in thread).
129
+ - After posting, confirm in the command output: "Report posted to #panda-new-work."
130
+
131
+ ---
132
+
133
+ ## Output format
134
+
135
+ Format the Slack message for optimal readability:
136
+
137
+ ```
138
+ My human asked to generate report.
139
+
140
+ *Panda On-call Issues — [Time range]*
141
+
142
+ *Channels scanned:* [comma-separated list with #]
143
+ *Unresolved (needs attention):* [count]
144
+
145
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
146
+
147
+ 🚨 *UNRESOLVED (No conclusion/fix/solution)*
148
+
149
+ [IMPORTANT: List Team Panda scope threads FIRST, then Other/Unclear]
150
+
151
+ [For Team Panda scope threads, use this format with 🔴 marker:]
152
+ 🔴 *[Channel]* | [Brief summary]
153
+ └ _Scope:_ *Team Panda* ⚠️
154
+ └ _State:_ [2-5 word current status]
155
+ └ _Suggested:_ [Triage / Reply with ETA / Create Jira]
156
+
157
+ [For Other/Unclear scope threads, use this format:]
158
+ • *[Channel]* | [Brief summary]
159
+ └ _Scope:_ [Other / Unclear]
160
+ └ _State:_ [2-5 word current status]
161
+ └ _Suggested:_ [Triage / Reply with ETA / Create Jira]
162
+
163
+ [Blank line between each thread]
164
+
165
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
166
+
167
+ ⚠️ *NEEDS REVIEW (Inconclusive)*
168
+
169
+ [IMPORTANT: List Team Panda scope threads FIRST, then Other/Unclear]
170
+
171
+ [For Team Panda scope threads with 🔴 marker:]
172
+ 🔴 *[Channel]* | [Brief summary]
173
+ └ _Scope:_ *Team Panda* ⚠️
174
+ └ _State:_ [2-5 word current status]
175
+
176
+ [For Other/Unclear scope threads:]
177
+ • *[Channel]* | [Brief summary]
178
+ └ _Scope:_ [Other / Unclear]
179
+ └ _State:_ [2-5 word current status]
180
+
181
+ [Blank line between each thread]
182
+
183
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
184
+
185
+ ✅ *PROCESSED (Resolution reached)*
186
+
187
+ [For each processed thread, keep brief:]
188
+ • *[Channel]* | [Brief summary]
189
+ └ _Resolution:_ [How resolved in 3-8 words]
190
+
191
+ [Blank line between threads if many, otherwise can be compact]
192
+
193
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
194
+
195
+ *Summary:* [X unresolved (Y Team Panda), Z needs review, W processed across N channels]
196
+ ```
197
+
198
+ **Formatting guidelines:**
199
+ - **CRITICAL SORTING**: Team Panda scope issues appear FIRST in Unresolved and Needs Review sections
200
+ - **Team Panda highlighting**: Use `🔴` red circle emoji at start of line + bold "Team Panda" text with ⚠️ emoji
201
+ - **State field** (Unresolved/Needs Review): 2-5 word summary of current status:
202
+ - "No replies yet" (no one has responded)
203
+ - "Awaiting confirmation" (waiting for someone to confirm/check)
204
+ - "Not reproduced" (issue cannot be reproduced)
205
+ - "Checking with team" (someone said "let me check")
206
+ - "FYI only—no action" (informational, no one taking action)
207
+ - "Needs triage" (unclear who should handle)
208
+ - **Resolution field** (Processed): How the issue was resolved in 3-8 words:
209
+ - "Ticket ENG-XXXXX created" or "Ticket ENG-XXXXX assigned to [name]"
210
+ - "Workaround provided" (temporary solution given)
211
+ - "Fixed in PR #XXXX" or "PR merged"
212
+ - "User found solution" (user resolved themselves)
213
+ - "Assigned to [name]" (ownership established)
214
+ - "Discussion concluded" (decision made)
215
+ - "Clarified approach" (scope/plan agreed)
216
+ - **DO NOT include thread_ts timestamps** (not useful to humans)
217
+ - Use `*bold*` for emphasis on channel names and section headers
218
+ - Use `_italic_` for labels like "Scope:", "State:", "Resolution:", "Suggested:"
219
+ - Use emojis (🚨, ⚠️, ✅, 🔴) to make sections visually distinct
220
+ - Use `━` separator lines between major sections
221
+ - Use bullet points `•` for regular threads; use `🔴` for Team Panda scope threads
222
+ - Use `└` (box drawing character) for sub-details with 3-space indentation
223
+ - Keep summaries to one line (max ~80 chars)
224
+ - Add blank lines between threads for readability
225
+ - If Unresolved or Needs Review sections are empty, show: "None — all clear! 🎉"
226
+ - In Summary line, include count of Team Panda issues in parentheses: "8 unresolved (2 Team Panda)"
227
+
228
+ ---
229
+
230
+ ## Guardrails
231
+
232
+ - Use only **Slack MCP** to read channels and threads; do not guess or invent message content.
233
+ - If a channel is private and Slack MCP returns permission errors, note "Channel X not accessible" and continue with other channels.
234
+ - Default time range is **3 days** unless the user specifies otherwise.
235
+ - When classifying, prefer **not processed** when uncertain so issues are not missed.
236
+ - **Posting:** This command posts the on-call report to **#panda-new-work** only (step 9). Do not post to other channels, create Jira tickets, or modify other external systems. (Future versions may add triage/Jira steps.)
237
+
238
+ ---
239
+
240
+ ## Extensibility (future)
241
+
242
+ This command is designed so that later you can:
243
+
244
+ - **Triage**: For each unresolved thread, add a triage label (e.g. bug, feature, question, incident) and owner.
245
+ - **Identify**: Map thread to code/service (e.g. report-btf-service) and suggest owners.
246
+ - **Create Jira ticket**: For unresolved Team Panda scope threads, draft or create a Jira ticket and post the link in the thread (when such automation is enabled).
247
+ - **Assign**: Assign to on-call or a specific engineer.
248
+
249
+ For now, the command only **retrieves threads, checks for resolution, and highlights unresolved**; next steps are suggested in the report but not executed.
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const chalk = require('chalk');
5
+ const { install } = require('../lib/installer');
6
+ const { upgrade } = require('../lib/upgrader');
7
+ const { list } = require('../lib/list');
8
+ const { diff } = require('../lib/diff');
9
+ const packageJson = require('../package.json');
10
+
11
+ program
12
+ .name('panda-cli')
13
+ .description('Docyt Team Panda development tools CLI')
14
+ .version(packageJson.version);
15
+
16
+ program
17
+ .command('install')
18
+ .description('Install Docyt dev tools to workspace')
19
+ .argument('[path]', 'Target directory (defaults to current directory)')
20
+ .option('--commands', 'Install only Cursor commands')
21
+ .option('--skills', 'Install only skills')
22
+ .option('--rules', 'Install only rules')
23
+ .option('--all', 'Install everything (default)', true)
24
+ .action(async (targetPath, options) => {
25
+ try {
26
+ console.log(chalk.blue('🚀 Installing Docyt Panda On-Call Tools...\n'));
27
+ await install(targetPath, options);
28
+ console.log(chalk.green('\n✓ Installation complete!'));
29
+ } catch (error) {
30
+ console.error(chalk.red('\n✗ Installation failed:'), error.message);
31
+ process.exit(1);
32
+ }
33
+ });
34
+
35
+ program
36
+ .command('upgrade')
37
+ .description('Upgrade to latest version')
38
+ .option('--preview', 'Show what would change')
39
+ .option('--force', 'Overwrite local changes')
40
+ .action(async (options) => {
41
+ try {
42
+ console.log(chalk.blue('🔄 Checking for updates...\n'));
43
+ await upgrade(options);
44
+ if (!options.preview) {
45
+ console.log(chalk.green('\n✓ Upgrade complete!'));
46
+ }
47
+ } catch (error) {
48
+ console.error(chalk.red('\n✗ Upgrade failed:'), error.message);
49
+ process.exit(1);
50
+ }
51
+ });
52
+
53
+ program
54
+ .command('list')
55
+ .description('List installed and available tools')
56
+ .action(async () => {
57
+ try {
58
+ await list();
59
+ } catch (error) {
60
+ console.error(chalk.red('\n✗ Failed to list tools:'), error.message);
61
+ process.exit(1);
62
+ }
63
+ });
64
+
65
+ program
66
+ .command('diff')
67
+ .description('Show differences between local and latest')
68
+ .action(async () => {
69
+ try {
70
+ await diff();
71
+ } catch (error) {
72
+ console.error(chalk.red('\n✗ Failed to show diff:'), error.message);
73
+ process.exit(1);
74
+ }
75
+ });
76
+
77
+ program.parse(process.argv);
package/lib/diff.js ADDED
@@ -0,0 +1,61 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const {
5
+ findWorkspaceRoot,
6
+ loadManifest,
7
+ getAssetsDir,
8
+ getAvailableCommands
9
+ } = require('./utils');
10
+
11
+ /**
12
+ * Show diff between local and latest versions
13
+ */
14
+ async function diff() {
15
+ try {
16
+ const workspace = findWorkspaceRoot();
17
+ const manifest = loadManifest(workspace);
18
+
19
+ if (!manifest) {
20
+ console.log(chalk.yellow('No installation found. Run `docyt-oncall install` first.'));
21
+ return;
22
+ }
23
+
24
+ console.log(chalk.bold('\n📊 Differences between local and latest\n'));
25
+
26
+ const commands = getAvailableCommands();
27
+ const commandsDir = path.join(workspace, '.cursor', 'commands');
28
+
29
+ let hasDifferences = false;
30
+
31
+ for (const command of commands) {
32
+ const sourcePath = path.join(getAssetsDir(), 'commands', command);
33
+ const targetPath = path.join(commandsDir, command);
34
+
35
+ if (!fs.existsSync(targetPath)) {
36
+ console.log(chalk.blue(`+ ${command} (new)`));
37
+ hasDifferences = true;
38
+ continue;
39
+ }
40
+
41
+ const sourceContent = fs.readFileSync(sourcePath, 'utf-8');
42
+ const targetContent = fs.readFileSync(targetPath, 'utf-8');
43
+
44
+ if (sourceContent !== targetContent) {
45
+ console.log(chalk.yellow(`± ${command} (modified)`));
46
+ hasDifferences = true;
47
+ }
48
+ }
49
+
50
+ if (!hasDifferences) {
51
+ console.log(chalk.green('✓ No differences found - you are up to date!'));
52
+ }
53
+
54
+ console.log('');
55
+
56
+ } catch (error) {
57
+ throw error;
58
+ }
59
+ }
60
+
61
+ module.exports = { diff };
@@ -0,0 +1,138 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const {
5
+ findWorkspaceRoot,
6
+ loadManifest,
7
+ saveManifest,
8
+ getAssetsDir,
9
+ getAvailableCommands,
10
+ getAvailableSkills,
11
+ getAvailableRules
12
+ } = require('./utils');
13
+
14
+ /**
15
+ * Install commands to workspace
16
+ */
17
+ async function installCommands(workspace) {
18
+ const commands = getAvailableCommands();
19
+ const targetDir = path.join(workspace, '.cursor', 'commands');
20
+
21
+ fs.ensureDirSync(targetDir);
22
+
23
+ console.log(chalk.blue('Installing Cursor commands...'));
24
+
25
+ for (const command of commands) {
26
+ const sourcePath = path.join(getAssetsDir(), 'commands', command);
27
+ const targetPath = path.join(targetDir, command);
28
+
29
+ await fs.copy(sourcePath, targetPath, { overwrite: true });
30
+ console.log(chalk.green(` ✓ Installed: ${command}`));
31
+ }
32
+
33
+ return commands;
34
+ }
35
+
36
+ /**
37
+ * Install skills to workspace
38
+ */
39
+ async function installSkills(workspace) {
40
+ const skills = getAvailableSkills();
41
+
42
+ if (skills.length === 0) {
43
+ console.log(chalk.gray(' No skills available'));
44
+ return [];
45
+ }
46
+
47
+ const targetDir = path.join(workspace, '.cursor', 'skills');
48
+
49
+ fs.ensureDirSync(targetDir);
50
+
51
+ console.log(chalk.blue('Installing Cursor skills...'));
52
+
53
+ for (const skill of skills) {
54
+ const sourcePath = path.join(getAssetsDir(), 'skills', skill);
55
+ const targetPath = path.join(targetDir, skill);
56
+
57
+ await fs.copy(sourcePath, targetPath, { overwrite: true });
58
+ console.log(chalk.green(` ✓ Installed: ${skill}`));
59
+ }
60
+
61
+ return skills;
62
+ }
63
+
64
+ /**
65
+ * Install rules to workspace
66
+ */
67
+ async function installRules(workspace) {
68
+ const rules = getAvailableRules();
69
+
70
+ if (rules.length === 0) {
71
+ console.log(chalk.gray(' No rules available'));
72
+ return [];
73
+ }
74
+
75
+ const targetDir = path.join(workspace, '.cursor', 'rules');
76
+
77
+ fs.ensureDirSync(targetDir);
78
+
79
+ console.log(chalk.blue('Installing Cursor rules...'));
80
+
81
+ for (const rule of rules) {
82
+ const sourcePath = path.join(getAssetsDir(), 'rules', rule);
83
+ const targetPath = path.join(targetDir, rule);
84
+
85
+ await fs.copy(sourcePath, targetPath, { overwrite: true });
86
+ console.log(chalk.green(` ✓ Installed: ${rule}`));
87
+ }
88
+
89
+ return rules;
90
+ }
91
+
92
+ /**
93
+ * Main install function
94
+ */
95
+ async function install(targetPath, options) {
96
+ try {
97
+ // Find workspace (use targetPath if provided, otherwise current directory)
98
+ const workspace = findWorkspaceRoot(targetPath);
99
+ console.log(chalk.gray(`Installing to: ${workspace}\n`));
100
+
101
+ const installedAssets = {
102
+ commands: [],
103
+ skills: [],
104
+ rules: []
105
+ };
106
+
107
+ // Install based on options
108
+ if (options.all || options.commands) {
109
+ installedAssets.commands = await installCommands(workspace);
110
+ }
111
+
112
+ if (options.all || options.skills) {
113
+ installedAssets.skills = await installSkills(workspace);
114
+ }
115
+
116
+ if (options.all || options.rules) {
117
+ installedAssets.rules = await installRules(workspace);
118
+ }
119
+
120
+ // Save manifest
121
+ const packageJson = require('../package.json');
122
+ const manifest = {
123
+ version: packageJson.version,
124
+ installed: new Date().toISOString(),
125
+ assets: installedAssets,
126
+ customizations: {}
127
+ };
128
+
129
+ saveManifest(workspace, manifest);
130
+
131
+ console.log(chalk.gray(`\nManifest saved to ${path.join(workspace, '.cursor/.panda-cli-manifest.json')}`));
132
+
133
+ } catch (error) {
134
+ throw error;
135
+ }
136
+ }
137
+
138
+ module.exports = { install };
package/lib/list.js ADDED
@@ -0,0 +1,88 @@
1
+ const chalk = require('chalk');
2
+ const {
3
+ findWorkspaceRoot,
4
+ loadManifest,
5
+ getAvailableCommands,
6
+ getAvailableSkills,
7
+ getAvailableRules
8
+ } = require('./utils');
9
+
10
+ /**
11
+ * List installed and available tools
12
+ */
13
+ async function list() {
14
+ try {
15
+ const workspace = findWorkspaceRoot();
16
+ const manifest = loadManifest(workspace);
17
+ const packageJson = require('../package.json');
18
+
19
+ console.log(chalk.bold('\n📦 Docyt Panda On-Call Tools\n'));
20
+
21
+ if (manifest) {
22
+ console.log(chalk.green('✓ Installed'));
23
+ console.log(chalk.gray(` Version: ${manifest.version}`));
24
+ console.log(chalk.gray(` Installed: ${new Date(manifest.installed).toLocaleString()}`));
25
+
26
+ if (manifest.upgraded) {
27
+ console.log(chalk.gray(` Last upgraded: ${new Date(manifest.upgraded).toLocaleString()}`));
28
+ }
29
+ } else {
30
+ console.log(chalk.yellow('○ Not installed'));
31
+ console.log(chalk.gray(' Run `docyt-oncall install` to get started'));
32
+ }
33
+
34
+ console.log(chalk.gray(` Latest available: ${packageJson.version}\n`));
35
+
36
+ // List commands
37
+ const availableCommands = getAvailableCommands();
38
+ console.log(chalk.bold('Cursor Commands:'));
39
+ console.log(chalk.gray(' (Installed to: .cursor/commands/)'));
40
+
41
+ if (availableCommands.length === 0) {
42
+ console.log(chalk.gray(' None available'));
43
+ } else {
44
+ availableCommands.forEach(cmd => {
45
+ const isInstalled = manifest?.assets?.commands?.includes(cmd);
46
+ const icon = isInstalled ? chalk.green('✓') : chalk.gray('○');
47
+ console.log(` ${icon} ${cmd}`);
48
+ });
49
+ }
50
+
51
+ // List skills
52
+ const availableSkills = getAvailableSkills();
53
+ console.log(chalk.bold('\nCursor Skills:'));
54
+ console.log(chalk.gray(' (Installed to: .cursor/skills/)'));
55
+
56
+ if (availableSkills.length === 0) {
57
+ console.log(chalk.gray(' None available'));
58
+ } else {
59
+ availableSkills.forEach(skill => {
60
+ const isInstalled = manifest?.assets?.skills?.includes(skill);
61
+ const icon = isInstalled ? chalk.green('✓') : chalk.gray('○');
62
+ console.log(` ${icon} ${skill}`);
63
+ });
64
+ }
65
+
66
+ // List rules
67
+ const availableRules = getAvailableRules();
68
+ console.log(chalk.bold('\nCursor Rules:'));
69
+ console.log(chalk.gray(' (Installed to: .cursor/rules/)'));
70
+
71
+ if (availableRules.length === 0) {
72
+ console.log(chalk.gray(' None available'));
73
+ } else {
74
+ availableRules.forEach(rule => {
75
+ const isInstalled = manifest?.assets?.rules?.includes(rule);
76
+ const icon = isInstalled ? chalk.green('✓') : chalk.gray('○');
77
+ console.log(` ${icon} ${rule}`);
78
+ });
79
+ }
80
+
81
+ console.log('');
82
+
83
+ } catch (error) {
84
+ throw error;
85
+ }
86
+ }
87
+
88
+ module.exports = { list };
@@ -0,0 +1,140 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const prompts = require('prompts');
5
+ const semver = require('semver');
6
+ const {
7
+ findWorkspaceRoot,
8
+ loadManifest,
9
+ saveManifest,
10
+ getAssetsDir,
11
+ getAvailableCommands,
12
+ getAvailableSkills,
13
+ getAvailableRules
14
+ } = require('./utils');
15
+
16
+ /**
17
+ * Check if a file has been modified by comparing checksums
18
+ */
19
+ function hasLocalChanges(filePath, originalContent) {
20
+ if (!fs.existsSync(filePath)) {
21
+ return false;
22
+ }
23
+
24
+ const currentContent = fs.readFileSync(filePath, 'utf-8');
25
+ return currentContent !== originalContent;
26
+ }
27
+
28
+ /**
29
+ * Upgrade commands
30
+ */
31
+ async function upgradeCommands(workspace, manifest, options) {
32
+ const commands = getAvailableCommands();
33
+ const targetDir = path.join(workspace, '.cursor', 'commands');
34
+ const upgraded = [];
35
+
36
+ console.log(chalk.blue('Upgrading Cursor commands...'));
37
+
38
+ for (const command of commands) {
39
+ const sourcePath = path.join(getAssetsDir(), 'commands', command);
40
+ const targetPath = path.join(targetDir, command);
41
+ const sourceContent = fs.readFileSync(sourcePath, 'utf-8');
42
+
43
+ // Check if file exists and has local changes
44
+ if (fs.existsSync(targetPath)) {
45
+ const hasChanges = manifest?.customizations?.[`commands/${command}`] === 'modified' ||
46
+ hasLocalChanges(targetPath, sourceContent);
47
+
48
+ if (hasChanges && !options.force) {
49
+ // Ask user what to do
50
+ if (!options.preview) {
51
+ const response = await prompts({
52
+ type: 'select',
53
+ name: 'action',
54
+ message: `${command} has local changes. What do you want to do?`,
55
+ choices: [
56
+ { title: 'Keep local version', value: 'keep' },
57
+ { title: 'Overwrite with new version', value: 'overwrite' },
58
+ { title: 'Skip', value: 'skip' }
59
+ ]
60
+ });
61
+
62
+ if (response.action === 'keep') {
63
+ console.log(chalk.yellow(` ⊙ Kept local: ${command}`));
64
+ continue;
65
+ } else if (response.action === 'skip') {
66
+ console.log(chalk.gray(` ○ Skipped: ${command}`));
67
+ continue;
68
+ }
69
+ } else {
70
+ console.log(chalk.yellow(` ⊙ Would ask about: ${command} (has local changes)`));
71
+ continue;
72
+ }
73
+ }
74
+ }
75
+
76
+ if (!options.preview) {
77
+ await fs.copy(sourcePath, targetPath, { overwrite: true });
78
+ console.log(chalk.green(` ✓ Updated: ${command}`));
79
+ upgraded.push(`commands/${command}`);
80
+ } else {
81
+ console.log(chalk.blue(` → Would update: ${command}`));
82
+ }
83
+ }
84
+
85
+ return upgraded;
86
+ }
87
+
88
+ /**
89
+ * Main upgrade function
90
+ */
91
+ async function upgrade(options) {
92
+ try {
93
+ // Find workspace
94
+ const workspace = findWorkspaceRoot();
95
+ const manifest = loadManifest(workspace);
96
+ const packageJson = require('../package.json');
97
+
98
+ if (!manifest) {
99
+ console.log(chalk.yellow('No installation found. Run `docyt-oncall install` first.'));
100
+ return;
101
+ }
102
+
103
+ console.log(chalk.gray(`Workspace: ${workspace}`));
104
+ console.log(chalk.gray(`Current version: ${manifest.version}`));
105
+ console.log(chalk.gray(`Latest version: ${packageJson.version}\n`));
106
+
107
+ // Check if upgrade is needed
108
+ if (semver.gte(manifest.version, packageJson.version)) {
109
+ console.log(chalk.green('✓ You are already on the latest version!'));
110
+ return;
111
+ }
112
+
113
+ const upgradedAssets = [];
114
+
115
+ // Upgrade commands
116
+ if (manifest.assets.commands && manifest.assets.commands.length > 0) {
117
+ const upgraded = await upgradeCommands(workspace, manifest, options);
118
+ upgradedAssets.push(...upgraded);
119
+ }
120
+
121
+ // TODO: Add skills and rules upgrade when they exist
122
+
123
+ if (options.preview) {
124
+ console.log(chalk.blue('\n📋 Preview mode - no changes were made'));
125
+ return;
126
+ }
127
+
128
+ // Update manifest
129
+ manifest.version = packageJson.version;
130
+ manifest.upgraded = new Date().toISOString();
131
+ saveManifest(workspace, manifest);
132
+
133
+ console.log(chalk.gray(`\n✓ Upgraded to version ${packageJson.version}`));
134
+
135
+ } catch (error) {
136
+ throw error;
137
+ }
138
+ }
139
+
140
+ module.exports = { upgrade };
package/lib/utils.js ADDED
@@ -0,0 +1,119 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+
5
+ /**
6
+ * Find the workspace root - use specified path or current directory
7
+ */
8
+ function findWorkspaceRoot(specifiedPath = null) {
9
+ // If path is specified, use it directly
10
+ if (specifiedPath) {
11
+ const resolvedPath = path.resolve(specifiedPath);
12
+ if (!fs.existsSync(resolvedPath)) {
13
+ throw new Error(`Specified path does not exist: ${resolvedPath}`);
14
+ }
15
+ return resolvedPath;
16
+ }
17
+
18
+ // Otherwise, use current working directory
19
+ return process.cwd();
20
+ }
21
+
22
+ /**
23
+ * Get the manifest file path
24
+ */
25
+ function getManifestPath(workspace) {
26
+ return path.join(workspace, '.cursor', '.panda-cli-manifest.json');
27
+ }
28
+
29
+ /**
30
+ * Load the manifest file
31
+ */
32
+ function loadManifest(workspace) {
33
+ const manifestPath = getManifestPath(workspace);
34
+
35
+ if (!fs.existsSync(manifestPath)) {
36
+ return null;
37
+ }
38
+
39
+ try {
40
+ return fs.readJsonSync(manifestPath);
41
+ } catch (error) {
42
+ console.warn(chalk.yellow('Warning: Could not read manifest file'));
43
+ return null;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Save the manifest file
49
+ */
50
+ function saveManifest(workspace, manifest) {
51
+ const manifestPath = getManifestPath(workspace);
52
+ const cursorDir = path.dirname(manifestPath);
53
+
54
+ // Ensure .cursor directory exists
55
+ fs.ensureDirSync(cursorDir);
56
+
57
+ // Write manifest
58
+ fs.writeJsonSync(manifestPath, manifest, { spaces: 2 });
59
+ }
60
+
61
+ /**
62
+ * Get the assets directory from this CLI package
63
+ */
64
+ function getAssetsDir() {
65
+ return path.join(__dirname, '..', 'assets');
66
+ }
67
+
68
+ /**
69
+ * Get list of available commands
70
+ */
71
+ function getAvailableCommands() {
72
+ const commandsDir = path.join(getAssetsDir(), 'commands');
73
+
74
+ if (!fs.existsSync(commandsDir)) {
75
+ return [];
76
+ }
77
+
78
+ return fs.readdirSync(commandsDir)
79
+ .filter(file => file.endsWith('.md'));
80
+ }
81
+
82
+ /**
83
+ * Get list of available skills
84
+ */
85
+ function getAvailableSkills() {
86
+ const skillsDir = path.join(getAssetsDir(), 'skills');
87
+
88
+ if (!fs.existsSync(skillsDir)) {
89
+ return [];
90
+ }
91
+
92
+ return fs.readdirSync(skillsDir)
93
+ .filter(file => file.endsWith('.md'));
94
+ }
95
+
96
+ /**
97
+ * Get list of available rules
98
+ */
99
+ function getAvailableRules() {
100
+ const rulesDir = path.join(getAssetsDir(), 'rules');
101
+
102
+ if (!fs.existsSync(rulesDir)) {
103
+ return [];
104
+ }
105
+
106
+ return fs.readdirSync(rulesDir)
107
+ .filter(file => file.endsWith('.md'));
108
+ }
109
+
110
+ module.exports = {
111
+ findWorkspaceRoot,
112
+ getManifestPath,
113
+ loadManifest,
114
+ saveManifest,
115
+ getAssetsDir,
116
+ getAvailableCommands,
117
+ getAvailableSkills,
118
+ getAvailableRules
119
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@docyt/panda-cli",
3
+ "version": "0.1.1",
4
+ "description": "CLI tool for installing and managing Docyt Team Panda development tools (Cursor commands, skills, rules)",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "panda-cli": "./bin/panda-cli.js"
8
+ },
9
+ "preferGlobal": true,
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "keywords": [
14
+ "docyt",
15
+ "cursor",
16
+ "commands",
17
+ "cli",
18
+ "development-tools"
19
+ ],
20
+ "author": "Docyt Team Panda",
21
+ "license": "ISC",
22
+ "dependencies": {
23
+ "commander": "^12.0.0",
24
+ "chalk": "^4.1.2",
25
+ "prompts": "^2.4.2",
26
+ "fs-extra": "^11.2.0",
27
+ "semver": "^7.5.4"
28
+ },
29
+ "engines": {
30
+ "node": ">=14.0.0"
31
+ },
32
+ "files": [
33
+ "bin/",
34
+ "lib/",
35
+ "assets/",
36
+ "README.md",
37
+ "QUICKSTART.md"
38
+ ]
39
+ }