@openthread/claude-code-plugin 0.1.3 → 0.1.5
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/.claude-plugin/plugin.json +1 -1
- package/bin/cli.sh +103 -99
- package/bin/postinstall.js +46 -32
- package/package.json +1 -1
package/bin/cli.sh
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
# CLI for managing the OpenThread Claude Code plugin
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
PLUGIN_ID="ot"
|
|
6
|
+
MARKETPLACE_NAME="openthread"
|
|
7
|
+
PLUGIN_KEY="${PLUGIN_ID}@${MARKETPLACE_NAME}"
|
|
6
8
|
PLUGIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
7
|
-
|
|
9
|
+
MARKETPLACE_DIR="$HOME/.claude/plugins/marketplaces/$MARKETPLACE_NAME"
|
|
10
|
+
DEST_DIR="$MARKETPLACE_DIR/plugins/$PLUGIN_ID"
|
|
11
|
+
KNOWN_FILE="$HOME/.claude/plugins/known_marketplaces.json"
|
|
8
12
|
SETTINGS_FILE="$HOME/.claude/settings.json"
|
|
9
13
|
|
|
10
14
|
usage() {
|
|
@@ -17,129 +21,129 @@ usage() {
|
|
|
17
21
|
echo " update Reinstall plugin (update to current version)"
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
register_plugin() {
|
|
21
|
-
# Register the plugin in ~/.claude/settings.json so Claude Code loads it
|
|
22
|
-
python3 -c "
|
|
23
|
-
import json, os
|
|
24
|
-
|
|
25
|
-
settings_path = '$SETTINGS_FILE'
|
|
26
|
-
dest_dir = '$DEST_DIR'
|
|
27
|
-
|
|
28
|
-
settings = {}
|
|
29
|
-
if os.path.exists(settings_path):
|
|
30
|
-
try:
|
|
31
|
-
with open(settings_path) as f:
|
|
32
|
-
settings = json.load(f)
|
|
33
|
-
except:
|
|
34
|
-
settings = {}
|
|
35
|
-
|
|
36
|
-
if not isinstance(settings.get('enabledPlugins'), list):
|
|
37
|
-
settings['enabledPlugins'] = []
|
|
38
|
-
|
|
39
|
-
# Check if already registered
|
|
40
|
-
already = any(
|
|
41
|
-
p.get('source', {}).get('path') == dest_dir
|
|
42
|
-
for p in settings['enabledPlugins']
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
if not already:
|
|
46
|
-
settings['enabledPlugins'].append({
|
|
47
|
-
'source': {'type': 'local', 'path': dest_dir}
|
|
48
|
-
})
|
|
49
|
-
os.makedirs(os.path.dirname(settings_path), exist_ok=True)
|
|
50
|
-
with open(settings_path, 'w') as f:
|
|
51
|
-
json.dump(settings, f, indent=2)
|
|
52
|
-
f.write('\n')
|
|
53
|
-
"
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
deregister_plugin() {
|
|
57
|
-
# Remove the plugin from ~/.claude/settings.json
|
|
58
|
-
if [ -f "$SETTINGS_FILE" ]; then
|
|
59
|
-
python3 -c "
|
|
60
|
-
import json
|
|
61
|
-
|
|
62
|
-
settings_path = '$SETTINGS_FILE'
|
|
63
|
-
dest_dir = '$DEST_DIR'
|
|
64
|
-
|
|
65
|
-
with open(settings_path) as f:
|
|
66
|
-
settings = json.load(f)
|
|
67
|
-
|
|
68
|
-
if isinstance(settings.get('enabledPlugins'), list):
|
|
69
|
-
settings['enabledPlugins'] = [
|
|
70
|
-
p for p in settings['enabledPlugins']
|
|
71
|
-
if p.get('source', {}).get('path') != dest_dir
|
|
72
|
-
]
|
|
73
|
-
with open(settings_path, 'w') as f:
|
|
74
|
-
json.dump(settings, f, indent=2)
|
|
75
|
-
f.write('\n')
|
|
76
|
-
"
|
|
77
|
-
fi
|
|
78
|
-
}
|
|
79
|
-
|
|
80
24
|
install_plugin() {
|
|
25
|
+
# Copy plugin files into marketplace structure
|
|
81
26
|
mkdir -p "$DEST_DIR"
|
|
82
|
-
|
|
83
27
|
for dir in .claude-plugin commands skills scripts; do
|
|
84
28
|
if [ -d "$PLUGIN_DIR/$dir" ]; then
|
|
85
29
|
cp -r "$PLUGIN_DIR/$dir" "$DEST_DIR/"
|
|
86
30
|
fi
|
|
87
31
|
done
|
|
32
|
+
[ -f "$PLUGIN_DIR/icon.svg" ] && cp "$PLUGIN_DIR/icon.svg" "$DEST_DIR/"
|
|
33
|
+
chmod +x "$DEST_DIR/scripts/"*.sh 2>/dev/null || true
|
|
88
34
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
35
|
+
# Create marketplace.json
|
|
36
|
+
mkdir -p "$MARKETPLACE_DIR/.claude-plugin"
|
|
37
|
+
python3 -c "
|
|
38
|
+
import json
|
|
39
|
+
mkt = {
|
|
40
|
+
'name': '$MARKETPLACE_NAME',
|
|
41
|
+
'description': 'OpenThread plugins for sharing AI conversations',
|
|
42
|
+
'owner': {'name': 'OpenThread'},
|
|
43
|
+
'plugins': [{'name': '$PLUGIN_ID', 'description': 'Share Claude Code conversations to OpenThread', 'source': './plugins/$PLUGIN_ID'}]
|
|
44
|
+
}
|
|
45
|
+
with open('$MARKETPLACE_DIR/.claude-plugin/marketplace.json', 'w') as f:
|
|
46
|
+
json.dump(mkt, f, indent=2)
|
|
47
|
+
f.write('\n')
|
|
48
|
+
"
|
|
92
49
|
|
|
93
|
-
|
|
50
|
+
# Register in known_marketplaces.json
|
|
51
|
+
python3 -c "
|
|
52
|
+
import json, os, datetime
|
|
53
|
+
path = '$KNOWN_FILE'
|
|
54
|
+
known = {}
|
|
55
|
+
if os.path.exists(path):
|
|
56
|
+
try:
|
|
57
|
+
with open(path) as f: known = json.load(f)
|
|
58
|
+
except: pass
|
|
59
|
+
known['$MARKETPLACE_NAME'] = {
|
|
60
|
+
'source': {'source': 'local', 'path': '$MARKETPLACE_DIR'},
|
|
61
|
+
'installLocation': '$MARKETPLACE_DIR',
|
|
62
|
+
'lastUpdated': datetime.datetime.utcnow().isoformat() + 'Z'
|
|
63
|
+
}
|
|
64
|
+
with open(path, 'w') as f:
|
|
65
|
+
json.dump(known, f, indent=2)
|
|
66
|
+
f.write('\n')
|
|
67
|
+
"
|
|
94
68
|
|
|
95
|
-
|
|
69
|
+
# Enable in settings.json
|
|
70
|
+
python3 -c "
|
|
71
|
+
import json, os
|
|
72
|
+
path = '$SETTINGS_FILE'
|
|
73
|
+
settings = {}
|
|
74
|
+
if os.path.exists(path):
|
|
75
|
+
try:
|
|
76
|
+
with open(path) as f: settings = json.load(f)
|
|
77
|
+
except: pass
|
|
78
|
+
if not isinstance(settings.get('enabledPlugins'), dict):
|
|
79
|
+
settings['enabledPlugins'] = {}
|
|
80
|
+
settings['enabledPlugins']['$PLUGIN_KEY'] = True
|
|
81
|
+
settings.pop('extraKnownMarketplaces', None)
|
|
82
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
83
|
+
with open(path, 'w') as f:
|
|
84
|
+
json.dump(settings, f, indent=2)
|
|
85
|
+
f.write('\n')
|
|
86
|
+
"
|
|
96
87
|
|
|
97
88
|
VERSION=$(python3 -c "import json; print(json.load(open('$DEST_DIR/.claude-plugin/plugin.json'))['version'])")
|
|
98
|
-
echo "✓ OpenThread plugin v$VERSION installed
|
|
99
|
-
echo "
|
|
100
|
-
echo "
|
|
89
|
+
echo "✓ OpenThread plugin v$VERSION installed"
|
|
90
|
+
echo " Marketplace: $MARKETPLACE_DIR"
|
|
91
|
+
echo " Restart Claude Code, then use /ot:share to share conversations."
|
|
101
92
|
}
|
|
102
93
|
|
|
103
94
|
uninstall_plugin() {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
95
|
+
[ -d "$MARKETPLACE_DIR" ] && rm -rf "$MARKETPLACE_DIR"
|
|
96
|
+
|
|
97
|
+
# Remove from known_marketplaces.json
|
|
98
|
+
[ -f "$KNOWN_FILE" ] && python3 -c "
|
|
99
|
+
import json
|
|
100
|
+
with open('$KNOWN_FILE') as f: known = json.load(f)
|
|
101
|
+
known.pop('$MARKETPLACE_NAME', None)
|
|
102
|
+
with open('$KNOWN_FILE', 'w') as f:
|
|
103
|
+
json.dump(known, f, indent=2)
|
|
104
|
+
f.write('\n')
|
|
105
|
+
" 2>/dev/null || true
|
|
106
|
+
|
|
107
|
+
# Remove from settings.json
|
|
108
|
+
[ -f "$SETTINGS_FILE" ] && python3 -c "
|
|
109
|
+
import json
|
|
110
|
+
with open('$SETTINGS_FILE') as f: settings = json.load(f)
|
|
111
|
+
if isinstance(settings.get('enabledPlugins'), dict):
|
|
112
|
+
settings['enabledPlugins'].pop('$PLUGIN_KEY', None)
|
|
113
|
+
with open('$SETTINGS_FILE', 'w') as f:
|
|
114
|
+
json.dump(settings, f, indent=2)
|
|
115
|
+
f.write('\n')
|
|
116
|
+
" 2>/dev/null || true
|
|
117
|
+
|
|
118
|
+
echo "✓ OpenThread plugin removed and deregistered"
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
check_status() {
|
|
114
|
-
local installed=false
|
|
115
|
-
local registered=false
|
|
116
|
-
|
|
117
122
|
if [ -d "$DEST_DIR" ] && [ -f "$DEST_DIR/.claude-plugin/plugin.json" ]; then
|
|
118
|
-
|
|
123
|
+
VERSION=$(python3 -c "import json; print(json.load(open('$DEST_DIR/.claude-plugin/plugin.json'))['version'])")
|
|
124
|
+
echo "✓ Plugin: v$VERSION at $DEST_DIR"
|
|
125
|
+
else
|
|
126
|
+
echo "✗ Plugin: not installed"
|
|
119
127
|
fi
|
|
120
128
|
|
|
121
|
-
if [ -f "$
|
|
122
|
-
registered=$(python3 -c "
|
|
129
|
+
if [ -f "$KNOWN_FILE" ] && python3 -c "
|
|
123
130
|
import json
|
|
124
|
-
with open('$
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
" 2>/dev/null || echo "false")
|
|
129
|
-
fi
|
|
130
|
-
|
|
131
|
-
if [ "$installed" = true ]; then
|
|
132
|
-
VERSION=$(python3 -c "import json; print(json.load(open('$DEST_DIR/.claude-plugin/plugin.json'))['version'])")
|
|
133
|
-
echo "✓ Plugin files: v$VERSION at $DEST_DIR"
|
|
131
|
+
with open('$KNOWN_FILE') as f: k = json.load(f)
|
|
132
|
+
exit(0 if '$MARKETPLACE_NAME' in k else 1)
|
|
133
|
+
" 2>/dev/null; then
|
|
134
|
+
echo "✓ Marketplace: registered"
|
|
134
135
|
else
|
|
135
|
-
echo "✗
|
|
136
|
+
echo "✗ Marketplace: not registered"
|
|
136
137
|
fi
|
|
137
138
|
|
|
138
|
-
if [ "$
|
|
139
|
-
|
|
139
|
+
if [ -f "$SETTINGS_FILE" ] && python3 -c "
|
|
140
|
+
import json
|
|
141
|
+
with open('$SETTINGS_FILE') as f: s = json.load(f)
|
|
142
|
+
exit(0 if isinstance(s.get('enabledPlugins'), dict) and s['enabledPlugins'].get('$PLUGIN_KEY') else 1)
|
|
143
|
+
" 2>/dev/null; then
|
|
144
|
+
echo "✓ Enabled: $PLUGIN_KEY"
|
|
140
145
|
else
|
|
141
|
-
echo "✗
|
|
142
|
-
echo " Run: openthread-claude install"
|
|
146
|
+
echo "✗ Enabled: no"
|
|
143
147
|
fi
|
|
144
148
|
}
|
|
145
149
|
|
package/bin/postinstall.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Auto-install plugin
|
|
2
|
+
// Auto-install plugin into a Claude Code marketplace so it's discoverable via /ot:share
|
|
3
3
|
const { existsSync, mkdirSync, cpSync, chmodSync, readdirSync, readFileSync, writeFileSync } = require("fs");
|
|
4
4
|
const { join, resolve } = require("path");
|
|
5
5
|
const { homedir } = require("os");
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const PLUGIN_ID = "ot";
|
|
8
|
+
const MARKETPLACE_NAME = "openthread";
|
|
8
9
|
const pluginSrc = resolve(__dirname, "..");
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
// Marketplace lives alongside the official one
|
|
12
|
+
const marketplaceDir = join(homedir(), ".claude", "plugins", "marketplaces", MARKETPLACE_NAME);
|
|
13
|
+
const pluginDest = join(marketplaceDir, "plugins", PLUGIN_ID);
|
|
10
14
|
|
|
11
15
|
const DIRS_TO_COPY = [".claude-plugin", "commands", "skills", "scripts"];
|
|
12
16
|
const FILES_TO_COPY = ["icon.svg"];
|
|
13
17
|
|
|
14
18
|
try {
|
|
19
|
+
// 1. Copy plugin files into marketplace/plugins/ot/
|
|
15
20
|
mkdirSync(pluginDest, { recursive: true });
|
|
16
21
|
|
|
17
22
|
for (const dir of DIRS_TO_COPY) {
|
|
@@ -48,43 +53,52 @@ try {
|
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
//
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
},
|
|
56
|
+
// 2. Create marketplace.json (like the official marketplace has)
|
|
57
|
+
const marketplaceJsonDir = join(marketplaceDir, ".claude-plugin");
|
|
58
|
+
mkdirSync(marketplaceJsonDir, { recursive: true });
|
|
59
|
+
writeFileSync(join(marketplaceJsonDir, "marketplace.json"), JSON.stringify({
|
|
60
|
+
name: MARKETPLACE_NAME,
|
|
61
|
+
description: "OpenThread plugins for sharing AI conversations",
|
|
62
|
+
owner: { name: "OpenThread" },
|
|
63
|
+
plugins: [{
|
|
64
|
+
name: PLUGIN_ID,
|
|
65
|
+
description: "Share Claude Code conversations to OpenThread",
|
|
66
|
+
source: `./plugins/${PLUGIN_ID}`,
|
|
67
|
+
}],
|
|
68
|
+
}, null, 2) + "\n");
|
|
69
|
+
|
|
70
|
+
// 3. Register marketplace in known_marketplaces.json
|
|
71
|
+
const knownPath = join(homedir(), ".claude", "plugins", "known_marketplaces.json");
|
|
72
|
+
let known = {};
|
|
73
|
+
if (existsSync(knownPath)) {
|
|
74
|
+
try { known = JSON.parse(readFileSync(knownPath, "utf8")); } catch { known = {}; }
|
|
75
|
+
}
|
|
76
|
+
known[MARKETPLACE_NAME] = {
|
|
77
|
+
source: { source: "local", path: marketplaceDir },
|
|
78
|
+
installLocation: marketplaceDir,
|
|
79
|
+
lastUpdated: new Date().toISOString(),
|
|
58
80
|
};
|
|
81
|
+
writeFileSync(knownPath, JSON.stringify(known, null, 2) + "\n");
|
|
59
82
|
|
|
83
|
+
// 4. Enable the plugin in settings.json
|
|
84
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
60
85
|
let settings = {};
|
|
61
86
|
if (existsSync(settingsPath)) {
|
|
62
|
-
try {
|
|
63
|
-
settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
64
|
-
} catch {
|
|
65
|
-
settings = {};
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!Array.isArray(settings.enabledPlugins)) {
|
|
70
|
-
settings.enabledPlugins = [];
|
|
87
|
+
try { settings = JSON.parse(readFileSync(settingsPath, "utf8")); } catch { settings = {}; }
|
|
71
88
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const alreadyRegistered = settings.enabledPlugins.some(
|
|
75
|
-
(p) => p.source && p.source.path === pluginDest
|
|
76
|
-
);
|
|
77
|
-
if (!alreadyRegistered) {
|
|
78
|
-
settings.enabledPlugins.push(pluginEntry);
|
|
79
|
-
mkdirSync(join(homedir(), ".claude"), { recursive: true });
|
|
80
|
-
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
89
|
+
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object" || Array.isArray(settings.enabledPlugins)) {
|
|
90
|
+
settings.enabledPlugins = {};
|
|
81
91
|
}
|
|
92
|
+
settings.enabledPlugins[`${PLUGIN_ID}@${MARKETPLACE_NAME}`] = true;
|
|
93
|
+
// Clean up old format if present
|
|
94
|
+
delete settings.extraKnownMarketplaces;
|
|
95
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
82
96
|
|
|
83
97
|
const version = JSON.parse(readFileSync(join(pluginSrc, "package.json"), "utf8")).version;
|
|
84
|
-
console.log(`\x1b[32m✓\x1b[0m OpenThread plugin v${version} installed
|
|
85
|
-
console.log(`
|
|
86
|
-
console.log(`
|
|
98
|
+
console.log(`\x1b[32m✓\x1b[0m OpenThread plugin v${version} installed`);
|
|
99
|
+
console.log(` Marketplace: ${marketplaceDir}`);
|
|
100
|
+
console.log(` Restart Claude Code, then use \x1b[1m/ot:share\x1b[0m to share conversations.`);
|
|
87
101
|
} catch (err) {
|
|
88
102
|
console.error(`Warning: Could not auto-install plugin: ${err.message}`);
|
|
89
|
-
console.error(
|
|
103
|
+
console.error("Try: claude --plugin-dir " + pluginSrc);
|
|
90
104
|
}
|