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