@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 +153 -0
- package/README.md +201 -0
- package/assets/commands/docyt-panda-oncall-issues.md +249 -0
- package/bin/panda-cli.js +77 -0
- package/lib/diff.js +61 -0
- package/lib/installer.js +138 -0
- package/lib/list.js +88 -0
- package/lib/upgrader.js +140 -0
- package/lib/utils.js +119 -0
- package/package.json +39 -0
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.
|
package/bin/panda-cli.js
ADDED
|
@@ -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 };
|
package/lib/installer.js
ADDED
|
@@ -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 };
|
package/lib/upgrader.js
ADDED
|
@@ -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
|
+
}
|