@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.
- package/CHANGELOG.md +225 -0
- package/CODE_OF_CONDUCT.md +77 -0
- package/CONTRIBUTING.md +176 -0
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/SECURITY.md +69 -0
- package/docs/CONFIGURATION.md +283 -0
- package/docs/DEVELOPER-VS-USER.md +129 -0
- package/docs/IMPROVED-AUTHENTICATION.md +317 -0
- package/docs/INSTALLATION-SCRIPTS.md +304 -0
- package/docs/PLANNER-AGENT-IMPROVEMENTS.md +88 -0
- package/docs/PLUGIN-ARCHITECTURE.md +308 -0
- package/docs/QUICKSTART-NON-DEV.md +94 -0
- package/docs/README-FOR-HUMANS-AND-AI.md +109 -0
- package/docs/README.md +70 -0
- package/docs/SKILLS-GAP-ANALYSIS.md +157 -0
- package/docs/USER-INTERACTION-MODEL.md +106 -0
- package/docs/file-management/2026-04-12-forloop-aivy-documents-sync.md +395 -0
- package/docs/file-management/FILE_UPLOAD_ENHANCEMENT.md +431 -0
- package/docs/file-management/FILE_UPLOAD_FLOW.md +300 -0
- package/docs/images/forloop-opencode-flow.png +0 -0
- package/docs/images/forloop-opencode-header.png +0 -0
- package/docs/superpowers/plans/2026-05-03-sprint-isolated-directory-structure.md +1450 -0
- package/docs/superpowers/plans/2026-05-29-install-system.md +850 -0
- package/docs/superpowers/specs/2026-05-03-sprint-isolated-directory-structure-design.md +149 -0
- package/docs/superpowers/specs/2026-05-19-opencode-message-recording-design.md +468 -0
- package/docs/superpowers/specs/2026-05-29-install-system-design.md +135 -0
- package/install.sh +317 -0
- package/package.json +87 -0
- package/plugins/capabilities/api-client.ts +556 -0
- package/plugins/capabilities/auth.ts +120 -0
- package/plugins/capabilities/config.ts +195 -0
- package/plugins/capabilities/context-resolver.ts +106 -0
- package/plugins/capabilities/token-storage.ts +98 -0
- package/plugins/forloop-plugin.ts +186 -0
- package/plugins/hooks/message-recorder.ts +258 -0
- package/plugins/hooks/shell-env.ts +62 -0
- package/plugins/tools/agent-sprint-tools.ts +100 -0
- package/plugins/tools/agent-suggestion-tools.ts +275 -0
- package/plugins/tools/agent-tools.ts +114 -0
- package/plugins/tools/ai-trigger-tools.ts +79 -0
- package/plugins/tools/developer-status-tool.ts +97 -0
- package/plugins/tools/file-tools.ts +240 -0
- package/plugins/tools/folder-tools.ts +75 -0
- package/plugins/tools/organization-tools.ts +213 -0
- package/plugins/tools/schedule-tools.ts +173 -0
- package/plugins/tools/sprint-tools.ts +287 -0
- package/plugins/tools/story-tools.ts +320 -0
- package/plugins/tools/sync-tools.ts +509 -0
- package/plugins/tools/template-tools.ts +170 -0
- package/plugins/tools/user-tools.ts +146 -0
- package/scripts/migrate-tokens.js +90 -0
- package/scripts/use-prod.sh +11 -0
- package/tsconfig.json +17 -0
- package/update.sh +172 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin';
|
|
2
|
+
import { ForLoopAPIClient } from '../capabilities/api-client';
|
|
3
|
+
import { validateToken } from '../capabilities/auth';
|
|
4
|
+
|
|
5
|
+
export function createUserProfileTool(client: ForLoopAPIClient) {
|
|
6
|
+
return tool({
|
|
7
|
+
description: 'Get current user profile information (name, email, tier)',
|
|
8
|
+
args: {},
|
|
9
|
+
async execute(_args, _context) {
|
|
10
|
+
const tokenResult = await validateToken();
|
|
11
|
+
if (!tokenResult.valid) {
|
|
12
|
+
return tokenResult.error;
|
|
13
|
+
}
|
|
14
|
+
client.setToken?.(tokenResult.token);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const profile = await client.getUserProfile();
|
|
18
|
+
|
|
19
|
+
const lines: string[] = [
|
|
20
|
+
`👤 User Profile`,
|
|
21
|
+
'',
|
|
22
|
+
`**Name**: ${profile.name || 'Not set'}`,
|
|
23
|
+
`**Email**: ${profile.email}`,
|
|
24
|
+
`**Tier**: ${profile.tier || 'free'}`,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
if (profile.bio) {
|
|
28
|
+
lines.push(`**Bio**: ${profile.bio}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (profile.avatarUrl) {
|
|
32
|
+
lines.push(`**Avatar**: ${profile.avatarUrl}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return lines.join('\n');
|
|
36
|
+
} catch (error: any) {
|
|
37
|
+
return `❌ Error: ${error.message}`;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createUserQuotasTool(client: ForLoopAPIClient) {
|
|
44
|
+
return tool({
|
|
45
|
+
description: 'Check user quota limits and usage (organizations, sprints, storage)',
|
|
46
|
+
args: {},
|
|
47
|
+
async execute(_args, _context) {
|
|
48
|
+
const tokenResult = await validateToken();
|
|
49
|
+
if (!tokenResult.valid) {
|
|
50
|
+
return tokenResult.error;
|
|
51
|
+
}
|
|
52
|
+
client.setToken?.(tokenResult.token);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const quotas = await client.getUserQuotas();
|
|
56
|
+
|
|
57
|
+
const formatBytes = (bytes: string) => {
|
|
58
|
+
const num = parseInt(bytes, 10);
|
|
59
|
+
if (num === 0) return '0 Bytes';
|
|
60
|
+
const k = 1024;
|
|
61
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
62
|
+
const i = Math.floor(Math.log(num) / Math.log(k));
|
|
63
|
+
return parseFloat((num / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const lines: string[] = [
|
|
67
|
+
`📊 Quota Usage (${quotas.tier || 'free'} tier)`,
|
|
68
|
+
'',
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
// Organizations
|
|
72
|
+
lines.push(`**Organizations**: ${quotas.remaining?.ownedOrganizations ?? '?'}/${quotas.limits?.maxOwnedOrganizations ?? '?'}`);
|
|
73
|
+
|
|
74
|
+
// System Sprints
|
|
75
|
+
lines.push(`**System Sprints**: ${quotas.remaining?.systemSpaces ?? '?'}/${quotas.limits?.maxSystemSprints ?? '?'}`);
|
|
76
|
+
|
|
77
|
+
// Storage
|
|
78
|
+
const storageUsed = formatBytes(quotas.usage?.storageUsedBytes || '0');
|
|
79
|
+
const storageLimit = formatBytes(quotas.limits?.maxStorageBytes || '0');
|
|
80
|
+
lines.push(`**Storage**: ${storageUsed} / ${storageLimit}`);
|
|
81
|
+
|
|
82
|
+
// Free tier stories
|
|
83
|
+
if (quotas.tier === 'free') {
|
|
84
|
+
lines.push(`**Free Stories**: ${quotas.remaining?.freeStories ?? '?'}/${quotas.limits?.maxStoriesPerFreeSystemSprint ?? '?'}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Owned organizations usage
|
|
88
|
+
if (quotas.ownedOrgsUsage && quotas.ownedOrgsUsage.length > 0) {
|
|
89
|
+
lines.push('');
|
|
90
|
+
lines.push('**Owned Organizations:**');
|
|
91
|
+
for (const org of quotas.ownedOrgsUsage) {
|
|
92
|
+
lines.push(` - ${org.name}: ${org.spacesUsed}/${org.spacesLimit} sprints`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return lines.join('\n');
|
|
97
|
+
} catch (error: any) {
|
|
98
|
+
return `❌ Error: ${error.message}`;
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function createOrganizationQuotasTool(client: ForLoopAPIClient) {
|
|
105
|
+
return tool({
|
|
106
|
+
description: 'Get quota information for a specific organization',
|
|
107
|
+
args: {
|
|
108
|
+
organizationId: tool.schema.number()
|
|
109
|
+
.describe('Organization ID to check quotas for'),
|
|
110
|
+
},
|
|
111
|
+
async execute(args, _context) {
|
|
112
|
+
const tokenResult = await validateToken();
|
|
113
|
+
if (!tokenResult.valid) {
|
|
114
|
+
return tokenResult.error;
|
|
115
|
+
}
|
|
116
|
+
client.setToken?.(tokenResult.token);
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const quotas = await client.getOrganizationQuotas(args.organizationId);
|
|
120
|
+
|
|
121
|
+
const lines: string[] = [
|
|
122
|
+
`📊 Organization Quotas`,
|
|
123
|
+
'',
|
|
124
|
+
`**Organization**: ${quotas.organization?.name || `#${quotas.organization?.id}`}`,
|
|
125
|
+
`**Type**: ${quotas.organization?.type || 'user'}`,
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
if (quotas.limits) {
|
|
129
|
+
lines.push('');
|
|
130
|
+
lines.push('**Limits:**');
|
|
131
|
+
lines.push(` - Storage: ${parseInt(quotas.limits.maxStorageBytes).toLocaleString()} bytes`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (quotas.usage) {
|
|
135
|
+
lines.push('');
|
|
136
|
+
lines.push('**Usage:**');
|
|
137
|
+
lines.push(` - Sprints: ${quotas.usage.spacesUsed}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return lines.join('\n');
|
|
141
|
+
} catch (error: any) {
|
|
142
|
+
return `❌ Error: ${error.message}`;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ForLoop Token Migration Script
|
|
4
|
+
*
|
|
5
|
+
* Migrates tokens from old location (~/.config/opencode/tokens.json)
|
|
6
|
+
* to new location (~/.config/forloop/tokens.json)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { promises as fs } from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
|
|
15
|
+
const OLD_TOKEN_PATH = path.join(
|
|
16
|
+
process.env.HOME || process.env.USERPROFILE || '.',
|
|
17
|
+
'.config',
|
|
18
|
+
'opencode',
|
|
19
|
+
'tokens.json'
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const NEW_TOKEN_PATH = path.join(
|
|
23
|
+
process.env.HOME || process.env.USERPROFILE || '.',
|
|
24
|
+
'.config',
|
|
25
|
+
'forloop',
|
|
26
|
+
'tokens.json'
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
async function migrate() {
|
|
30
|
+
console.log('🔄 ForLoop Token Migration');
|
|
31
|
+
console.log('=========================\n');
|
|
32
|
+
|
|
33
|
+
// Check if old file exists
|
|
34
|
+
try {
|
|
35
|
+
await fs.access(OLD_TOKEN_PATH);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error.code === 'ENOENT') {
|
|
38
|
+
console.log('✅ No old token file found - nothing to migrate');
|
|
39
|
+
console.log(`\nTokens will be stored at: ${NEW_TOKEN_PATH}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log(`📂 Old location: ${OLD_TOKEN_PATH}`);
|
|
46
|
+
console.log(`📂 New location: ${NEW_TOKEN_PATH}`);
|
|
47
|
+
console.log('');
|
|
48
|
+
|
|
49
|
+
// Read old tokens
|
|
50
|
+
const content = await fs.readFile(OLD_TOKEN_PATH, 'utf-8');
|
|
51
|
+
const oldTokens = JSON.parse(content);
|
|
52
|
+
|
|
53
|
+
// Create new token structure
|
|
54
|
+
const newTokens = {
|
|
55
|
+
version: '1.0',
|
|
56
|
+
note: 'ForLoop API tokens - do not share',
|
|
57
|
+
default: oldTokens.default,
|
|
58
|
+
profiles: oldTokens.profiles || {},
|
|
59
|
+
lastUpdated: oldTokens.lastUpdated || new Date().toISOString(),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Create new directory
|
|
63
|
+
const newDir = path.dirname(NEW_TOKEN_PATH);
|
|
64
|
+
await fs.mkdir(newDir, { recursive: true });
|
|
65
|
+
|
|
66
|
+
// Write new file with secure permissions
|
|
67
|
+
await fs.writeFile(NEW_TOKEN_PATH, JSON.stringify(newTokens, null, 2), {
|
|
68
|
+
mode: 0o600, // Owner read/write only
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
console.log('✅ Migration successful!');
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log('📊 Migrated:');
|
|
74
|
+
if (newTokens.default) {
|
|
75
|
+
const masked = newTokens.default.slice(0, 10) + '...';
|
|
76
|
+
console.log(` - Default token: ${masked}`);
|
|
77
|
+
}
|
|
78
|
+
if (newTokens.profiles && Object.keys(newTokens.profiles).length > 0) {
|
|
79
|
+
console.log(` - Profiles: ${Object.keys(newTokens.profiles).join(', ')}`);
|
|
80
|
+
}
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log('📝 You can safely delete the old file:');
|
|
83
|
+
console.log(` rm ${OLD_TOKEN_PATH}`);
|
|
84
|
+
console.log('');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
migrate().catch(error => {
|
|
88
|
+
console.error('❌ Migration failed:', error.message);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Quick script to switch to production environment
|
|
3
|
+
|
|
4
|
+
rm -f .env
|
|
5
|
+
|
|
6
|
+
echo "✅ Production environment configured"
|
|
7
|
+
echo ""
|
|
8
|
+
echo "API URL: https://api.forloop.cc (default)"
|
|
9
|
+
echo ""
|
|
10
|
+
echo "To test: opencode"
|
|
11
|
+
echo "Or restart opencode to use default config"
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": ".",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"allowSyntheticDefaultImports": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["./plugins/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|
package/update.sh
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# ForLoop Plugin Updater
|
|
5
|
+
# https://github.com/forloop-cc/forloop-opencode-plugin-planner
|
|
6
|
+
#
|
|
7
|
+
# opencode auto-updates plugins on startup. This script verifies the plugin
|
|
8
|
+
# is registered and shows the latest changelog.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# bash update.sh
|
|
12
|
+
# bash update.sh --changelog
|
|
13
|
+
|
|
14
|
+
# ── Colors ───────────────────────────────────────────────────────────────────
|
|
15
|
+
PLATFORM="$(uname -s)"
|
|
16
|
+
if [[ "$PLATFORM" =~ MINGW|MSYS|CYGWIN ]] && [ -z "$WT_SESSION" ] && [ -z "$ConEmuPID" ]; then
|
|
17
|
+
RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; BOLD=''; NC=''
|
|
18
|
+
else
|
|
19
|
+
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
|
20
|
+
BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# ── Configuration ────────────────────────────────────────────────────────────
|
|
24
|
+
NPM_PACKAGE="@forloop-cc/forloop-opencode-plugin-planner"
|
|
25
|
+
GIT_URL="forloop-planner@git+https://github.com/forloop-cc/forloop-opencode-plugin-planner.git"
|
|
26
|
+
PLUGIN_DIRNAME="forloop-planner"
|
|
27
|
+
|
|
28
|
+
SHOW_CHANGELOG=false
|
|
29
|
+
|
|
30
|
+
print_success() { echo -e "${GREEN}✓${NC} $1"; }
|
|
31
|
+
print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
|
|
32
|
+
print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
|
|
33
|
+
print_error() { echo -e "${RED}✗${NC} $1" >&2; }
|
|
34
|
+
|
|
35
|
+
show_help() {
|
|
36
|
+
cat << 'HELPEOF'
|
|
37
|
+
Usage: update.sh [OPTIONS]
|
|
38
|
+
|
|
39
|
+
opencode auto-updates plugins from git/npm on startup. This script:
|
|
40
|
+
- Verifies the plugin is registered in opencode.json
|
|
41
|
+
- Shows the latest changelog from the plugin source
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
-c, --changelog Show recent changelog after verification
|
|
45
|
+
-h, --help Show this help
|
|
46
|
+
|
|
47
|
+
Auto-detection order:
|
|
48
|
+
1. ./opencode.json (project config)
|
|
49
|
+
2. ~/.config/opencode/opencode.json (global config)
|
|
50
|
+
HELPEOF
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# ── Dependency checks ────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
check_jq() {
|
|
56
|
+
if ! command -v jq &>/dev/null; then
|
|
57
|
+
print_error "jq is required but not installed"
|
|
58
|
+
return 1
|
|
59
|
+
fi
|
|
60
|
+
return 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# ── Config lookup ────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
find_config() {
|
|
66
|
+
local candidates=("./opencode.json" "$HOME/.config/opencode/opencode.json")
|
|
67
|
+
for config in "${candidates[@]}"; do
|
|
68
|
+
if [ -f "$config" ]; then
|
|
69
|
+
echo "$config"
|
|
70
|
+
return 0
|
|
71
|
+
fi
|
|
72
|
+
done
|
|
73
|
+
return 1
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
find_plugin_entry() {
|
|
77
|
+
local config_file="$1"
|
|
78
|
+
jq -r '.plugin[]? // empty' "$config_file" | grep -E "forloop-opencode-plugin-planner|$NPM_PACKAGE" | head -1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# ── Changelog ────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
show_changelog_from_repo() {
|
|
84
|
+
local changelog_url="https://raw.githubusercontent.com/forloop-cc/forloop-opencode-plugin-planner/main/CHANGELOG.md"
|
|
85
|
+
echo ""
|
|
86
|
+
print_info "Recent changes:"
|
|
87
|
+
echo "────────────────────────────────────────────"
|
|
88
|
+
curl -fsSL "$changelog_url" 2>/dev/null | head -40 || {
|
|
89
|
+
echo " (could not fetch changelog — check https://github.com/forloop-cc/forloop-opencode-plugin-planner/releases)"
|
|
90
|
+
}
|
|
91
|
+
echo "────────────────────────────────────────────"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
show_changelog_from_cache() {
|
|
95
|
+
local cache_dir="$HOME/.cache/opencode/node_modules"
|
|
96
|
+
local plugin_paths=(
|
|
97
|
+
"$cache_dir/$NPM_PACKAGE/CHANGELOG.md"
|
|
98
|
+
"$cache_dir/$NPM_PACKAGE/plugins/forloop-plugin.ts"
|
|
99
|
+
)
|
|
100
|
+
for candidate in "${plugin_paths[@]}"; do
|
|
101
|
+
local changelog="$(dirname "$candidate")/CHANGELOG.md"
|
|
102
|
+
if [ -f "$changelog" ]; then
|
|
103
|
+
echo ""
|
|
104
|
+
print_info "Recent changes:"
|
|
105
|
+
echo "────────────────────────────────────────────"
|
|
106
|
+
head -40 "$changelog"
|
|
107
|
+
echo "────────────────────────────────────────────"
|
|
108
|
+
return 0
|
|
109
|
+
fi
|
|
110
|
+
done
|
|
111
|
+
return 1
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# ── Main ─────────────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
while [[ $# -gt 0 ]]; do
|
|
117
|
+
case "$1" in
|
|
118
|
+
-c|--changelog) SHOW_CHANGELOG=true; shift ;;
|
|
119
|
+
-h|--help) show_help; exit 0 ;;
|
|
120
|
+
*) print_error "Unknown option: $1"; show_help; exit 1 ;;
|
|
121
|
+
esac
|
|
122
|
+
done
|
|
123
|
+
|
|
124
|
+
echo ""
|
|
125
|
+
echo -e "${BLUE}${BOLD}╔══════════════════════════════════════════╗"
|
|
126
|
+
echo -e "║ ForLoop Plugin Updater ║"
|
|
127
|
+
echo -e "╚══════════════════════════════════════════╝${NC}"
|
|
128
|
+
echo ""
|
|
129
|
+
|
|
130
|
+
check_jq || exit 1
|
|
131
|
+
|
|
132
|
+
local config_file
|
|
133
|
+
config_file="$(find_config)" || {
|
|
134
|
+
print_error "No opencode.json found"
|
|
135
|
+
echo ""
|
|
136
|
+
echo " Checked:"
|
|
137
|
+
echo " 1. ./opencode.json"
|
|
138
|
+
echo " 2. ~/.config/opencode/opencode.json"
|
|
139
|
+
echo ""
|
|
140
|
+
echo " Install first: curl -fsSL .../install.sh | bash"
|
|
141
|
+
exit 1
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
local plugin_entry
|
|
145
|
+
plugin_entry="$(find_plugin_entry "$config_file")"
|
|
146
|
+
|
|
147
|
+
if [ -z "$plugin_entry" ]; then
|
|
148
|
+
print_error "ForLoop plugin not registered in $config_file"
|
|
149
|
+
echo ""
|
|
150
|
+
echo " Install first: curl -fsSL .../install.sh | bash"
|
|
151
|
+
exit 1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
print_success "Plugin registered in: $config_file"
|
|
155
|
+
print_info "Entry: $plugin_entry"
|
|
156
|
+
echo ""
|
|
157
|
+
|
|
158
|
+
# Detect source type
|
|
159
|
+
if echo "$plugin_entry" | grep -q "git+https://"; then
|
|
160
|
+
print_info "Source: Git (opencode will pull latest on startup)"
|
|
161
|
+
elif echo "$plugin_entry" | grep -q "@forloop-cc/"; then
|
|
162
|
+
print_info "Source: npm (opencode will check for updates on startup)"
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
echo ""
|
|
166
|
+
print_success "To update: restart opencode — it auto-fetches the latest plugin version"
|
|
167
|
+
|
|
168
|
+
if [ "$SHOW_CHANGELOG" = true ]; then
|
|
169
|
+
show_changelog_from_cache || show_changelog_from_repo
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
echo ""
|