@forloop-cc/forloop-opencode-plugin-planner 2.2.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +225 -0
  2. package/CODE_OF_CONDUCT.md +77 -0
  3. package/CONTRIBUTING.md +176 -0
  4. package/LICENSE +21 -0
  5. package/README.md +211 -0
  6. package/SECURITY.md +69 -0
  7. package/docs/CONFIGURATION.md +283 -0
  8. package/docs/DEVELOPER-VS-USER.md +129 -0
  9. package/docs/IMPROVED-AUTHENTICATION.md +317 -0
  10. package/docs/INSTALLATION-SCRIPTS.md +304 -0
  11. package/docs/PLANNER-AGENT-IMPROVEMENTS.md +88 -0
  12. package/docs/PLUGIN-ARCHITECTURE.md +308 -0
  13. package/docs/QUICKSTART-NON-DEV.md +94 -0
  14. package/docs/README-FOR-HUMANS-AND-AI.md +109 -0
  15. package/docs/README.md +70 -0
  16. package/docs/SKILLS-GAP-ANALYSIS.md +157 -0
  17. package/docs/USER-INTERACTION-MODEL.md +106 -0
  18. package/docs/file-management/2026-04-12-forloop-aivy-documents-sync.md +395 -0
  19. package/docs/file-management/FILE_UPLOAD_ENHANCEMENT.md +431 -0
  20. package/docs/file-management/FILE_UPLOAD_FLOW.md +300 -0
  21. package/docs/images/forloop-opencode-flow.png +0 -0
  22. package/docs/images/forloop-opencode-header.png +0 -0
  23. package/docs/superpowers/plans/2026-05-03-sprint-isolated-directory-structure.md +1450 -0
  24. package/docs/superpowers/plans/2026-05-29-install-system.md +850 -0
  25. package/docs/superpowers/specs/2026-05-03-sprint-isolated-directory-structure-design.md +149 -0
  26. package/docs/superpowers/specs/2026-05-19-opencode-message-recording-design.md +468 -0
  27. package/docs/superpowers/specs/2026-05-29-install-system-design.md +135 -0
  28. package/install.sh +317 -0
  29. package/package.json +87 -0
  30. package/plugins/capabilities/api-client.ts +556 -0
  31. package/plugins/capabilities/auth.ts +120 -0
  32. package/plugins/capabilities/config.ts +195 -0
  33. package/plugins/capabilities/context-resolver.ts +106 -0
  34. package/plugins/capabilities/token-storage.ts +98 -0
  35. package/plugins/forloop-plugin.ts +186 -0
  36. package/plugins/hooks/message-recorder.ts +258 -0
  37. package/plugins/hooks/shell-env.ts +62 -0
  38. package/plugins/tools/agent-sprint-tools.ts +100 -0
  39. package/plugins/tools/agent-suggestion-tools.ts +275 -0
  40. package/plugins/tools/agent-tools.ts +114 -0
  41. package/plugins/tools/ai-trigger-tools.ts +79 -0
  42. package/plugins/tools/developer-status-tool.ts +97 -0
  43. package/plugins/tools/file-tools.ts +240 -0
  44. package/plugins/tools/folder-tools.ts +75 -0
  45. package/plugins/tools/organization-tools.ts +213 -0
  46. package/plugins/tools/schedule-tools.ts +173 -0
  47. package/plugins/tools/sprint-tools.ts +287 -0
  48. package/plugins/tools/story-tools.ts +320 -0
  49. package/plugins/tools/sync-tools.ts +509 -0
  50. package/plugins/tools/template-tools.ts +170 -0
  51. package/plugins/tools/user-tools.ts +146 -0
  52. package/scripts/migrate-tokens.js +90 -0
  53. package/scripts/use-prod.sh +11 -0
  54. package/tsconfig.json +17 -0
  55. package/update.sh +172 -0
