@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ot",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Share Claude Code conversations to OpenThread",
5
5
  "icon": "icon.svg",
6
6
  "author": {
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
- PLUGIN_NAME="openthread-share"
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
- DEST_DIR="$HOME/.claude/plugins/$PLUGIN_NAME"
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
- if [ -f "$PLUGIN_DIR/icon.svg" ]; then
90
- cp "$PLUGIN_DIR/icon.svg" "$DEST_DIR/"
91
- fi
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
- chmod +x "$DEST_DIR/scripts/"*.sh 2>/dev/null || true
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
- register_plugin
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 to $DEST_DIR"
99
- echo " Registered in $SETTINGS_FILE"
100
- echo " Use /ot:share in Claude Code to share conversations."
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
- if [ -d "$DEST_DIR" ]; then
105
- rm -rf "$DEST_DIR"
106
- deregister_plugin
107
- echo " OpenThread plugin removed and deregistered"
108
- else
109
- echo "Plugin is not installed."
110
- fi
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
- installed=true
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 "$SETTINGS_FILE" ]; then
122
- registered=$(python3 -c "
129
+ if [ -f "$KNOWN_FILE" ] && python3 -c "
123
130
  import json
124
- with open('$SETTINGS_FILE') as f:
125
- s = json.load(f)
126
- plugins = s.get('enabledPlugins', [])
127
- print('true' if any(p.get('source',{}).get('path') == '$DEST_DIR' for p in plugins) else 'false')
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 "✗ Plugin files: not installed"
136
+ echo "✗ Marketplace: not registered"
136
137
  fi
137
138
 
138
- if [ "$registered" = "true" ]; then
139
- echo "✓ Registered: yes (in $SETTINGS_FILE)"
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 "✗ Registered: no (Claude Code won't detect the plugin)"
142
- echo " Run: openthread-claude install"
146
+ echo "✗ Enabled: no"
143
147
  fi
144
148
  }
145
149
 
@@ -1,17 +1,22 @@
1
1
  #!/usr/bin/env node
2
- // Auto-install plugin files to ~/.claude/plugins/ on npm install
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 PLUGIN_NAME = "openthread-share";
7
+ const PLUGIN_ID = "ot";
8
+ const MARKETPLACE_NAME = "openthread";
8
9
  const pluginSrc = resolve(__dirname, "..");
9
- const pluginDest = join(homedir(), ".claude", "plugins", PLUGIN_NAME);
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
- // Register plugin in ~/.claude/settings.json so Claude Code detects it
52
- const settingsPath = join(homedir(), ".claude", "settings.json");
53
- const pluginEntry = {
54
- source: {
55
- type: "local",
56
- path: pluginDest,
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
- // Only add if not already registered
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 to ${pluginDest}`);
85
- console.log(` Registered in ${settingsPath}`);
86
- console.log(` Use \x1b[1m/ot:share\x1b[0m in Claude Code to share conversations.`);
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(`You can manually install by copying plugin files to ${pluginDest}`);
103
+ console.error("Try: claude --plugin-dir " + pluginSrc);
90
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openthread/claude-code-plugin",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Share Claude Code conversations to OpenThread",
5
5
  "bin": {
6
6
  "openthread-claude": "bin/cli.sh"