planflow-plugin 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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/bin/cli.js +169 -0
  4. package/bin/postinstall.js +87 -0
  5. package/commands/pfActivity/SKILL.md +725 -0
  6. package/commands/pfAssign/SKILL.md +623 -0
  7. package/commands/pfCloudLink/SKILL.md +192 -0
  8. package/commands/pfCloudList/SKILL.md +222 -0
  9. package/commands/pfCloudNew/SKILL.md +187 -0
  10. package/commands/pfCloudUnlink/SKILL.md +152 -0
  11. package/commands/pfComment/SKILL.md +227 -0
  12. package/commands/pfComments/SKILL.md +159 -0
  13. package/commands/pfConnectionStatus/SKILL.md +433 -0
  14. package/commands/pfDiscord/SKILL.md +740 -0
  15. package/commands/pfGithubBranch/SKILL.md +672 -0
  16. package/commands/pfGithubIssue/SKILL.md +963 -0
  17. package/commands/pfGithubLink/SKILL.md +859 -0
  18. package/commands/pfGithubPr/SKILL.md +1335 -0
  19. package/commands/pfGithubUnlink/SKILL.md +401 -0
  20. package/commands/pfLive/SKILL.md +185 -0
  21. package/commands/pfLogin/SKILL.md +249 -0
  22. package/commands/pfLogout/SKILL.md +155 -0
  23. package/commands/pfMyTasks/SKILL.md +198 -0
  24. package/commands/pfNotificationSettings/SKILL.md +619 -0
  25. package/commands/pfNotifications/SKILL.md +420 -0
  26. package/commands/pfNotificationsClear/SKILL.md +421 -0
  27. package/commands/pfReact/SKILL.md +232 -0
  28. package/commands/pfSlack/SKILL.md +659 -0
  29. package/commands/pfSyncPull/SKILL.md +210 -0
  30. package/commands/pfSyncPush/SKILL.md +299 -0
  31. package/commands/pfSyncStatus/SKILL.md +212 -0
  32. package/commands/pfTeamInvite/SKILL.md +161 -0
  33. package/commands/pfTeamList/SKILL.md +253 -0
  34. package/commands/pfTeamRemove/SKILL.md +115 -0
  35. package/commands/pfTeamRole/SKILL.md +160 -0
  36. package/commands/pfTestWebhooks/SKILL.md +722 -0
  37. package/commands/pfUnassign/SKILL.md +134 -0
  38. package/commands/pfWhoami/SKILL.md +258 -0
  39. package/commands/pfWorkload/SKILL.md +219 -0
  40. package/commands/planExportCsv/SKILL.md +106 -0
  41. package/commands/planExportGithub/SKILL.md +222 -0
  42. package/commands/planExportJson/SKILL.md +159 -0
  43. package/commands/planExportSummary/SKILL.md +158 -0
  44. package/commands/planNew/SKILL.md +641 -0
  45. package/commands/planNext/SKILL.md +1200 -0
  46. package/commands/planSettingsAutoSync/SKILL.md +199 -0
  47. package/commands/planSettingsLanguage/SKILL.md +201 -0
  48. package/commands/planSettingsReset/SKILL.md +237 -0
  49. package/commands/planSettingsShow/SKILL.md +482 -0
  50. package/commands/planSpec/SKILL.md +929 -0
  51. package/commands/planUpdate/SKILL.md +2518 -0
  52. package/commands/team/SKILL.md +740 -0
  53. package/locales/en.json +1499 -0
  54. package/locales/ka.json +1499 -0
  55. package/package.json +48 -0
  56. package/templates/PROJECT_PLAN.template.md +157 -0
  57. package/templates/backend-api.template.md +562 -0
  58. package/templates/frontend-spa.template.md +610 -0
  59. package/templates/fullstack.template.md +397 -0
  60. package/templates/ka/backend-api.template.md +562 -0
  61. package/templates/ka/frontend-spa.template.md +610 -0
  62. package/templates/ka/fullstack.template.md +397 -0
  63. package/templates/sections/architecture.md +21 -0
  64. package/templates/sections/overview.md +15 -0
  65. package/templates/sections/tasks.md +22 -0
  66. package/templates/sections/tech-stack.md +19 -0
