@openthread/claude-code-plugin 0.1.2 → 0.1.4

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
- "name": "openthread-share",
3
- "version": "0.1.2",
2
+ "name": "ot",
3
+ "version": "0.1.4",
4
4
  "description": "Share Claude Code conversations to OpenThread",
5
5
  "icon": "icon.svg",
6
6
  "author": {
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Share Claude Code conversations to [OpenThread](https://openthread.me) -- a Reddit-like platform for AI conversation threads.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@openthread/claude-code-plugin)](https://www.npmjs.com/package/@openthread/claude-code-plugin)
6
- [![license](https://img.shields.io/npm/l/@openthread/claude-code-plugin)](https://github.com/nicholasgriffintn/openthread/blob/main/LICENSE)
6
+ [![license](https://img.shields.io/npm/l/@openthread/claude-code-plugin)](https://opensource.org/licenses/MIT)
7
7
  [![downloads](https://img.shields.io/npm/dm/@openthread/claude-code-plugin)](https://www.npmjs.com/package/@openthread/claude-code-plugin)
8
8
 
9
9
  ## Install
@@ -12,27 +12,66 @@ Share Claude Code conversations to [OpenThread](https://openthread.me) -- a Redd
12
12
  npm i -g @openthread/claude-code-plugin
13
13
  ```
14
14
 
15
- The postinstall script registers the plugin with Claude Code automatically.
15
+ The postinstall script registers the plugin with Claude Code automatically. Restart Claude Code after installing.
16
16
 
17
17
  ## Quick Start
18
18
 
19
19
  ```
20
- You> /share fixing auth bug
20
+ > /ot:share fixing auth bug
21
21
 
22
22
  Sharing conversation to OpenThread...
23
- Authenticated as @yourname
24
23
  Auto-generated title: "Debugging PKCE token refresh in auth middleware"
25
- Posted to c/ClaudeCode
24
+ Community: Coding with AI
25
+ Tags: typescript, authentication, debugging
26
26
 
27
- https://openthread.me/c/ClaudeCode/posts/abc123
27
+ Post shared successfully!
28
+ View it at: https://openthread.me/post/abc123
28
29
  ```
29
30
 
31
+ ## Examples
32
+
33
+ ### Share instantly with quick mode
34
+
35
+ ```
36
+ > /ot:share building a REST API with Hono
37
+ ```
38
+
39
+ Auto-generates title, picks the best community, adds tags, and posts -- no questions asked.
40
+
41
+ ### Share interactively
42
+
43
+ ```
44
+ > /ot:share
45
+
46
+ ? Title: Building a REST API with Hono on Bun
47
+ ? Community:
48
+ > Coding with AI
49
+ Prompt Engineering
50
+ OpenThread Meta
51
+ ? Tags: typescript, hono, api
52
+
53
+ Post shared successfully!
54
+ View it at: https://openthread.me/post/def456
55
+ ```
56
+
57
+ ### Share after a long debugging session
58
+
59
+ ```
60
+ > /ot:share tracked down a memory leak in the worker pool
61
+ ```
62
+
63
+ The plugin reads the full conversation, generates a summary, and shares it as a post others can learn from.
64
+
65
+ ### Share from any project
66
+
67
+ The plugin auto-detects the current Claude Code session file. Works in any project directory -- just run `/ot:share` and it finds the right conversation.
68
+
30
69
  ## Features
31
70
 
32
- - **One-command sharing** -- run `/share` inside any Claude Code session to publish the conversation to OpenThread.
33
- - **Quick mode** -- `/share <description>` auto-generates a title, selects a community, and posts immediately.
34
- - **Interactive mode** -- `/share` with no arguments prompts you to choose a title, community, and tags.
35
- - **Secure auth** -- PKCE OAuth flow with automatic token refresh. Credentials are stored locally.
71
+ - **One-command sharing** -- run `/ot:share` inside any Claude Code session to publish the conversation to OpenThread.
72
+ - **Quick mode** -- `/ot:share <description>` auto-generates a title, selects a community, and posts immediately.
73
+ - **Interactive mode** -- `/ot:share` with no arguments prompts you to choose a title, community, and tags.
74
+ - **Secure auth** -- PKCE OAuth flow with automatic token refresh. Credentials are stored locally at `~/.config/openthread/`.
36
75
  - **CLI management** -- `openthread-claude` binary for install, uninstall, status checks, and updates.
37
76
 
38
77
  ## Usage
@@ -40,31 +79,31 @@ https://openthread.me/c/ClaudeCode/posts/abc123
40
79
  ### Quick mode
41
80
 
42
81
  ```
43
- /share fixing auth bug
82
+ /ot:share <short description of what you worked on>
44
83
  ```
45
84
 
46
- Provide a short description after `/share`. The plugin generates a title from the conversation context, picks the best-matching community, and posts without further prompts.
85
+ Provide a short description after `/ot:share`. The plugin generates a title from the conversation context, picks the best-matching community, and posts without further prompts.
47
86
 
48
87
  ### Interactive mode
49
88
 
50
89
  ```
51
- /share
90
+ /ot:share
52
91
  ```
53
92
 
54
93
  Run with no arguments to step through each field:
55
94
 
56
95
  1. Title (suggested from conversation, editable)
57
- 2. Community (searchable list)
58
- 3. Tags (optional, comma-separated)
96
+ 2. Community (selectable from list)
97
+ 3. Tags (suggested, accept or modify)
59
98
 
60
99
  ## CLI Commands
61
100
 
62
101
  | Command | Description |
63
102
  | --- | --- |
64
- | `openthread-claude install` | Register the plugin with Claude Code |
65
- | `openthread-claude uninstall` | Remove the plugin from Claude Code |
66
- | `openthread-claude status` | Show current auth and plugin state |
67
- | `openthread-claude update` | Pull the latest plugin version |
103
+ | `openthread-claude install` | Install and register the plugin with Claude Code |
104
+ | `openthread-claude uninstall` | Remove the plugin and deregister from Claude Code |
105
+ | `openthread-claude status` | Show plugin installation and registration state |
106
+ | `openthread-claude update` | Reinstall plugin (update to current version) |
68
107
 
69
108
  ## Configuration
70
109
 
@@ -81,7 +120,8 @@ If you prefer not to use npm:
81
120
 
82
121
  1. Download the latest `.zip` release from [openthread.me/extensions](https://openthread.me/extensions).
83
122
  2. Extract it to `~/.claude/plugins/openthread-share/`.
84
- 3. Restart Claude Code. The plugin will be detected on next launch.
123
+ 3. Run `openthread-claude install` to register with Claude Code.
124
+ 4. Restart Claude Code.
85
125
 
86
126
  ```bash
87
127
  mkdir -p ~/.claude/plugins/openthread-share
package/bin/cli.sh CHANGED
@@ -3,19 +3,86 @@
3
3
  set -euo pipefail
4
4
 
5
5
  PLUGIN_NAME="openthread-share"
6
+ PLUGIN_ID="ot"
7
+ MARKETPLACE_NAME="local-plugins"
8
+ PLUGIN_KEY="${PLUGIN_ID}@${MARKETPLACE_NAME}"
6
9
  PLUGIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
7
10
  DEST_DIR="$HOME/.claude/plugins/$PLUGIN_NAME"
11
+ SETTINGS_FILE="$HOME/.claude/settings.json"
8
12
 
9
13
  usage() {
10
14
  echo "Usage: openthread-claude <command>"
11
15
  echo ""
12
16
  echo "Commands:"
13
- echo " install Install plugin to ~/.claude/plugins/"
14
- echo " uninstall Remove plugin from ~/.claude/plugins/"
15
- echo " status Check if plugin is installed"
17
+ echo " install Install and register plugin with Claude Code"
18
+ echo " uninstall Remove plugin and deregister from Claude Code"
19
+ echo " status Check if plugin is installed and registered"
16
20
  echo " update Reinstall plugin (update to current version)"
17
21
  }
18
22
 
23
+ register_plugin() {
24
+ python3 -c "
25
+ import json, os
26
+
27
+ settings_path = '$SETTINGS_FILE'
28
+ dest_dir = '$DEST_DIR'
29
+ marketplace = '$MARKETPLACE_NAME'
30
+ plugin_key = '$PLUGIN_KEY'
31
+
32
+ settings = {}
33
+ if os.path.exists(settings_path):
34
+ 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}
45
+ }
46
+
47
+ # Enable the plugin (must be an object, not array)
48
+ if not isinstance(settings.get('enabledPlugins'), dict):
49
+ 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:
54
+ json.dump(settings, f, indent=2)
55
+ f.write('\n')
56
+ "
57
+ }
58
+
59
+ deregister_plugin() {
60
+ if [ -f "$SETTINGS_FILE" ]; then
61
+ python3 -c "
62
+ import json
63
+
64
+ settings_path = '$SETTINGS_FILE'
65
+ marketplace = '$MARKETPLACE_NAME'
66
+ plugin_key = '$PLUGIN_KEY'
67
+
68
+ with open(settings_path) as f:
69
+ settings = json.load(f)
70
+
71
+ # Remove from enabledPlugins
72
+ 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:
80
+ json.dump(settings, f, indent=2)
81
+ f.write('\n')
82
+ "
83
+ fi
84
+ }
85
+
19
86
  install_plugin() {
20
87
  mkdir -p "$DEST_DIR"
21
88
 
@@ -31,26 +98,53 @@ install_plugin() {
31
98
 
32
99
  chmod +x "$DEST_DIR/scripts/"*.sh 2>/dev/null || true
33
100
 
101
+ register_plugin
102
+
34
103
  VERSION=$(python3 -c "import json; print(json.load(open('$DEST_DIR/.claude-plugin/plugin.json'))['version'])")
35
104
  echo "✓ OpenThread plugin v$VERSION installed to $DEST_DIR"
36
- echo " Use /share in Claude Code to share conversations."
105
+ echo " Registered in $SETTINGS_FILE"
106
+ echo " Restart Claude Code, then use /ot:share to share conversations."
37
107
  }
38
108
 
39
109
  uninstall_plugin() {
40
110
  if [ -d "$DEST_DIR" ]; then
41
111
  rm -rf "$DEST_DIR"
42
- echo "✓ OpenThread plugin removed from $DEST_DIR"
112
+ deregister_plugin
113
+ echo "✓ OpenThread plugin removed and deregistered"
43
114
  else
44
115
  echo "Plugin is not installed."
45
116
  fi
46
117
  }
47
118
 
48
119
  check_status() {
120
+ local installed=false
121
+ local registered=false
122
+
49
123
  if [ -d "$DEST_DIR" ] && [ -f "$DEST_DIR/.claude-plugin/plugin.json" ]; then
124
+ installed=true
125
+ fi
126
+
127
+ if [ -f "$SETTINGS_FILE" ]; then
128
+ registered=$(python3 -c "
129
+ 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
50
138
  VERSION=$(python3 -c "import json; print(json.load(open('$DEST_DIR/.claude-plugin/plugin.json'))['version'])")
51
- echo "✓ OpenThread plugin v$VERSION is installed at $DEST_DIR"
139
+ echo "✓ Plugin files: v$VERSION at $DEST_DIR"
140
+ else
141
+ echo "✗ Plugin files: not installed"
142
+ fi
143
+
144
+ if [ "$registered" = "true" ]; then
145
+ echo "✓ Registered: yes ($PLUGIN_KEY in $SETTINGS_FILE)"
52
146
  else
53
- echo "✗ OpenThread plugin is not installed."
147
+ echo "✗ Registered: no (Claude Code won't detect the plugin)"
54
148
  echo " Run: openthread-claude install"
55
149
  fi
56
150
  }
@@ -48,9 +48,44 @@ try {
48
48
  }
49
49
  }
50
50
 
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
+ }
64
+
65
+ // Register as a local marketplace
66
+ if (!settings.extraKnownMarketplaces) {
67
+ settings.extraKnownMarketplaces = {};
68
+ }
69
+ settings.extraKnownMarketplaces[MARKETPLACE_NAME] = {
70
+ source: {
71
+ source: "directory",
72
+ path: pluginDest,
73
+ },
74
+ };
75
+
76
+ // Enable the plugin
77
+ if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object" || Array.isArray(settings.enabledPlugins)) {
78
+ settings.enabledPlugins = {};
79
+ }
80
+ settings.enabledPlugins[`${PLUGIN_ID}@${MARKETPLACE_NAME}`] = true;
81
+
82
+ mkdirSync(join(homedir(), ".claude"), { recursive: true });
83
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
84
+
51
85
  const version = JSON.parse(readFileSync(join(pluginSrc, "package.json"), "utf8")).version;
52
86
  console.log(`\x1b[32m✓\x1b[0m OpenThread plugin v${version} installed to ${pluginDest}`);
53
- console.log(` Use \x1b[1m/share\x1b[0m in Claude Code to share conversations.`);
87
+ console.log(` Registered in ${settingsPath}`);
88
+ console.log(` Use \x1b[1m/ot:share\x1b[0m in Claude Code to share conversations.`);
54
89
  } catch (err) {
55
90
  console.error(`Warning: Could not auto-install plugin: ${err.message}`);
56
91
  console.error(`You can manually install by copying plugin files to ${pluginDest}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openthread/claude-code-plugin",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Share Claude Code conversations to OpenThread",
5
5
  "bin": {
6
6
  "openthread-claude": "bin/cli.sh"
package/scripts/share.sh CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env bash
2
2
  # Share a Claude Code session to OpenThread
3
3
  # Usage:
4
- # bash share.sh parse <session_file> — output compact markdown from JSONL
5
- # bash share.sh import <session_file> <title> <community_id> <tags> <token> — parse JSONL & import as compact post
6
- # bash share.sh import-export <export_file> <title> <community_id> <tags> <token> — send /export text as body & import
4
+ # bash share.sh parse <session_file> — output compact markdown from JSONL
5
+ # bash share.sh import <session_file> <title> <community_id> <tags> <token> [summary] — parse JSONL & import as compact post
6
+ # bash share.sh import-export <export_file> <title> <community_id> <tags> <token> [summary] — send /export text as body & import
7
7
  set -euo pipefail
8
8
 
9
9
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -94,6 +94,7 @@ import_post() {
94
94
  local community_id="$3"
95
95
  local tags_csv="$4"
96
96
  local access_token="$5"
97
+ local summary="${6:-}"
97
98
 
98
99
  # Parse the session into compact markdown
99
100
  local compact_body
@@ -114,11 +115,14 @@ print(json.dumps(tags))
114
115
  " "$tags_csv")
115
116
  fi
116
117
 
117
- # Build the import payload with body (no rawThread)
118
+ # Build the import payload prepend summary to body if provided
118
119
  local payload
119
120
  payload=$(python3 -c "
120
121
  import json, sys
121
122
  body_text = sys.argv[1]
123
+ summary = sys.argv[5] if len(sys.argv) > 5 and sys.argv[5] else ''
124
+ if summary:
125
+ body_text = '> ' + summary.replace('\n', '\n> ') + '\n\n---\n\n' + body_text
122
126
  payload = {
123
127
  'title': sys.argv[2],
124
128
  'communityId': sys.argv[3],
@@ -128,7 +132,7 @@ payload = {
128
132
  'provider': 'claude',
129
133
  }
130
134
  print(json.dumps(payload))
131
- " "$compact_body" "$title" "$community_id" "$tags_json")
135
+ " "$compact_body" "$title" "$community_id" "$tags_json" "$summary")
132
136
 
133
137
  # POST to import endpoint
134
138
  RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${API_BASE}/posts/import" \
@@ -156,6 +160,7 @@ import_export_post() {
156
160
  local community_id="$3"
157
161
  local tags_csv="$4"
158
162
  local access_token="$5"
163
+ local summary="${6:-}"
159
164
 
160
165
  if [ ! -f "$export_file" ]; then
161
166
  echo "ERROR: Export file not found: $export_file" >&2
@@ -180,6 +185,9 @@ with open(sys.argv[1], 'r') as f:
180
185
  export_text = f.read()
181
186
  # Truncate to 500000 chars
182
187
  export_text = export_text[:500000]
188
+ summary = sys.argv[5] if len(sys.argv) > 5 and sys.argv[5] else ''
189
+ if summary:
190
+ export_text = '> ' + summary.replace('\n', '\n> ') + '\n\n---\n\n' + export_text
183
191
  payload = {
184
192
  'title': sys.argv[2],
185
193
  'communityId': sys.argv[3],
@@ -189,7 +197,7 @@ payload = {
189
197
  'provider': 'claude',
190
198
  }
191
199
  print(json.dumps(payload))
192
- " "$export_file" "$title" "$community_id" "$tags_json")
200
+ " "$export_file" "$title" "$community_id" "$tags_json" "$summary")
193
201
 
194
202
  # POST to import endpoint
195
203
  RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${API_BASE}/posts/import" \
@@ -221,17 +229,17 @@ case "${1:-}" in
221
229
  ;;
222
230
  import)
223
231
  if [ -z "${6:-}" ]; then
224
- echo "Usage: share.sh import <session_file> <title> <community_id> <tags> <token>" >&2
232
+ echo "Usage: share.sh import <session_file> <title> <community_id> <tags> <token> [summary]" >&2
225
233
  exit 1
226
234
  fi
227
- import_post "$2" "$3" "$4" "${5:--}" "$6"
235
+ import_post "$2" "$3" "$4" "${5:--}" "$6" "${7:-}"
228
236
  ;;
229
237
  import-export)
230
238
  if [ -z "${6:-}" ]; then
231
- echo "Usage: share.sh import-export <export_file> <title> <community_id> <tags> <token>" >&2
239
+ echo "Usage: share.sh import-export <export_file> <title> <community_id> <tags> <token> [summary]" >&2
232
240
  exit 1
233
241
  fi
234
- import_export_post "$2" "$3" "$4" "${5:--}" "$6"
242
+ import_export_post "$2" "$3" "$4" "${5:--}" "$6" "${7:-}"
235
243
  ;;
236
244
  *)
237
245
  echo "Usage: share.sh {parse|import|import-export} ..." >&2