@@ -0,0 +1,850 @@
1
+ # Install System Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Build curl-pipeable `install.sh` and `update.sh` scripts with npm+git support, local+global install, and jq-based config merging that preserves existing opencode configuration.
6
+
7
+ **Architecture:** Two standalone bash scripts at repo root. `install.sh` handles first-time setup (git clone or npm install, dep checks, config merge, token setup). `update.sh` handles upgrades (auto-detect install, git pull or npm update). Both are curl-pipeable from GitHub raw URLs. `package.json` updated with `files` field for npm publishing.
8
+
9
+ **Tech Stack:** Bash 3.2+ (macOS/Linux/Windows Git Bash), jq, git, npm, curl
10
+
11
+ ---
12
+
13
+ ## File Structure
14
+
15
+ | File | Action | Responsibility |
16
+ |------|--------|---------------|
17
+ | `install.sh` | Rewrite | One-liner installer — dep checks, git clone/npm install, config merge, token setup |
18
+ | `update.sh` | Rewrite | Auto-detect install location, git pull or npm update, show changelog |
19
+ | `package.json` | Modify | Add `files` field for npm publishing |
20
+
21
+ ---
22
+
23
+ ### Task 1: Rewrite `install.sh`
24
+
25
+ **Files:**
26
+ - Rewrite: `forloop-opencode-plugin-planner/install.sh`
27
+ - Read reference: `forloop-opencode-plugin-planner/plugins/forloop-plugin.ts` (to verify entry point path)
28
+
29
+ - [ ] **Step 1: Write the install.sh script**
30
+
31
+ ```bash
32
+ #!/usr/bin/env bash
33
+ set -euo pipefail
34
+
35
+ # ForLoop Plugin Installer
36
+ # https://github.com/forloop-cc/forloop-opencode-plugin-planner
37
+ #
38
+ # Usage:
39
+ # curl -fsSL https://raw.githubusercontent.com/forloop-cc/forloop-opencode-plugin-planner/main/install.sh | bash
40
+ # curl -fsSL ... | bash -s -- --global
41
+ # bash install.sh --local --force
42
+ # bash install.sh --npm --global
43
+
44
+ # ── Platform & colors ────────────────────────────────────────────────────────
45
+ PLATFORM="$(uname -s)"
46
+ case "$PLATFORM" in
47
+ Darwin*) PLATFORM_LABEL="macOS";;
48
+ Linux*) PLATFORM_LABEL="Linux";;
49
+ CYGWIN*|MINGW*|MSYS*) PLATFORM_LABEL="Windows";;
50
+ *) PLATFORM_LABEL="Unknown";;
51
+ esac
52
+
53
+ if [ "$PLATFORM_LABEL" = "Windows" ] && [ -z "$WT_SESSION" ] && [ -z "$ConEmuPID" ]; then
54
+ RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; BOLD=''; NC=''
55
+ else
56
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
57
+ BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
58
+ fi
59
+
60
+ # ── Configuration ────────────────────────────────────────────────────────────
61
+ REPO="https://github.com/forloop-cc/forloop-opencode-plugin-planner.git"
62
+ RAW_URL="https://raw.githubusercontent.com/forloop-cc/forloop-opencode-plugin-planner/main"
63
+ BRANCH="${FORLOOP_BRANCH:-main}"
64
+ PLUGIN_DIRNAME="forloop-planner"
65
+ GLOBAL_INSTALL_DIR="$HOME/.config/opencode/plugins/$PLUGIN_DIRNAME"
66
+ LOCAL_INSTALL_DIR=".opencode/plugins/$PLUGIN_DIRNAME"
67
+ ENTRY_POINT="plugins/forloop-plugin.ts"
68
+
69
+ INSTALL_TYPE="" # global | local | custom
70
+ INSTALL_DIR="" # resolved path
71
+ USE_NPM=false
72
+ FORCE=false
73
+ NON_INTERACTIVE=false
74
+
75
+ print_header() { echo -e "${BLUE}${BOLD}╔══════════════════════════════════════════╗\n║ ForLoop Plugin Installer ║\n╚══════════════════════════════════════════╝${NC}\n"; }
76
+ print_success() { echo -e "${GREEN}✓${NC} $1"; }
77
+ print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
78
+ print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
79
+ print_error() { echo -e "${RED}✗${NC} $1" >&2; }
80
+ print_step() { echo -e "\n${CYAN}${BOLD}▶${NC} $1\n"; }
81
+
82
+ show_help() {
83
+ cat << EOF
84
+ Usage: install.sh [OPTIONS]
85
+
86
+ Options:
87
+ -g, --global Install globally (~/.config/opencode/plugins/)
88
+ -l, --local Install locally (./.opencode/plugins/)
89
+ -d, --dir PATH Custom install directory
90
+ -n, --npm Use npm install instead of git clone
91
+ -b, --branch NAME Clone a specific branch (default: $BRANCH)
92
+ -f, --force Overwrite existing installation
93
+ -h, --help Show this help
94
+
95
+ Examples:
96
+ curl -fsSL .../install.sh | bash # Interactive
97
+ curl -fsSL .../install.sh | bash -s -- -g # Global, non-interactive
98
+ bash install.sh --local --force # Local, force overwrite
99
+ bash install.sh --npm --global # npm install, global
100
+ EOF
101
+ }
102
+
103
+ # ── Dependency checks ────────────────────────────────────────────────────────
104
+
105
+ check_git() {
106
+ if ! command -v git &>/dev/null; then
107
+ print_error "git is required but not installed"
108
+ case "$PLATFORM_LABEL" in
109
+ macOS) echo " brew install git" ;;
110
+ Linux) echo " sudo apt-get install git (Ubuntu/Debian)"
111
+ echo " sudo dnf install git (Fedora/RHEL)" ;;
112
+ Windows) echo " https://git-scm.com/download/win" ;;
113
+ esac
114
+ return 1
115
+ fi
116
+ return 0
117
+ }
118
+
119
+ check_node() {
120
+ if ! command -v node &>/dev/null; then
121
+ print_warning "Node.js not found (needed for npm install)"
122
+ case "$PLATFORM_LABEL" in
123
+ macOS) echo " brew install node" ;;
124
+ Linux) echo " curl -fsSL https://deb.nodesource.com/setup_20.x | bash -"
125
+ echo " sudo apt-get install -y nodejs" ;;
126
+ Windows) echo " https://nodejs.org" ;;
127
+ esac
128
+ if [ "$NON_INTERACTIVE" = true ]; then
129
+ print_info "Continuing anyway (npm install will be attempted)..."
130
+ else
131
+ read -p "Continue anyway? [y/N] " -r; echo
132
+ [[ ! $REPLY =~ ^[Yy]$ ]] && return 1
133
+ fi
134
+ fi
135
+ return 0
136
+ }
137
+
138
+ check_jq() {
139
+ if ! command -v jq &>/dev/null; then
140
+ print_error "jq is required for config merging"
141
+ case "$PLATFORM_LABEL" in
142
+ macOS) echo " brew install jq" ;;
143
+ Linux) echo " sudo apt-get install jq (Ubuntu/Debian)"
144
+ echo " sudo dnf install jq (Fedora/RHEL)" ;;
145
+ Windows) echo " scoop install jq or https://stedolan.github.io/jq/download/" ;;
146
+ esac
147
+ return 1
148
+ fi
149
+ return 0
150
+ }
151
+
152
+ check_opencode() {
153
+ if command -v opencode &>/dev/null; then
154
+ print_success "OpenCode CLI found: $(which opencode)"
155
+ else
156
+ print_warning "OpenCode CLI not found"
157
+ echo " Install: curl -fsSL https://opencode.ai/install.sh | bash"
158
+ if [ "$NON_INTERACTIVE" != true ]; then
159
+ read -p "Continue anyway? [y/N] " -r; echo
160
+ [[ ! $REPLY =~ ^[Yy]$ ]] && exit 1
161
+ fi
162
+ fi
163
+ }
164
+
165
+ check_all_deps() {
166
+ print_step "Checking dependencies..."
167
+ local missing=0
168
+ check_git || true # always check git
169
+ [ "$USE_NPM" != true ] && { check_git || missing=$((missing + 1)); }
170
+ check_node || missing=$((missing + 1))
171
+ check_jq || missing=$((missing + 1))
172
+ if [ "$missing" -gt 0 ]; then
173
+ print_error "Missing $missing required dependencies. Install them and try again."
174
+ exit 1
175
+ fi
176
+ print_success "All dependencies found"
177
+ }
178
+
179
+ # ── Install location ─────────────────────────────────────────────────────────
180
+
181
+ resolve_install_dir() {
182
+ case "$INSTALL_TYPE" in
183
+ global) INSTALL_DIR="$GLOBAL_INSTALL_DIR" ;;
184
+ local) INSTALL_DIR="$LOCAL_INSTALL_DIR" ;;
185
+ custom) ;; # INSTALL_DIR already set by --dir flag
186
+ esac
187
+
188
+ local parent_dir
189
+ parent_dir="$(dirname "$INSTALL_DIR")"
190
+ if [ ! -d "$parent_dir" ]; then
191
+ print_info "Creating parent directory: $parent_dir"
192
+ mkdir -p "$parent_dir"
193
+ fi
194
+ }
195
+
196
+ prompt_install_type() {
197
+ echo -e "${BOLD}Choose installation type:${NC}"
198
+ echo " 1) Global — available for all projects (~/.config/opencode/plugins/)"
199
+ echo " 2) Local — only for this project (./.opencode/plugins/)"
200
+ echo " 3) Cancel"
201
+ read -p "Select [1-3]: " -r; echo
202
+ case "$REPLY" in
203
+ 1) INSTALL_TYPE="global" ;;
204
+ 2) INSTALL_TYPE="local" ;;
205
+ *) print_info "Installation cancelled"; exit 0 ;;
206
+ esac
207
+ }
208
+
209
+ # ── Installation ─────────────────────────────────────────────────────────────
210
+
211
+ install_via_git() {
212
+ print_step "Cloning plugin repository..."
213
+ local clone_dir="$INSTALL_DIR"
214
+
215
+ if [ -d "$clone_dir" ] && [ "$FORCE" = true ]; then
216
+ print_warning "Removing existing installation..."
217
+ rm -rf "$clone_dir"
218
+ elif [ -d "$clone_dir" ]; then
219
+ print_warning "Plugin already installed at: $clone_dir"
220
+ if [ "$NON_INTERACTIVE" = true ]; then
221
+ print_error "Use --force to overwrite in non-interactive mode"
222
+ exit 1
223
+ fi
224
+ read -p "Overwrite? [y/N] " -r; echo
225
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
226
+ rm -rf "$clone_dir"
227
+ else
228
+ print_info "Installation cancelled"
229
+ exit 0
230
+ fi
231
+ fi
232
+
233
+ mkdir -p "$clone_dir"
234
+ git clone --depth 1 --branch "$BRANCH" "$REPO" "$clone_dir" --quiet
235
+
236
+ print_success "Repository cloned to: $clone_dir"
237
+
238
+ print_step "Installing dependencies..."
239
+ cd "$clone_dir"
240
+ npm install --omit=dev --silent
241
+ print_success "Dependencies installed"
242
+ }
243
+
244
+ install_via_npm() {
245
+ print_step "Installing via npm..."
246
+ local npm_package="@forloop/opencode-plugin-planner"
247
+
248
+ if npm install "$npm_package" --prefix "$INSTALL_DIR" --silent 2>/dev/null; then
249
+ print_success "npm package installed: $npm_package"
250
+ else
251
+ print_error "npm install failed. Is the package published?"
252
+ echo " Try: npm install $npm_package (manually)"
253
+ echo " Or run without --npm to use git clone instead"
254
+ exit 1
255
+ fi
256
+ }
257
+
258
+ # ── Config merging ───────────────────────────────────────────────────────────
259
+
260
+ get_config_file() {
261
+ if [ "$INSTALL_TYPE" = "global" ]; then
262
+ mkdir -p "$HOME/.config/opencode"
263
+ echo "$HOME/.config/opencode/config.json"
264
+ else
265
+ echo "$(pwd)/opencode.json"
266
+ fi
267
+ }
268
+
269
+ merge_plugin_into_config() {
270
+ local config_file
271
+ config_file="$(get_config_file)"
272
+ local plugin_path="file://$INSTALL_DIR"
273
+
274
+ print_step "Updating OpenCode configuration..."
275
+
276
+ if [ ! -f "$config_file" ]; then
277
+ echo "{}" > "$config_file"
278
+ fi
279
+
280
+ local backup="${config_file}.backup.$(date +%Y%m%d%H%M%S)"
281
+ cp "$config_file" "$backup"
282
+
283
+ local tmp
284
+ tmp="$(mktemp)"
285
+
286
+ jq --arg p "$plugin_path" '
287
+ if .plugin then
288
+ .plugin += [$p] | .plugin |= unique
289
+ else
290
+ . + {"plugin": [$p]}
291
+ end
292
+ ' "$config_file" > "$tmp"
293
+
294
+ if [ -s "$tmp" ]; then
295
+ mv "$tmp" "$config_file"
296
+ print_success "Configuration updated: $config_file"
297
+ print_info "Backup saved: $backup"
298
+ else
299
+ print_error "Config merge produced empty file — restoring backup"
300
+ mv "$backup" "$config_file"
301
+ rm -f "$tmp"
302
+ exit 1
303
+ fi
304
+ }
305
+
306
+ # ── Token setup ──────────────────────────────────────────────────────────────
307
+
308
+ setup_token() {
309
+ echo ""
310
+ print_step "Setting up API token..."
311
+ echo " Create a token at: https://forloop.cc/profile?tab=api-tokens"
312
+ echo " Scopes needed: sprint:read, sprint:write, story:read, story:write, agent:query, profile:read"
313
+ echo ""
314
+
315
+ if [ "$NON_INTERACTIVE" = true ]; then
316
+ print_info "Non-interactive mode — set your token later:"
317
+ echo " opencode run 'forloopTokenSet --token floop_YOUR_TOKEN'"
318
+ return
319
+ fi
320
+
321
+ read -p "Have you created a token? [y/N] " -r; echo
322
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
323
+ print_info "Set it later: opencode run 'forloopTokenSet --token floop_YOUR_TOKEN'"
324
+ return
325
+ fi
326
+
327
+ read -p "Enter your token: " -s TOKEN; echo
328
+ if [ -z "$TOKEN" ]; then
329
+ print_info "No token entered, skipping"
330
+ return
331
+ fi
332
+
333
+ local token_dir="$HOME/.config/forloop"
334
+ mkdir -p "$token_dir"
335
+ cat > "$token_dir/tokens.json" << TOKENEOF
336
+ {
337
+ "version": "1.0",
338
+ "note": "ForLoop API tokens — do not share",
339
+ "default": "$TOKEN",
340
+ "lastUpdated": "$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")"
341
+ }
342
+ TOKENEOF
343
+ chmod 600 "$token_dir/tokens.json"
344
+ print_success "Token saved to: $token_dir/tokens.json"
345
+ }
346
+
347
+ # ── Verification ─────────────────────────────────────────────────────────────
348
+
349
+ verify_installation() {
350
+ print_step "Verifying installation..."
351
+ local entry="$INSTALL_DIR/$ENTRY_POINT"
352
+
353
+ if [ -f "$entry" ]; then
354
+ print_success "Plugin verified: $entry"
355
+ else
356
+ print_error "Installation failed: entry point not found at $entry"
357
+ echo " Expected: $entry"
358
+ echo " Check the installation directory: $INSTALL_DIR"
359
+ exit 1
360
+ fi
361
+ }
362
+
363
+ show_summary() {
364
+ echo ""
365
+ echo -e "${GREEN}${BOLD}========================================${NC}"
366
+ echo -e "${GREEN}${BOLD} Installation Complete!${NC}"
367
+ echo -e "${GREEN}${BOLD}========================================${NC}"
368
+ echo ""
369
+ echo " Type: $INSTALL_TYPE"
370
+ echo " Location: $INSTALL_DIR"
371
+ echo ""
372
+ echo " Next steps:"
373
+ echo " 1. Start opencode: opencode"
374
+ echo " 2. List sprints: forloopSprintList"
375
+ echo " 3. Create a story: forloopStoryCreate --title 'My feature'"
376
+ echo ""
377
+ echo " Docs: https://github.com/forloop-cc/forloop-opencode-plugin-planner"
378
+ echo ""
379
+ }
380
+
381
+ # ── Main ─────────────────────────────────────────────────────────────────────
382
+
383
+ # Parse CLI flags
384
+ while [[ $# -gt 0 ]]; do
385
+ case "$1" in
386
+ -g|--global) INSTALL_TYPE="global"; shift ;;
387
+ -l|--local) INSTALL_TYPE="local"; shift ;;
388
+ -d|--dir)
389
+ [ -z "${2:-}" ] && { print_error "--dir requires a path"; exit 1; }
390
+ INSTALL_TYPE="custom"; INSTALL_DIR="$2"; shift 2 ;;
391
+ -n|--npm) USE_NPM=true; shift ;;
392
+ -b|--branch)
393
+ [ -z "${2:-}" ] && { print_error "--branch requires a name"; exit 1; }
394
+ BRANCH="$2"; shift 2 ;;
395
+ -f|--force) FORCE=true; shift ;;
396
+ -h|--help) show_help; exit 0 ;;
397
+ *) print_error "Unknown option: $1"; show_help; exit 1 ;;
398
+ esac
399
+ done
400
+
401
+ # Detect non-interactive mode (stdin is a pipe, not terminal)
402
+ if [ ! -t 0 ]; then
403
+ NON_INTERACTIVE=true
404
+ if [ -z "$INSTALL_TYPE" ]; then
405
+ print_info "Running in non-interactive mode, defaulting to local install"
406
+ INSTALL_TYPE="local"
407
+ fi
408
+ fi
409
+
410
+ print_header
411
+
412
+ # Interactive: prompt for install type if not given
413
+ if [ -z "$INSTALL_TYPE" ] && [ "$NON_INTERACTIVE" != true ]; then
414
+ prompt_install_type
415
+ fi
416
+ [ -z "$INSTALL_TYPE" ] && { print_error "Installation type required"; exit 1; }
417
+
418
+ resolve_install_dir
419
+ check_all_deps
420
+ check_opencode
421
+
422
+ if [ "$USE_NPM" = true ]; then
423
+ install_via_npm
424
+ else
425
+ install_via_git
426
+ fi
427
+
428
+ merge_plugin_into_config
429
+ setup_token
430
+ verify_installation
431
+ show_summary
432
+ ```
433
+
434
+ - [ ] **Step 2: Set executable permissions**
435
+
436
+ ```bash
437
+ chmod +x forloop-opencode-plugin-planner/install.sh
438
+ ```
439
+
440
+ - [ ] **Step 3: Dry-run test — check the script is valid bash**
441
+
442
+ ```bash
443
+ bash -n forloop-opencode-plugin-planner/install.sh
444
+ ```
445
+
446
+ Expected: No syntax errors.
447
+
448
+ - [ ] **Step 4: Test --help flag**
449
+
450
+ ```bash
451
+ bash forloop-opencode-plugin-planner/install.sh --help
452
+ ```
453
+
454
+ Expected: Help text printed with all flags listed.
455
+
456
+ - [ ] **Step 5: Commit**
457
+
458
+ ```bash
459
+ git add forloop-opencode-plugin-planner/install.sh
460
+ git commit -m "feat: rewrite install.sh with npm/git support, jq config merge, non-interactive mode"
461
+ ```
462
+
463
+ ---
464
+
465
+ ### Task 2: Rewrite `update.sh`
466
+
467
+ **Files:**
468
+ - Rewrite: `forloop-opencode-plugin-planner/update.sh`
469
+
470
+ - [ ] **Step 1: Write the update.sh script**
471
+
472
+ ```bash
473
+ #!/usr/bin/env bash
474
+ set -euo pipefail
475
+
476
+ # ForLoop Plugin Updater
477
+ # https://github.com/forloop-cc/forloop-opencode-plugin-planner
478
+ #
479
+ # Usage:
480
+ # curl -fsSL https://raw.githubusercontent.com/forloop-cc/forloop-opencode-plugin-planner/main/update.sh | bash
481
+ # bash update.sh --force --changelog
482
+ # bash update.sh --dir ~/my-custom-install
483
+
484
+ # ── Colors ───────────────────────────────────────────────────────────────────
485
+ PLATFORM="$(uname -s)"
486
+ if [[ "$PLATFORM" =~ MINGW|MSYS|CYGWIN ]] && [ -z "$WT_SESSION" ] && [ -z "$ConEmuPID" ]; then
487
+ RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; BOLD=''; NC=''
488
+ else
489
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
490
+ BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
491
+ fi
492
+
493
+ # ── Configuration ────────────────────────────────────────────────────────────
494
+ PLUGIN_DIRNAME="forloop-planner"
495
+ GLOBAL_INSTALL_DIR="$HOME/.config/opencode/plugins/$PLUGIN_DIRNAME"
496
+ LOCAL_INSTALL_DIR=".opencode/plugins/$PLUGIN_DIRNAME"
497
+
498
+ PLUGIN_DIR=""
499
+ FORCE=false
500
+ SHOW_CHANGELOG=false
501
+
502
+ print_success() { echo -e "${GREEN}✓${NC} $1"; }
503
+ print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
504
+ print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
505
+ print_error() { echo -e "${RED}✗${NC} $1" >&2; }
506
+ print_step() { echo -e "\n${CYAN}${BOLD}▶${NC} $1\n"; }
507
+
508
+ show_help() {
509
+ cat << EOF
510
+ Usage: update.sh [OPTIONS]
511
+
512
+ Options:
513
+ -d, --dir PATH Update a specific install directory
514
+ -f, --force Pull even if already up to date
515
+ -c, --changelog Show recent changelog after update
516
+ -h, --help Show this help
517
+
518
+ Auto-detection order:
519
+ 1. --dir flag
520
+ 2. FORLOOP_PLUGIN_DIR env var
521
+ 3. ./.opencode/plugins/forloop-planner/
522
+ 4. ~/.config/opencode/plugins/forloop-planner/
523
+ EOF
524
+ }
525
+
526
+ # ── Auto-detection ───────────────────────────────────────────────────────────
527
+
528
+ find_installation() {
529
+ # 1. Check env var
530
+ if [ -n "${FORLOOP_PLUGIN_DIR:-}" ] && [ -d "$FORLOOP_PLUGIN_DIR/.git" ]; then
531
+ PLUGIN_DIR="$FORLOOP_PLUGIN_DIR"
532
+ print_info "Found via FORLOOP_PLUGIN_DIR: $PLUGIN_DIR"
533
+ return 0
534
+ fi
535
+
536
+ # 2. Check local
537
+ if [ -d "$LOCAL_INSTALL_DIR/.git" ]; then
538
+ PLUGIN_DIR="$LOCAL_INSTALL_DIR"
539
+ print_info "Found local install: $PLUGIN_DIR"
540
+ return 0
541
+ fi
542
+
543
+ # 3. Check global
544
+ if [ -d "$GLOBAL_INSTALL_DIR/.git" ]; then
545
+ PLUGIN_DIR="$GLOBAL_INSTALL_DIR"
546
+ print_info "Found global install: $PLUGIN_DIR"
547
+ return 0
548
+ fi
549
+
550
+ print_error "No ForLoop plugin installation found"
551
+ echo ""
552
+ echo " Checked:"
553
+ echo " 1. FORLOOP_PLUGIN_DIR env var"
554
+ echo " 2. $LOCAL_INSTALL_DIR"
555
+ echo " 3. $GLOBAL_INSTALL_DIR"
556
+ echo ""
557
+ echo " Specify location: bash update.sh --dir <path>"
558
+ echo " Or install first: curl -fsSL .../install.sh | bash"
559
+ return 1
560
+ }
561
+
562
+ # ── Update logic ─────────────────────────────────────────────────────────────
563
+
564
+ update_via_git() {
565
+ print_step "Checking for updates..."
566
+ cd "$PLUGIN_DIR"
567
+
568
+ local current
569
+ current="$(git rev-parse --short HEAD)"
570
+
571
+ # Ensure on main branch
572
+ local branch
573
+ branch="$(git rev-parse --abbrev-ref HEAD)"
574
+ if [ "$branch" != "main" ] && [ "$branch" != "master" ]; then
575
+ print_info "Switching from '$branch' to main"
576
+ git checkout main --quiet 2>/dev/null || git checkout master --quiet 2>/dev/null || true
577
+ fi
578
+
579
+ git fetch origin --quiet
580
+
581
+ local remote
582
+ remote="$(git rev-parse --short origin/main 2>/dev/null || git rev-parse --short origin/master 2>/dev/null)"
583
+
584
+ if [ "$current" = "$remote" ] && [ "$FORCE" != true ]; then
585
+ print_success "Already up to date ($current)"
586
+ return 0
587
+ fi
588
+
589
+ print_info "Updating: $current → $remote"
590
+ git pull --quiet origin main 2>/dev/null || git pull --quiet origin master 2>/dev/null
591
+
592
+ # Reinstall dependencies
593
+ print_step "Installing dependencies..."
594
+ npm install --omit=dev --silent
595
+ print_success "Dependencies updated"
596
+
597
+ local new_ver
598
+ new_ver="$(git rev-parse --short HEAD)"
599
+ print_success "Updated to: $new_ver"
600
+ }
601
+
602
+ update_via_npm() {
603
+ print_step "Updating via npm..."
604
+ cd "$PLUGIN_DIR"
605
+
606
+ local before
607
+ before="$(npm list @forloop/opencode-plugin-planner --depth=0 2>/dev/null || echo 'unknown')"
608
+
609
+ npm update @forloop/opencode-plugin-planner --silent
610
+
611
+ local after
612
+ after="$(npm list @forloop/opencode-plugin-planner --depth=0 2>/dev/null || echo 'updated')"
613
+
614
+ print_success "$after"
615
+ }
616
+
617
+ # ── Changelog ────────────────────────────────────────────────────────────────
618
+
619
+ show_recent_changelog() {
620
+ local changelog="$PLUGIN_DIR/CHANGELOG.md"
621
+ if [ -f "$changelog" ]; then
622
+ echo ""
623
+ print_info "Recent changes:"
624
+ echo "────────────────────────────────────────────"
625
+ head -60 "$changelog" | head -40
626
+ echo "────────────────────────────────────────────"
627
+ else
628
+ print_warning "No CHANGELOG.md found"
629
+ fi
630
+ }
631
+
632
+ # ── Verification ─────────────────────────────────────────────────────────────
633
+
634
+ verify_update() {
635
+ local entry="$PLUGIN_DIR/plugins/forloop-plugin.ts"
636
+ if [ -f "$entry" ]; then
637
+ print_success "Plugin verified: $entry"
638
+ else
639
+ print_error "Update may have failed — entry point not found: $entry"
640
+ exit 1
641
+ fi
642
+ }
643
+
644
+ # ── Main ─────────────────────────────────────────────────────────────────────
645
+
646
+ while [[ $# -gt 0 ]]; do
647
+ case "$1" in
648
+ -d|--dir)
649
+ [ -z "${2:-}" ] && { print_error "--dir requires a path"; exit 1; }
650
+ PLUGIN_DIR="$2"; shift 2 ;;
651
+ -f|--force) FORCE=true; shift ;;
652
+ -c|--changelog) SHOW_CHANGELOG=true; shift ;;
653
+ -h|--help) show_help; exit 0 ;;
654
+ *) print_error "Unknown option: $1"; show_help; exit 1 ;;
655
+ esac
656
+ done
657
+
658
+ echo ""
659
+ echo -e "${BLUE}${BOLD}╔══════════════════════════════════════════╗"
660
+ echo -e "║ ForLoop Plugin Updater ║"
661
+ echo -e "╚══════════════════════════════════════════╝${NC}"
662
+ echo ""
663
+
664
+ if [ -z "$PLUGIN_DIR" ]; then
665
+ find_installation || exit 1
666
+ fi
667
+
668
+ if [ ! -d "$PLUGIN_DIR" ]; then
669
+ print_error "Plugin directory not found: $PLUGIN_DIR"
670
+ exit 1
671
+ fi
672
+
673
+ # Detect install method
674
+ if [ -d "$PLUGIN_DIR/.git" ]; then
675
+ update_via_git
676
+ elif [ -f "$PLUGIN_DIR/package.json" ]; then
677
+ update_via_npm
678
+ else
679
+ print_error "Not a valid ForLoop plugin installation: $PLUGIN_DIR"
680
+ exit 1
681
+ fi
682
+
683
+ if [ "$SHOW_CHANGELOG" = true ]; then
684
+ show_recent_changelog
685
+ fi
686
+
687
+ verify_update
688
+
689
+ echo ""
690
+ print_success "Update complete!"
691
+ echo " Location: $PLUGIN_DIR"
692
+ echo ""
693
+ ```
694
+
695
+ - [ ] **Step 2: Set executable permissions**
696
+
697
+ ```bash
698
+ chmod +x forloop-opencode-plugin-planner/update.sh
699
+ ```
700
+
701
+ - [ ] **Step 3: Syntax check**
702
+
703
+ ```bash
704
+ bash -n forloop-opencode-plugin-planner/update.sh
705
+ ```
706
+
707
+ Expected: No syntax errors.
708
+
709
+ - [ ] **Step 4: Test --help flag**
710
+
711
+ ```bash
712
+ bash forloop-opencode-plugin-planner/update.sh --help
713
+ ```
714
+
715
+ Expected: Help text with all flags and auto-detection order.
716
+
717
+ - [ ] **Step 5: Commit**
718
+
719
+ ```bash
720
+ git add forloop-opencode-plugin-planner/update.sh
721
+ git commit -m "feat: rewrite update.sh with auto-detection, npm support, changelog display"
722
+ ```
723
+
724
+ ---
725
+
726
+ ### Task 3: Update `package.json` for npm publishing
727
+
728
+ **Files:**
729
+ - Modify: `forloop-opencode-plugin-planner/package.json`
730
+
731
+ - [ ] **Step 1: Read current package.json to verify current state**
732
+
733
+ ```bash
734
+ cat forloop-opencode-plugin-planner/package.json | jq '.name, .version, .main'
735
+ ```
736
+
737
+ Expected: Shows current name, version, main entry.
738
+
739
+ - [ ] **Step 2: Add `files` field to package.json**
740
+
741
+ Location: After the `"license"` line, before `"repository"`. The `files` field ensures only distributable files are published to npm.
742
+
743
+ Edit `forloop-opencode-plugin-planner/package.json`, adding these fields:
744
+
745
+ ```json
746
+ "main": "./plugins/forloop-plugin.ts",
747
+ "files": [
748
+ "plugins/",
749
+ "skills/",
750
+ "agents/",
751
+ "scripts/migrate-tokens.js",
752
+ "scripts/use-prod.sh",
753
+ "docs/",
754
+ "install.sh",
755
+ "update.sh",
756
+ "opencode.json",
757
+ "tsconfig.json",
758
+ "README.md",
759
+ "LICENSE",
760
+ "CHANGELOG.md",
761
+ "CODE_OF_CONDUCT.md",
762
+ "CONTRIBUTING.md",
763
+ "SECURITY.md"
764
+ ],
765
+ "engines": {
766
+ "node": ">=18.0.0"
767
+ },
768
+ ```
769
+
770
+ - [ ] **Step 3: Verify the files field with npm pack (dry run)**
771
+
772
+ ```bash
773
+ cd forloop-opencode-plugin-planner && npm pack --dry-run 2>&1 | head -30
774
+ ```
775
+
776
+ Expected: Shows only the files listed above. No test/, .github/, .gitignore, node_modules/, etc.
777
+
778
+ - [ ] **Step 4: Commit**
779
+
780
+ ```bash
781
+ git add forloop-opencode-plugin-planner/package.json
782
+ git commit -m "feat: add files field for npm publishing"
783
+ ```
784
+
785
+ ---
786
+
787
+ ### Task 4: End-to-End Verification
788
+
789
+ **Files:**
790
+ - No file changes — manual testing run
791
+
792
+ - [ ] **Step 1: Create a temporary test directory**
793
+
794
+ ```bash
795
+ mkdir -p /tmp/forloop-install-test && cd /tmp/forloop-install-test
796
+ ```
797
+
798
+ - [ ] **Step 2: Test local install (simulating curl-pipe)**
799
+
800
+ ```bash
801
+ bash /Users/toshilam/Documents/projects/phase4-opencode-plugin-planner/forloop-opencode-plugin-planner/install.sh --local --force
802
+ ```
803
+
804
+ Expected:
805
+ - Clones repo to `./.opencode/plugins/forloop-planner/`
806
+ - Runs `npm install --omit=dev`
807
+ - Creates `opencode.json` with plugin path
808
+ - Verifies `plugins/forloop-plugin.ts` exists
809
+ - Outputs success summary
810
+
811
+ - [ ] **Step 3: Verify the installed files**
812
+
813
+ ```bash
814
+ ls .opencode/plugins/forloop-planner/plugins/forloop-plugin.ts && echo "EXISTS"
815
+ ls .opencode/plugins/forloop-planner/agents/forLoopPlanner.md && echo "EXISTS"
816
+ ls .opencode/plugins/forloop-planner/skills/ && echo "DIR"
817
+ ls .opencode/plugins/forloop-planner/node_modules/@opencode-ai/plugin && echo "DEPS"
818
+ ```
819
+
820
+ Expected: All files and dependencies present.
821
+
822
+ - [ ] **Step 4: Verify config merge correctness**
823
+
824
+ ```bash
825
+ cat opencode.json | jq '.plugin'
826
+ ```
827
+
828
+ Expected: Contains `"file://./.opencode/plugins/forloop-planner"`.
829
+
830
+ - [ ] **Step 5: Test update (no changes expected)**
831
+
832
+ ```bash
833
+ bash /Users/toshilam/Documents/projects/phase4-opencode-plugin-planner/forloop-opencode-plugin-planner/update.sh --dir ./.opencode/plugins/forloop-planner
834
+ ```
835
+
836
+ Expected: "Already up to date" or similar.
837
+
838
+ - [ ] **Step 6: Test --force update**
839
+
840
+ ```bash
841
+ bash /Users/toshilam/Documents/projects/phase4-opencode-plugin-planner/forloop-opencode-plugin-planner/update.sh --dir ./.opencode/plugins/forloop-planner --force
842
+ ```
843
+
844
+ Expected: Runs git pull + npm install even if current.
845
+
846
+ - [ ] **Step 7: Clean up**
847
+
848
+ ```bash
849
+ rm -rf /tmp/forloop-install-test
850
+ ```