@@ -0,0 +1,963 @@
1
+ ---
2
+ name: pfGithubIssue
3
+ description: Create a GitHub Issue from a PlanFlow task
4
+ ---
5
+
6
+ # PlanFlow GitHub Issue
7
+
8
+ Create a GitHub Issue from a task ID. The issue title, body, and labels are automatically generated from task details.
9
+
10
+ ## Usage
11
+
12
+ ```bash
13
+ /pfGithubIssue T2.1 # Create GitHub issue from task
14
+ /pfGithubIssue T2.1 --open # Create and open in browser (default)
15
+ /pfGithubIssue T2.1 --no-open # Create without opening browser
16
+ ```
17
+
18
+ ## Step 0: Load Configuration
19
+
20
+ ```javascript
21
+ function getMergedConfig() {
22
+ let globalConfig = {}
23
+ let localConfig = {}
24
+
25
+ const globalPath = expandPath("~/.config/claude/plan-plugin-config.json")
26
+ if (fileExists(globalPath)) {
27
+ try { globalConfig = JSON.parse(readFile(globalPath)) } catch (e) {}
28
+ }
29
+
30
+ if (fileExists("./.plan-config.json")) {
31
+ try { localConfig = JSON.parse(readFile("./.plan-config.json")) } catch (e) {}
32
+ }
33
+
34
+ return {
35
+ ...globalConfig,
36
+ ...localConfig,
37
+ cloud: {
38
+ ...(globalConfig.cloud || {}),
39
+ ...(localConfig.cloud || {})
40
+ }
41
+ }
42
+ }
43
+
44
+ const config = getMergedConfig()
45
+ const language = config.language || "en"
46
+ const cloudConfig = config.cloud || {}
47
+ const isAuthenticated = !!cloudConfig.apiToken
48
+ const apiUrl = cloudConfig.apiUrl || "https://api.planflow.tools"
49
+ const projectId = cloudConfig.projectId || null
50
+ const githubConfig = localConfig.github || {}
51
+
52
+ const t = JSON.parse(readFile(`locales/${language}.json`))
53
+ ```
54
+
55
+ ## Step 0.5: Show Notification Badge
56
+
57
+ Only if authenticated AND linked to a project:
58
+
59
+ ```bash
60
+ if [ -n "$TOKEN" ] && [ -n "$PROJECT_ID" ]; then
61
+ RESPONSE=$(curl -s --connect-timeout 3 --max-time 5 \
62
+ -X GET \
63
+ -H "Accept: application/json" \
64
+ -H "Authorization: Bearer $TOKEN" \
65
+ "${API_URL}/projects/${PROJECT_ID}/notifications?limit=1&unread=true" 2>/dev/null)
66
+
67
+ if [ $? -eq 0 ]; then
68
+ UNREAD_COUNT=$(echo "$RESPONSE" | grep -o '"unreadCount":[0-9]*' | grep -o '[0-9]*')
69
+ if [ -n "$UNREAD_COUNT" ] && [ "$UNREAD_COUNT" -gt 0 ]; then
70
+ echo "🔔 $UNREAD_COUNT unread notification(s) — /pfNotifications to view"
71
+ echo ""
72
+ fi
73
+ fi
74
+ fi
75
+ ```
76
+
77
+ ## Step 1: Parse Arguments
78
+
79
+ ```javascript
80
+ const args = commandArgs.trim()
81
+ const taskIdPattern = /^T\d+\.\d+$/i
82
+
83
+ // Parse task ID and flags
84
+ const parts = args.split(/\s+/)
85
+ const taskId = parts[0]
86
+ const flags = parts.slice(1)
87
+
88
+ const noOpen = flags.includes("--no-open")
89
+ const shouldOpen = !noOpen // Default is to open in browser
90
+
91
+ if (!taskId || !taskIdPattern.test(taskId)) {
92
+ showUsageError()
93
+ }
94
+ ```
95
+
96
+ **Usage error output:**
97
+
98
+ ```
99
+ ╭──────────────────────────────────────────────────────────────────────────────╮
100
+ │ ❌ ERROR │
101
+ ├──────────────────────────────────────────────────────────────────────────────┤
102
+ │ │
103
+ │ {t.github.issue.invalidTaskId} │
104
+ │ │
105
+ │ Invalid or missing task ID. │
106
+ │ │
107
+ │ Usage: /pfGithubIssue <task-id> │
108
+ │ │
109
+ │ Examples: │
110
+ │ • /pfGithubIssue T2.1 │
111
+ │ • /pfGithubIssue T2.1 --no-open │
112
+ │ │
113
+ │ Task ID should be like: T1.1, T2.3, T10.5 │
114
+ │ │
115
+ ╰──────────────────────────────────────────────────────────────────────────────╯
116
+ ```
117
+
118
+ ## Step 2: Validate Prerequisites
119
+
120
+ ### 2a: Check Authentication
121
+
122
+ If not authenticated:
123
+
124
+ ```
125
+ ╭──────────────────────────────────────────────────────────────────────────────╮
126
+ │ ❌ ERROR │
127
+ ├──────────────────────────────────────────────────────────────────────────────┤
128
+ │ │
129
+ │ {t.commands.sync.notAuthenticated} │
130
+ │ │
131
+ │ You must be logged in to create GitHub issues. │
132
+ │ │
133
+ │ 💡 Next Steps: │
134
+ │ • /pfLogin Sign in to PlanFlow │
135
+ │ │
136
+ ╰──────────────────────────────────────────────────────────────────────────────╯
137
+ ```
138
+
139
+ ### 2b: Check Project Link
140
+
141
+ If not linked to a cloud project:
142
+
143
+ ```
144
+ ╭──────────────────────────────────────────────────────────────────────────────╮
145
+ │ ❌ ERROR │
146
+ ├──────────────────────────────────────────────────────────────────────────────┤
147
+ │ │
148
+ │ {t.commands.sync.notLinked} │
149
+ │ │
150
+ │ You must link to a cloud project first. │
151
+ │ │
152
+ │ 💡 Next Steps: │
153
+ │ • /pfCloudLink Link to existing project │
154
+ │ • /pfCloudNew Create new cloud project │
155
+ │ │
156
+ ╰──────────────────────────────────────────────────────────────────────────────╯
157
+ ```
158
+
159
+ ### 2c: Check GitHub Integration
160
+
161
+ If no GitHub repository is linked:
162
+
163
+ ```
164
+ ╭──────────────────────────────────────────────────────────────────────────────╮
165
+ │ ❌ ERROR │
166
+ ├──────────────────────────────────────────────────────────────────────────────┤
167
+ │ │
168
+ │ {t.commands.github.notLinked} │
169
+ │ │
170
+ │ No GitHub repository is linked to this project. │
171
+ │ │
172
+ │ 💡 To link a repository: │
173
+ │ • /pfGithubLink owner/repo │
174
+ │ │
175
+ │ Example: │
176
+ │ • /pfGithubLink microsoft/vscode │
177
+ │ │
178
+ ╰──────────────────────────────────────────────────────────────────────────────╯
179
+ ```
180
+
181
+ ## Step 3: Fetch Task Details
182
+
183
+ ### 3a: Try Cloud API First
184
+
185
+ ```bash
186
+ RESPONSE=$(curl -s -w "\n%{http_code}" \
187
+ --connect-timeout 5 \
188
+ --max-time 10 \
189
+ -X GET \
190
+ -H "Accept: application/json" \
191
+ -H "Authorization: Bearer $TOKEN" \
192
+ "${API_URL}/projects/${PROJECT_ID}/tasks/${TASK_ID}")
193
+
194
+ HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
195
+ BODY=$(echo "$RESPONSE" | sed '$d')
196
+
197
+ if [ "$HTTP_CODE" -eq 200 ]; then
198
+ TASK_TITLE=$(echo "$BODY" | jq -r '.data.task.name // .data.name // empty')
199
+ TASK_DESCRIPTION=$(echo "$BODY" | jq -r '.data.task.description // .data.description // empty')
200
+ TASK_STATUS=$(echo "$BODY" | jq -r '.data.task.status // .data.status // empty')
201
+ TASK_COMPLEXITY=$(echo "$BODY" | jq -r '.data.task.complexity // .data.complexity // empty')
202
+ fi
203
+ ```
204
+
205
+ ### 3b: Fallback to Local PROJECT_PLAN.md
206
+
207
+ If cloud API fails or task not found, parse from local file:
208
+
209
+ ```bash
210
+ # Extract task details from PROJECT_PLAN.md
211
+ # Try table format first: | T1.1 | Task Title | Complexity | Status | Dependencies |
212
+ TASK_LINE=$(grep -E "^\|\s*${TASK_ID}\s*\|" PROJECT_PLAN.md | head -1)
213
+
214
+ if [ -n "$TASK_LINE" ]; then
215
+ TASK_TITLE=$(echo "$TASK_LINE" | cut -d'|' -f3 | sed 's/^\s*//' | sed 's/\s*$//')
216
+ TASK_COMPLEXITY=$(echo "$TASK_LINE" | cut -d'|' -f4 | sed 's/^\s*//' | sed 's/\s*$//')
217
+ TASK_STATUS=$(echo "$TASK_LINE" | cut -d'|' -f5 | sed 's/^\s*//' | sed 's/\s*$//')
218
+ else
219
+ # Try markdown heading format: #### **T1.1**: Task Title
220
+ TASK_LINE=$(grep -E "^\s*####\s*\*\*${TASK_ID}\*\*:" PROJECT_PLAN.md | head -1)
221
+ if [ -n "$TASK_LINE" ]; then
222
+ TASK_TITLE=$(echo "$TASK_LINE" | sed 's/.*\*\*:\s*//' | sed 's/\s*$//')
223
+ fi
224
+ fi
225
+
226
+ if [ -z "$TASK_TITLE" ]; then
227
+ echo "❌ Task not found: $TASK_ID"
228
+ exit 1
229
+ fi
230
+ ```
231
+
232
+ ## Step 4: Check if Issue Already Exists
233
+
234
+ ### 4a: Query Existing Issues
235
+
236
+ ```bash
237
+ # Check if an issue already exists for this task
238
+ RESPONSE=$(curl -s -w "\n%{http_code}" \
239
+ --connect-timeout 5 \
240
+ --max-time 10 \
241
+ -X GET \
242
+ -H "Accept: application/json" \
243
+ -H "Authorization: Bearer $TOKEN" \
244
+ "${API_URL}/projects/${PROJECT_ID}/tasks/${TASK_ID}/github-issue")
245
+
246
+ HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
247
+ BODY=$(echo "$RESPONSE" | sed '$d')
248
+
249
+ if [ "$HTTP_CODE" -eq 200 ]; then
250
+ EXISTING_ISSUE_NUMBER=$(echo "$BODY" | jq -r '.data.issueNumber // empty')
251
+ EXISTING_ISSUE_URL=$(echo "$BODY" | jq -r '.data.issueUrl // empty')
252
+
253
+ if [ -n "$EXISTING_ISSUE_NUMBER" ]; then
254
+ # Issue already exists
255
+ showExistingIssue
256
+ fi
257
+ fi
258
+ ```
259
+
260
+ ### 4b: Show Existing Issue Warning
261
+
262
+ If an issue already exists:
263
+
264
+ ```
265
+ ╭──────────────────────────────────────────────────────────────────────────────╮
266
+ │ ⚠️ Issue Already Exists │
267
+ ├──────────────────────────────────────────────────────────────────────────────┤
268
+ │ │
269
+ │ {t.github.issue.alreadyExists} │
270
+ │ │
271
+ │ ── Existing Issue ──────────────────────────────────────────────────────── │
272
+ │ │
273
+ │ 📝 Task: T2.1 - Implement login API │
274
+ │ 🐙 Issue: #42 │
275
+ │ 🔗 URL: https://github.com/owner/repo/issues/42 │
276
+ │ 📊 State: open │
277
+ │ │
278
+ ├──────────────────────────────────────────────────────────────────────────────┤
279
+ │ │
280
+ │ 💡 Options: │
281
+ │ • View the existing issue in browser │
282
+ │ • Close the existing issue first to create a new one │
283
+ │ │
284
+ ╰──────────────────────────────────────────────────────────────────────────────╯
285
+ ```
286
+
287
+ ## Step 5: Create GitHub Issue
288
+
289
+ ### 5a: Build Issue Body
290
+
291
+ ```javascript
292
+ function buildIssueBody(task) {
293
+ const body = `## Task Details
294
+
295
+ **Task ID:** ${task.id}
296
+ **Complexity:** ${task.complexity || 'Not specified'}
297
+ **Status:** ${task.status || 'TODO'}
298
+
299
+ ## Description
300
+
301
+ ${task.description || 'No description provided.'}
302
+
303
+ ## Dependencies
304
+
305
+ ${task.dependencies && task.dependencies.length > 0
306
+ ? task.dependencies.map(d => `- ${d}`).join('\n')
307
+ : 'None'}
308
+
309
+ ---
310
+
311
+ *This issue was created from [PlanFlow](https://planflow.tools) task ${task.id}.*
312
+ *Closes ${task.id} when merged.*
313
+ `
314
+ return body
315
+ }
316
+ ```
317
+
318
+ ### 5b: Determine Labels
319
+
320
+ ```javascript
321
+ function getLabels(task) {
322
+ const labels = []
323
+
324
+ // Add complexity label
325
+ if (task.complexity) {
326
+ const complexity = task.complexity.toLowerCase()
327
+ if (complexity === 'low') labels.push('easy')
328
+ else if (complexity === 'medium') labels.push('medium')
329
+ else if (complexity === 'high') labels.push('hard')
330
+ }
331
+
332
+ // Add status label
333
+ if (task.status === 'TODO') labels.push('todo')
334
+ else if (task.status === 'IN_PROGRESS') labels.push('in-progress')
335
+ else if (task.status === 'BLOCKED') labels.push('blocked')
336
+
337
+ return labels
338
+ }
339
+ ```
340
+
341
+ ### 5c: Create Issue via API
342
+
343
+ ```bash
344
+ # Build request body
345
+ ISSUE_TITLE="[$TASK_ID] $TASK_TITLE"
346
+ ISSUE_BODY=$(cat <<EOF
347
+ ## Task Details
348
+
349
+ **Task ID:** $TASK_ID
350
+ **Complexity:** $TASK_COMPLEXITY
351
+ **Status:** $TASK_STATUS
352
+
353
+ ## Description
354
+
355
+ ${TASK_DESCRIPTION:-No description provided.}
356
+
357
+ ---
358
+
359
+ *This issue was created from [PlanFlow](https://planflow.tools) task $TASK_ID.*
360
+ *Closes $TASK_ID when merged.*
361
+ EOF
362
+ )
363
+
364
+ # Create issue
365
+ RESPONSE=$(curl -s -w "\n%{http_code}" \
366
+ --connect-timeout 5 \
367
+ --max-time 15 \
368
+ -X POST \
369
+ -H "Content-Type: application/json" \
370
+ -H "Accept: application/json" \
371
+ -H "Authorization: Bearer $TOKEN" \
372
+ -d "$(jq -n \
373
+ --arg title "$ISSUE_TITLE" \
374
+ --arg body "$ISSUE_BODY" \
375
+ --arg taskId "$TASK_ID" \
376
+ '{title: $title, body: $body, taskId: $taskId}')" \
377
+ "${API_URL}/projects/${PROJECT_ID}/integrations/github/issues")
378
+
379
+ HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
380
+ BODY=$(echo "$RESPONSE" | sed '$d')
381
+ ```
382
+
383
+ ### 5d: Handle Response
384
+
385
+ **Success (201):**
386
+
387
+ ```
388
+ ╭──────────────────────────────────────────────────────────────────────────────╮
389
+ │ ✅ SUCCESS │
390
+ ├──────────────────────────────────────────────────────────────────────────────┤
391
+ │ │
392
+ │ {t.github.issue.created} │
393
+ │ │
394
+ │ ── Issue Created ────────────────────────────────────────────────────── │
395
+ │ │
396
+ │ 📝 Task: T2.1 - Implement login API │
397
+ │ 🐙 Issue: #42 │
398
+ │ 🔗 URL: https://github.com/owner/repo/issues/42 │
399
+ │ 📊 State: open │
400
+ │ │
401
+ │ ╭────────────────────────────────────────────────────────────────────────╮ │
402
+ │ │ ✓ Issue linked to task T2.1 │ │
403
+ │ ╰────────────────────────────────────────────────────────────────────────╯ │
404
+ │ │
405
+ ├──────────────────────────────────────────────────────────────────────────────┤
406
+ │ │
407
+ │ 💡 What's Next? │
408
+ │ │
409
+ │ Create a branch to work on this issue: │
410
+ │ • /pfGithubBranch T2.1 │
411
+ │ │
412
+ │ When done, create a pull request: │
413
+ │ • /pfGithubPr T2.1 │
414
+ │ │
415
+ │ 💡 Tip: Include "Closes #42" in your PR description │
416
+ │ to auto-close the issue when merged! │
417
+ │ │
418
+ ╰──────────────────────────────────────────────────────────────────────────────╯
419
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
420
+ ```
421
+
422
+ **GitHub Not Linked (400):**
423
+
424
+ ```
425
+ ╭──────────────────────────────────────────────────────────────────────────────╮
426
+ │ ❌ ERROR │
427
+ ├──────────────────────────────────────────────────────────────────────────────┤
428
+ │ │
429
+ │ {t.commands.github.notLinked} │
430
+ │ │
431
+ │ No GitHub repository is linked to this project. │
432
+ │ │
433
+ │ 💡 To link a repository: │
434
+ │ • /pfGithubLink owner/repo │
435
+ │ │
436
+ ╰──────────────────────────────────────────────────────────────────────────────╯
437
+ ```
438
+
439
+ **Task Not Found (404):**
440
+
441
+ ```
442
+ ╭──────────────────────────────────────────────────────────────────────────────╮
443
+ │ ❌ ERROR │
444
+ ├──────────────────────────────────────────────────────────────────────────────┤
445
+ │ │
446
+ │ {t.github.issue.taskNotFound} │
447
+ │ │
448
+ │ Task not found: T99.1 │
449
+ │ │
450
+ │ Make sure the task exists in your PROJECT_PLAN.md or cloud project. │
451
+ │ │
452
+ │ 💡 Try: │
453
+ │ • /pfSyncPush Sync your local tasks to cloud │
454
+ │ • Check PROJECT_PLAN.md for valid task IDs │
455
+ │ │
456
+ ╰──────────────────────────────────────────────────────────────────────────────╯
457
+ ```
458
+
459
+ **GitHub API Error (502/503):**
460
+
461
+ ```
462
+ ╭──────────────────────────────────────────────────────────────────────────────╮
463
+ │ ❌ ERROR │
464
+ ├──────────────────────────────────────────────────────────────────────────────┤
465
+ │ │
466
+ │ {t.github.issue.githubError} │
467
+ │ │
468
+ │ GitHub API error. Could not create the issue. │
469
+ │ │
470
+ │ Possible reasons: │
471
+ │ • GitHub API rate limit exceeded │
472
+ │ • GitHub integration token expired │
473
+ │ • Repository permissions issue │
474
+ │ │
475
+ │ 💡 Try again in a few moments, or check: │
476
+ │ • /pfGithubLink Verify GitHub integration status │
477
+ │ │
478
+ ╰──────────────────────────────────────────────────────────────────────────────╯
479
+ ```
480
+
481
+ ## Step 6: Open Issue in Browser (Optional)
482
+
483
+ If `--no-open` flag is not set and issue was created successfully:
484
+
485
+ ```bash
486
+ # Open in default browser
487
+ if [ "$SHOULD_OPEN" = "true" ] && [ -n "$ISSUE_URL" ]; then
488
+ # macOS
489
+ if command -v open &> /dev/null; then
490
+ open "$ISSUE_URL"
491
+ # Linux
492
+ elif command -v xdg-open &> /dev/null; then
493
+ xdg-open "$ISSUE_URL"
494
+ # Windows (WSL)
495
+ elif command -v wslview &> /dev/null; then
496
+ wslview "$ISSUE_URL"
497
+ fi
498
+ fi
499
+ ```
500
+
501
+ ## Error Handling
502
+
503
+ ### Network Error
504
+
505
+ ```
506
+ ╭──────────────────────────────────────────────────────────────────────────────╮
507
+ │ ❌ ERROR │
508
+ ├──────────────────────────────────────────────────────────────────────────────┤
509
+ │ │
510
+ │ Network error. Could not connect to PlanFlow API. │
511
+ │ │
512
+ │ Please check your internet connection and try again. │
513
+ │ │
514
+ │ 💡 To retry: │
515
+ │ • /pfGithubIssue T2.1 │
516
+ │ │
517
+ ╰──────────────────────────────────────────────────────────────────────────────╯
518
+ ```
519
+
520
+ ### Token Expired (401)
521
+
522
+ ```
523
+ ╭──────────────────────────────────────────────────────────────────────────────╮
524
+ │ ❌ ERROR │
525
+ ├──────────────────────────────────────────────────────────────────────────────┤
526
+ │ │
527
+ │ Authentication failed. Your session may have expired. │
528
+ │ │
529
+ │ 💡 To re-authenticate: │
530
+ │ • /pfLogin │
531
+ │ │
532
+ ╰──────────────────────────────────────────────────────────────────────────────╯
533
+ ```
534
+
535
+ ### Permission Denied (403)
536
+
537
+ ```
538
+ ╭──────────────────────────────────────────────────────────────────────────────╮
539
+ │ ❌ ERROR │
540
+ ├──────────────────────────────────────────────────────────────────────────────┤
541
+ │ │
542
+ │ Permission denied. You don't have access to create issues. │
543
+ │ │
544
+ │ To create GitHub issues, you need: │
545
+ │ • Editor role or higher in the PlanFlow project │
546
+ │ • Write access to the linked GitHub repository │
547
+ │ │
548
+ ╰──────────────────────────────────────────────────────────────────────────────╯
549
+ ```
550
+
551
+ ## Translation Keys
552
+
553
+ Add to `locales/en.json` and `locales/ka.json`:
554
+
555
+ ```json
556
+ {
557
+ "github": {
558
+ "issue": {
559
+ "title": "Create GitHub Issue",
560
+ "created": "GitHub issue created successfully!",
561
+ "alreadyExists": "An issue already exists for this task.",
562
+ "taskNotFound": "Task not found.",
563
+ "invalidTaskId": "Invalid task ID format.",
564
+ "githubError": "GitHub API error.",
565
+ "permissionDenied": "Permission denied.",
566
+ "notLinked": "No GitHub repository linked.",
567
+ "usage": "Usage: /pfGithubIssue <task-id>",
568
+ "example": "Example: /pfGithubIssue T2.1",
569
+ "task": "Task:",
570
+ "issue": "Issue:",
571
+ "url": "URL:",
572
+ "state": "State:",
573
+ "linked": "Issue linked to task",
574
+ "whatsNext": "What's Next?",
575
+ "createBranch": "Create a branch to work on this issue:",
576
+ "createPr": "When done, create a pull request:",
577
+ "autoCloseTip": "Include \"Closes #{issue}\" in your PR description to auto-close the issue!"
578
+ }
579
+ }
580
+ }
581
+ ```
582
+
583
+ **Georgian translations:**
584
+
585
+ ```json
586
+ {
587
+ "github": {
588
+ "issue": {
589
+ "title": "GitHub Issue-ის შექმნა",
590
+ "created": "GitHub issue წარმატებით შეიქმნა!",
591
+ "alreadyExists": "ამ ამოცანისთვის issue უკვე არსებობს.",
592
+ "taskNotFound": "ამოცანა ვერ მოიძებნა.",
593
+ "invalidTaskId": "ამოცანის ID-ის არასწორი ფორმატი.",
594
+ "githubError": "GitHub API-ის შეცდომა.",
595
+ "permissionDenied": "წვდომა აკრძალულია.",
596
+ "notLinked": "GitHub რეპოზიტორია არ არის დაკავშირებული.",
597
+ "usage": "გამოყენება: /pfGithubIssue <task-id>",
598
+ "example": "მაგალითი: /pfGithubIssue T2.1",
599
+ "task": "ამოცანა:",
600
+ "issue": "Issue:",
601
+ "url": "URL:",
602
+ "state": "სტატუსი:",
603
+ "linked": "Issue დაკავშირებულია ამოცანასთან",
604
+ "whatsNext": "შემდეგი ნაბიჯები?",
605
+ "createBranch": "შექმენი ბრანჩი ამ issue-ზე სამუშაოდ:",
606
+ "createPr": "დასრულებისას შექმენი pull request:",
607
+ "autoCloseTip": "ჩასვი \"Closes #{issue}\" PR-ის აღწერაში issue-ის ავტომატურად დასახურად!"
608
+ }
609
+ }
610
+ }
611
+ ```
612
+
613
+ ## Full Bash Implementation
614
+
615
+ ```bash
616
+ #!/bin/bash
617
+
618
+ # Step 0: Load config
619
+ GLOBAL_CONFIG_PATH="$HOME/.config/claude/plan-plugin-config.json"
620
+ LOCAL_CONFIG_PATH="./.plan-config.json"
621
+
622
+ # Read configs and merge
623
+ if [ -f "$GLOBAL_CONFIG_PATH" ]; then
624
+ API_TOKEN=$(jq -r '.cloud.apiToken // empty' "$GLOBAL_CONFIG_PATH")
625
+ API_URL=$(jq -r '.cloud.apiUrl // "https://api.planflow.tools"' "$GLOBAL_CONFIG_PATH")
626
+ fi
627
+
628
+ if [ -f "$LOCAL_CONFIG_PATH" ]; then
629
+ PROJECT_ID=$(jq -r '.cloud.projectId // empty' "$LOCAL_CONFIG_PATH")
630
+ # Local can override global
631
+ LOCAL_TOKEN=$(jq -r '.cloud.apiToken // empty' "$LOCAL_CONFIG_PATH")
632
+ [ -n "$LOCAL_TOKEN" ] && API_TOKEN="$LOCAL_TOKEN"
633
+ fi
634
+
635
+ # Parse arguments
636
+ TASK_ID="$1"
637
+ shift
638
+ NO_OPEN=false
639
+
640
+ while [[ $# -gt 0 ]]; do
641
+ case "$1" in
642
+ --no-open)
643
+ NO_OPEN=true
644
+ shift
645
+ ;;
646
+ --open)
647
+ NO_OPEN=false
648
+ shift
649
+ ;;
650
+ *)
651
+ shift
652
+ ;;
653
+ esac
654
+ done
655
+
656
+ # Validate task ID format
657
+ if [ -z "$TASK_ID" ] || ! echo "$TASK_ID" | grep -qiE '^T[0-9]+\.[0-9]+$'; then
658
+ echo "╭──────────────────────────────────────────────────────────────────────────────╮"
659
+ echo "│ ❌ ERROR │"
660
+ echo "├──────────────────────────────────────────────────────────────────────────────┤"
661
+ echo "│ │"
662
+ echo "│ Invalid or missing task ID: $TASK_ID │"
663
+ echo "│ │"
664
+ echo "│ Usage: /pfGithubIssue <task-id> │"
665
+ echo "│ │"
666
+ echo "│ Examples: │"
667
+ echo "│ • /pfGithubIssue T2.1 │"
668
+ echo "│ • /pfGithubIssue T2.1 --no-open │"
669
+ echo "│ │"
670
+ echo "│ Task ID should be like: T1.1, T2.3, T10.5 │"
671
+ echo "│ │"
672
+ echo "╰──────────────────────────────────────────────────────────────────────────────╯"
673
+ exit 1
674
+ fi
675
+
676
+ # Normalize task ID to uppercase
677
+ TASK_ID=$(echo "$TASK_ID" | tr '[:lower:]' '[:upper:]')
678
+
679
+ # Validate prerequisites
680
+ if [ -z "$API_TOKEN" ]; then
681
+ echo "❌ Not authenticated. Run /pfLogin first."
682
+ exit 1
683
+ fi
684
+
685
+ if [ -z "$PROJECT_ID" ]; then
686
+ echo "❌ No project linked. Run /pfCloudLink first."
687
+ exit 1
688
+ fi
689
+
690
+ # Fetch task details from cloud
691
+ TASK_TITLE=""
692
+ TASK_DESCRIPTION=""
693
+ TASK_STATUS=""
694
+ TASK_COMPLEXITY=""
695
+
696
+ echo "🔍 Fetching task details..."
697
+
698
+ RESPONSE=$(curl -s -w "\n%{http_code}" \
699
+ --connect-timeout 5 \
700
+ --max-time 10 \
701
+ -X GET \
702
+ -H "Accept: application/json" \
703
+ -H "Authorization: Bearer $API_TOKEN" \
704
+ "${API_URL}/projects/${PROJECT_ID}/tasks/${TASK_ID}" 2>/dev/null)
705
+
706
+ HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
707
+ BODY=$(echo "$RESPONSE" | sed '$d')
708
+
709
+ if [ "$HTTP_CODE" -eq 200 ]; then
710
+ TASK_TITLE=$(echo "$BODY" | jq -r '.data.task.name // .data.name // empty' 2>/dev/null)
711
+ TASK_DESCRIPTION=$(echo "$BODY" | jq -r '.data.task.description // .data.description // empty' 2>/dev/null)
712
+ TASK_STATUS=$(echo "$BODY" | jq -r '.data.task.status // .data.status // empty' 2>/dev/null)
713
+ TASK_COMPLEXITY=$(echo "$BODY" | jq -r '.data.task.complexity // .data.complexity // empty' 2>/dev/null)
714
+ fi
715
+
716
+ # Fallback to local PROJECT_PLAN.md
717
+ if [ -z "$TASK_TITLE" ] && [ -f "PROJECT_PLAN.md" ]; then
718
+ echo "⚠️ Cloud task not found, using local PROJECT_PLAN.md..."
719
+
720
+ # Try table format: | T1.1 | Task Title | Complexity | Status | Dependencies |
721
+ TASK_LINE=$(grep -E "^\|\s*${TASK_ID}\s*\|" PROJECT_PLAN.md | head -1)
722
+
723
+ if [ -n "$TASK_LINE" ]; then
724
+ TASK_TITLE=$(echo "$TASK_LINE" | cut -d'|' -f3 | sed 's/^\s*//' | sed 's/\s*$//')
725
+ TASK_COMPLEXITY=$(echo "$TASK_LINE" | cut -d'|' -f4 | sed 's/^\s*//' | sed 's/\s*$//')
726
+ TASK_STATUS=$(echo "$TASK_LINE" | cut -d'|' -f5 | sed 's/^\s*//' | sed 's/\s*$//')
727
+ else
728
+ # Try markdown heading format: #### **T1.1**: Task Title
729
+ TASK_LINE=$(grep -E "^\s*####\s*\*\*${TASK_ID}\*\*:" PROJECT_PLAN.md | head -1)
730
+ if [ -n "$TASK_LINE" ]; then
731
+ TASK_TITLE=$(echo "$TASK_LINE" | sed 's/.*\*\*:\s*//' | sed 's/\s*$//')
732
+ fi
733
+ fi
734
+ fi
735
+
736
+ if [ -z "$TASK_TITLE" ]; then
737
+ echo "╭──────────────────────────────────────────────────────────────────────────────╮"
738
+ echo "│ ❌ ERROR │"
739
+ echo "├──────────────────────────────────────────────────────────────────────────────┤"
740
+ echo "│ │"
741
+ echo "│ Task not found: $TASK_ID │"
742
+ echo "│ │"
743
+ echo "│ Make sure the task exists in PROJECT_PLAN.md or is synced to cloud. │"
744
+ echo "│ │"
745
+ echo "│ 💡 Try: │"
746
+ echo "│ • /pfSyncPush Sync your local tasks to cloud │"
747
+ echo "│ • Check PROJECT_PLAN.md for valid task IDs │"
748
+ echo "│ │"
749
+ echo "╰──────────────────────────────────────────────────────────────────────────────╯"
750
+ exit 1
751
+ fi
752
+
753
+ echo "📝 Creating GitHub issue for: $TASK_ID - $TASK_TITLE"
754
+
755
+ # Build issue title and body
756
+ ISSUE_TITLE="[$TASK_ID] $TASK_TITLE"
757
+
758
+ # Handle null/empty description
759
+ if [ -z "$TASK_DESCRIPTION" ] || [ "$TASK_DESCRIPTION" = "null" ]; then
760
+ TASK_DESCRIPTION="No description provided."
761
+ fi
762
+
763
+ # Create issue via API
764
+ RESPONSE=$(curl -s -w "\n%{http_code}" \
765
+ --connect-timeout 5 \
766
+ --max-time 15 \
767
+ -X POST \
768
+ -H "Content-Type: application/json" \
769
+ -H "Accept: application/json" \
770
+ -H "Authorization: Bearer $API_TOKEN" \
771
+ -d "$(jq -n \
772
+ --arg title "$ISSUE_TITLE" \
773
+ --arg taskId "$TASK_ID" \
774
+ --arg complexity "${TASK_COMPLEXITY:-Not specified}" \
775
+ --arg status "${TASK_STATUS:-TODO}" \
776
+ --arg description "$TASK_DESCRIPTION" \
777
+ '{
778
+ title: $title,
779
+ taskId: $taskId,
780
+ body: "## Task Details\n\n**Task ID:** \($taskId)\n**Complexity:** \($complexity)\n**Status:** \($status)\n\n## Description\n\n\($description)\n\n---\n\n*This issue was created from [PlanFlow](https://planflow.tools) task \($taskId).*\n*Closes \($taskId) when merged.*"
781
+ }')" \
782
+ "${API_URL}/projects/${PROJECT_ID}/integrations/github/issues")
783
+
784
+ HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
785
+ BODY=$(echo "$RESPONSE" | sed '$d')
786
+
787
+ if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
788
+ ISSUE_NUMBER=$(echo "$BODY" | jq -r '.data.issueNumber // .data.number // empty')
789
+ ISSUE_URL=$(echo "$BODY" | jq -r '.data.issueUrl // .data.url // .data.html_url // empty')
790
+ ISSUE_STATE=$(echo "$BODY" | jq -r '.data.state // "open"')
791
+
792
+ echo ""
793
+ echo "╭──────────────────────────────────────────────────────────────────────────────╮"
794
+ echo "│ ✅ SUCCESS │"
795
+ echo "├──────────────────────────────────────────────────────────────────────────────┤"
796
+ echo "│ │"
797
+ echo "│ GitHub issue created successfully! │"
798
+ echo "│ │"
799
+ echo "│ ── Issue Created ──────────────────────────────────────────────────────── │"
800
+ echo "│ │"
801
+ echo "│ 📝 Task: $TASK_ID - $TASK_TITLE"
802
+ echo "│ 🐙 Issue: #$ISSUE_NUMBER"
803
+ echo "│ 🔗 URL: $ISSUE_URL"
804
+ echo "│ 📊 State: $ISSUE_STATE"
805
+ echo "│ │"
806
+ echo "│ ╭────────────────────────────────────────────────────────────────────────╮ │"
807
+ echo "│ │ ✓ Issue linked to task $TASK_ID │ │"
808
+ echo "│ ╰────────────────────────────────────────────────────────────────────────╯ │"
809
+ echo "│ │"
810
+ echo "├──────────────────────────────────────────────────────────────────────────────┤"
811
+ echo "│ │"
812
+ echo "│ 💡 What's Next? │"
813
+ echo "│ │"
814
+ echo "│ Create a branch to work on this issue: │"
815
+ echo "│ • /pfGithubBranch $TASK_ID │"
816
+ echo "│ │"
817
+ echo "│ When done, create a pull request: │"
818
+ echo "│ • /pfGithubPr $TASK_ID │"
819
+ echo "│ │"
820
+ echo "│ 💡 Tip: Include \"Closes #$ISSUE_NUMBER\" in your PR description │"
821
+ echo "│ to auto-close the issue when merged! │"
822
+ echo "│ │"
823
+ echo "╰──────────────────────────────────────────────────────────────────────────────╯"
824
+
825
+ # Open in browser if requested
826
+ if [ "$NO_OPEN" = "false" ] && [ -n "$ISSUE_URL" ]; then
827
+ if command -v open &> /dev/null; then
828
+ open "$ISSUE_URL" 2>/dev/null
829
+ elif command -v xdg-open &> /dev/null; then
830
+ xdg-open "$ISSUE_URL" 2>/dev/null
831
+ elif command -v wslview &> /dev/null; then
832
+ wslview "$ISSUE_URL" 2>/dev/null
833
+ fi
834
+ fi
835
+
836
+ exit 0
837
+ elif [ "$HTTP_CODE" -eq 400 ]; then
838
+ ERROR_MSG=$(echo "$BODY" | jq -r '.error.message // .message // "Bad request"')
839
+ echo ""
840
+ echo "╭──────────────────────────────────────────────────────────────────────────────╮"
841
+ echo "│ ❌ ERROR │"
842
+ echo "├──────────────────────────────────────────────────────────────────────────────┤"
843
+ echo "│ │"
844
+ echo "│ $ERROR_MSG"
845
+ echo "│ │"
846
+ echo "│ 💡 Make sure GitHub is linked: │"
847
+ echo "│ • /pfGithubLink owner/repo │"
848
+ echo "│ │"
849
+ echo "╰──────────────────────────────────────────────────────────────────────────────╯"
850
+ exit 1
851
+ elif [ "$HTTP_CODE" -eq 401 ]; then
852
+ echo "❌ Authentication failed. Run /pfLogin to refresh."
853
+ exit 1
854
+ elif [ "$HTTP_CODE" -eq 403 ]; then
855
+ echo "❌ Permission denied. You need Editor role and GitHub write access."
856
+ exit 1
857
+ elif [ "$HTTP_CODE" -eq 404 ]; then
858
+ echo "❌ Task or project not found."
859
+ exit 1
860
+ elif [ "$HTTP_CODE" -eq 409 ]; then
861
+ # Issue already exists
862
+ EXISTING_URL=$(echo "$BODY" | jq -r '.data.existingIssueUrl // .data.issueUrl // empty')
863
+ EXISTING_NUMBER=$(echo "$BODY" | jq -r '.data.existingIssueNumber // .data.issueNumber // empty')
864
+
865
+ echo ""
866
+ echo "╭──────────────────────────────────────────────────────────────────────────────╮"
867
+ echo "│ ⚠️ Issue Already Exists │"
868
+ echo "├──────────────────────────────────────────────────────────────────────────────┤"
869
+ echo "│ │"
870
+ echo "│ An issue already exists for this task. │"
871
+ echo "│ │"
872
+ echo "│ ── Existing Issue ──────────────────────────────────────────────────────── │"
873
+ echo "│ │"
874
+ echo "│ 📝 Task: $TASK_ID - $TASK_TITLE"
875
+ echo "│ 🐙 Issue: #$EXISTING_NUMBER"
876
+ echo "│ 🔗 URL: $EXISTING_URL"
877
+ echo "│ │"
878
+ echo "├──────────────────────────────────────────────────────────────────────────────┤"
879
+ echo "│ │"
880
+ echo "│ 💡 Options: │"
881
+ echo "│ • View the existing issue in browser │"
882
+ echo "│ • Close the existing issue first to create a new one │"
883
+ echo "│ │"
884
+ echo "╰──────────────────────────────────────────────────────────────────────────────╯"
885
+
886
+ # Open existing issue
887
+ if [ "$NO_OPEN" = "false" ] && [ -n "$EXISTING_URL" ]; then
888
+ if command -v open &> /dev/null; then
889
+ open "$EXISTING_URL" 2>/dev/null
890
+ elif command -v xdg-open &> /dev/null; then
891
+ xdg-open "$EXISTING_URL" 2>/dev/null
892
+ fi
893
+ fi
894
+ exit 0
895
+ else
896
+ echo "╭──────────────────────────────────────────────────────────────────────────────╮"
897
+ echo "│ ❌ ERROR │"
898
+ echo "├──────────────────────────────────────────────────────────────────────────────┤"
899
+ echo "│ │"
900
+ echo "│ Failed to create GitHub issue (HTTP $HTTP_CODE) │"
901
+ echo "│ │"
902
+ echo "│ Possible reasons: │"
903
+ echo "│ • GitHub API rate limit exceeded │"
904
+ echo "│ • GitHub integration token expired │"
905
+ echo "│ • Repository permissions issue │"
906
+ echo "│ │"
907
+ echo "│ 💡 Try again later, or check: │"
908
+ echo "│ • /pfGithubLink Verify GitHub integration status │"
909
+ echo "│ │"
910
+ echo "╰──────────────────────────────────────────────────────────────────────────────╯"
911
+ exit 1
912
+ fi
913
+ ```
914
+
915
+ ## Testing
916
+
917
+ ```bash
918
+ # Test 1: Create issue for valid task
919
+ /pfGithubIssue T2.1
920
+ # Expected: Creates issue and opens in browser
921
+
922
+ # Test 2: Create without opening browser
923
+ /pfGithubIssue T2.1 --no-open
924
+ # Expected: Creates issue, shows URL but doesn't open
925
+
926
+ # Test 3: Issue already exists
927
+ /pfGithubIssue T2.1
928
+ # Expected: Shows existing issue info
929
+
930
+ # Test 4: Invalid task ID
931
+ /pfGithubIssue invalid
932
+ # Expected: Error with format hint
933
+
934
+ # Test 5: Task not found
935
+ /pfGithubIssue T99.99
936
+ # Expected: Error with suggestions
937
+
938
+ # Test 6: Not authenticated
939
+ # (Clear token first)
940
+ /pfGithubIssue T2.1
941
+ # Expected: Error "Not authenticated"
942
+
943
+ # Test 7: No project linked
944
+ # (Clear projectId first)
945
+ /pfGithubIssue T2.1
946
+ # Expected: Error "No project linked"
947
+
948
+ # Test 8: GitHub not linked
949
+ # (Unlink GitHub first)
950
+ /pfGithubIssue T2.1
951
+ # Expected: Error "No GitHub repository linked"
952
+ ```
953
+
954
+ ## Success Criteria
955
+
956
+ - [ ] Creates GitHub issue from task ID with proper title/body
957
+ - [ ] Opens created issue in browser by default
958
+ - [ ] --no-open flag prevents browser opening
959
+ - [ ] Handles existing issues gracefully (shows link)
960
+ - [ ] Falls back to local PROJECT_PLAN.md when cloud unavailable
961
+ - [ ] Shows helpful next steps after creation
962
+ - [ ] Validates prerequisites (auth, project, GitHub link)
963
+ - [ ] Works in both English and Georgian