@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ot",
3
- "version": "0.1.4",
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,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="local-plugins"
6
+ MARKETPLACE_NAME="openthread"
8
7
  PLUGIN_KEY="${PLUGIN_ID}@${MARKETPLACE_NAME}"
9
8
  PLUGIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
10
- 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"
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
- register_plugin() {
24
- python3 -c "
25
- import json, os
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
- settings_path = '$SETTINGS_FILE'
28
- dest_dir = '$DEST_DIR'
29
- marketplace = '$MARKETPLACE_NAME'
30
- plugin_key = '$PLUGIN_KEY'
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
- settings = {}
33
- if os.path.exists(settings_path):
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(settings_path) as f:
36
- settings = json.load(f)
37
- except:
38
- settings = {}
39
-
40
- # Register as local marketplace
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 the plugin (must be an object, not array)
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'][plugin_key] = True
51
-
52
- os.makedirs(os.path.dirname(settings_path), exist_ok=True)
53
- with open(settings_path, 'w') as f:
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
- deregister_plugin() {
60
- if [ -f "$SETTINGS_FILE" ]; then
61
- python3 -c "
62
- import json
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
- settings_path = '$SETTINGS_FILE'
65
- marketplace = '$MARKETPLACE_NAME'
66
- plugin_key = '$PLUGIN_KEY'
94
+ uninstall_plugin() {
95
+ [ -d "$MARKETPLACE_DIR" ] && rm -rf "$MARKETPLACE_DIR"
67
96
 
68
- with open(settings_path) as f:
69
- settings = json.load(f)
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 enabledPlugins
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(plugin_key, None)
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
- install_plugin() {
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
- 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"
125
127
  fi
126
128
 
127
- if [ -f "$SETTINGS_FILE" ]; then
128
- registered=$(python3 -c "
129
+ if [ -f "$KNOWN_FILE" ] && python3 -c "
129
130
  import json
130
- with open('$SETTINGS_FILE') as f:
131
- s = json.load(f)
132
- plugins = s.get('enabledPlugins', {})
133
- print('true' if isinstance(plugins, dict) and plugins.get('$PLUGIN_KEY') else 'false')
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 "✗ Plugin files: not installed"
136
+ echo "✗ Marketplace: not registered"
142
137
  fi
143
138
 
144
- if [ "$registered" = "true" ]; then
145
- echo "✓ Registered: yes ($PLUGIN_KEY 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"
146
145
  else
147
- echo "✗ Registered: no (Claude Code won't detect the plugin)"
148
- echo " Run: openthread-claude install"
146
+ echo "✗ Enabled: no"
149
147
  fi
150
148
  }
151
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,45 +53,52 @@ try {
48
53
  }
49
54
  }
50
55
 
51
- // Register plugin in ~/.claude/settings.json so Claude Code detects it
52
- const MARKETPLACE_NAME = "local-plugins";
53
- const PLUGIN_ID = "ot";
54
- const settingsPath = join(homedir(), ".claude", "settings.json");
55
-
56
- let settings = {};
57
- if (existsSync(settingsPath)) {
58
- try {
59
- settings = JSON.parse(readFileSync(settingsPath, "utf8"));
60
- } catch {
61
- settings = {};
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 as a local marketplace
66
- if (!settings.extraKnownMarketplaces) {
67
- settings.extraKnownMarketplaces = {};
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
- settings.extraKnownMarketplaces[MARKETPLACE_NAME] = {
70
- source: {
71
- source: "directory",
72
- path: pluginDest,
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
- mkdirSync(join(homedir(), ".claude"), { recursive: true });
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 to ${pluginDest}`);
87
- console.log(` Registered in ${settingsPath}`);
88
- 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.`);
89
101
  } catch (err) {
90
102
  console.error(`Warning: Could not auto-install plugin: ${err.message}`);
91
- console.error(`You can manually install by copying plugin files to ${pluginDest}`);
103
+ console.error("Try: claude --plugin-dir " + pluginSrc);
92
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openthread/claude-code-plugin",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Share Claude Code conversations to OpenThread",
5
5
  "bin": {
6
6
  "openthread-claude": "bin/cli.sh"