@hailer/mcp 0.2.3 → 0.2.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.
@@ -0,0 +1,261 @@
1
+ ---
2
+ description: Install a plugin from local marketplace clone to .claude/ folder
3
+ ---
4
+
5
+ # Install Plugin from Local Marketplace
6
+
7
+ Copies plugin files from local marketplace repo to project's `.claude/` folder.
8
+
9
+ ## Usage
10
+
11
+ ```
12
+ /install-plugin <plugin-name>
13
+ ```
14
+
15
+ ## Pre-flight Checks
16
+
17
+ ### 1. Marketplace Path
18
+ ```bash
19
+ PROJECT_ROOT="$(pwd)"
20
+ MARKETPLACE_PATH="$PROJECT_ROOT/hailer-marketplace"
21
+ if [ ! -d "$MARKETPLACE_PATH" ]; then
22
+ echo "ERROR: Marketplace not found at $MARKETPLACE_PATH"
23
+ echo "Clone it: git clone git@github.com:Bdolf/Hailer-Marketplace.git $MARKETPLACE_PATH"
24
+ exit 1
25
+ fi
26
+ ```
27
+
28
+ ### 2. Plugin Exists
29
+ ```bash
30
+ PLUGIN_NAME="$1"
31
+ if [ ! -d "$MARKETPLACE_PATH/$PLUGIN_NAME" ]; then
32
+ echo "ERROR: Plugin '$PLUGIN_NAME' not found in marketplace"
33
+ echo "Available plugins:"
34
+ ls -1 "$MARKETPLACE_PATH" | grep -v "^\." | grep -v "node_modules"
35
+ exit 1
36
+ fi
37
+ ```
38
+
39
+ ## Installation Steps
40
+
41
+ For each plugin, copy files to the correct locations:
42
+
43
+ | Source | Destination |
44
+ |--------|-------------|
45
+ | `{plugin}/agents/*.md` | `.claude/agents/` |
46
+ | `{plugin}/skills/*/` | `.claude/skills/` |
47
+ | `{plugin}/hooks/*.cjs` | `.claude/hooks/` |
48
+ | `{plugin}/hooks/hooks.json` | Merge into `.claude/settings.json` |
49
+ | `{plugin}/.lsp.json` | Install to `~/.claude/plugins/cache/` (LSP support) |
50
+
51
+ ## Execution Script
52
+
53
+ ```bash
54
+ #!/bin/bash
55
+
56
+ PROJECT_ROOT="$(pwd)"
57
+ MARKETPLACE_PATH="$PROJECT_ROOT/hailer-marketplace"
58
+ PLUGIN_NAME="$1"
59
+ PLUGIN_PATH="$MARKETPLACE_PATH/$PLUGIN_NAME"
60
+ TARGET_CLAUDE=".claude"
61
+
62
+ # Pre-flight
63
+ if [ ! -d "$PLUGIN_PATH" ]; then
64
+ echo "ERROR: Plugin '$PLUGIN_NAME' not found"
65
+ exit 1
66
+ fi
67
+
68
+ # Create target dirs if needed
69
+ mkdir -p "$TARGET_CLAUDE/agents" "$TARGET_CLAUDE/skills" "$TARGET_CLAUDE/hooks"
70
+
71
+ # Copy agents
72
+ if [ -d "$PLUGIN_PATH/agents" ]; then
73
+ cp -v "$PLUGIN_PATH/agents/"*.md "$TARGET_CLAUDE/agents/" 2>/dev/null
74
+ echo "Installed agents"
75
+ fi
76
+
77
+ # Copy skills (entire directories)
78
+ if [ -d "$PLUGIN_PATH/skills" ]; then
79
+ cp -rv "$PLUGIN_PATH/skills/"* "$TARGET_CLAUDE/skills/" 2>/dev/null || true
80
+ echo "Installed skills"
81
+ fi
82
+
83
+ # Copy hooks
84
+ if [ -d "$PLUGIN_PATH/hooks" ]; then
85
+ find "$PLUGIN_PATH/hooks" -name "*.cjs" -exec cp -v {} "$TARGET_CLAUDE/hooks/" \;
86
+ echo "Installed hooks"
87
+ fi
88
+
89
+ # Auto-install LSP if plugin has .lsp.json
90
+ if [ -f "$PLUGIN_PATH/.lsp.json" ]; then
91
+ echo "Plugin includes LSP support, installing..."
92
+
93
+ # Use typescript-lsp from marketplace as LSP source
94
+ LSP_SOURCE="$MARKETPLACE_PATH/typescript-lsp"
95
+ LSP_CACHE="$HOME/.claude/plugins/cache/hailer-mcp-marketplace/typescript-lsp/1.0.0"
96
+ LSP_INSTALLED="$HOME/.claude/plugins/installed_plugins.json"
97
+
98
+ # Create plugin cache directory
99
+ mkdir -p "$LSP_CACHE/.claude-plugin"
100
+
101
+ # Copy .lsp.json from the LSP plugin source (not the current plugin)
102
+ if [ -f "$LSP_SOURCE/.lsp.json" ]; then
103
+ cp "$LSP_SOURCE/.lsp.json" "$LSP_CACHE/.lsp.json"
104
+ else
105
+ cp "$PLUGIN_PATH/.lsp.json" "$LSP_CACHE/.lsp.json"
106
+ fi
107
+
108
+ # Copy the actual plugin.json from typescript-lsp plugin
109
+ if [ -f "$LSP_SOURCE/.claude-plugin/plugin.json" ]; then
110
+ cp "$LSP_SOURCE/.claude-plugin/plugin.json" "$LSP_CACHE/.claude-plugin/plugin.json"
111
+ else
112
+ # Fallback: create minimal plugin.json
113
+ cat > "$LSP_CACHE/.claude-plugin/plugin.json" << 'PLUGINJSON'
114
+ {
115
+ "name": "typescript-lsp",
116
+ "version": "1.0.0",
117
+ "description": "TypeScript/JavaScript LSP support"
118
+ }
119
+ PLUGINJSON
120
+ fi
121
+
122
+ # Register in installed_plugins.json
123
+ node -e "
124
+ const fs = require('fs');
125
+ const path = '$LSP_INSTALLED';
126
+
127
+ let data = { version: 2, plugins: {} };
128
+ if (fs.existsSync(path)) {
129
+ data = JSON.parse(fs.readFileSync(path, 'utf-8'));
130
+ }
131
+
132
+ const key = 'typescript-lsp@hailer-mcp-marketplace';
133
+ if (!data.plugins[key]) {
134
+ data.plugins[key] = [{
135
+ scope: 'user',
136
+ installPath: '$LSP_CACHE',
137
+ version: '1.0.0',
138
+ installedAt: new Date().toISOString(),
139
+ lastUpdated: new Date().toISOString()
140
+ }];
141
+ fs.writeFileSync(path, JSON.stringify(data, null, 2));
142
+ console.log('LSP registered in installed_plugins.json');
143
+ } else {
144
+ console.log('LSP already registered');
145
+ }
146
+ "
147
+
148
+ # Enable LSP tool in settings.json and .env.local
149
+ node -e "
150
+ const fs = require('fs');
151
+
152
+ // 1. Update .claude/settings.json
153
+ const settingsPath = '.claude/settings.json';
154
+ let settings = {};
155
+ if (fs.existsSync(settingsPath)) {
156
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
157
+ }
158
+ if (!settings.env) settings.env = {};
159
+ if (settings.env.ENABLE_LSP_TOOL !== '1') {
160
+ settings.env.ENABLE_LSP_TOOL = '1';
161
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
162
+ console.log('ENABLE_LSP_TOOL set in .claude/settings.json');
163
+ } else {
164
+ console.log('ENABLE_LSP_TOOL already in settings.json');
165
+ }
166
+
167
+ // 2. Update .env
168
+ const envPath = '.env';
169
+ let envContent = '';
170
+ if (fs.existsSync(envPath)) {
171
+ envContent = fs.readFileSync(envPath, 'utf-8');
172
+ }
173
+ if (!envContent.includes('ENABLE_LSP_TOOL=')) {
174
+ const line = (envContent && !envContent.endsWith('\n') ? '\n' : '') + 'ENABLE_LSP_TOOL=1\n';
175
+ fs.appendFileSync(envPath, line);
176
+ console.log('ENABLE_LSP_TOOL added to .env');
177
+ } else {
178
+ console.log('ENABLE_LSP_TOOL already in .env');
179
+ }
180
+ "
181
+
182
+ echo "LSP config installed"
183
+ fi
184
+
185
+ echo "Files copied. Updating CLAUDE.md..."
186
+ ```
187
+
188
+ ## Update CLAUDE.md (Node.js)
189
+
190
+ Run after file copy to add agent to the main `<agents>` table:
191
+
192
+ ```bash
193
+ node << 'EOF'
194
+ const fs = require("fs");
195
+ const path = require("path");
196
+
197
+ const PLUGIN_NAME = process.env.PLUGIN_NAME;
198
+ const AGENTS_DIR = ".claude/agents";
199
+ const CLAUDE_MD = "CLAUDE.md";
200
+
201
+ // Find agent files for this plugin
202
+ const agentFiles = fs.readdirSync(AGENTS_DIR)
203
+ .filter(f => f.startsWith("agent-" + PLUGIN_NAME) || f.includes(PLUGIN_NAME))
204
+ .filter(f => f.endsWith(".md"));
205
+
206
+ if (agentFiles.length === 0) {
207
+ console.log("No agent files found for " + PLUGIN_NAME);
208
+ process.exit(0);
209
+ }
210
+
211
+ let content = fs.readFileSync(CLAUDE_MD, "utf-8");
212
+
213
+ for (const agentFile of agentFiles) {
214
+ const agentPath = path.join(AGENTS_DIR, agentFile);
215
+ const agentContent = fs.readFileSync(agentPath, "utf-8");
216
+ const agentName = path.basename(agentFile, ".md");
217
+
218
+ // Parse frontmatter
219
+ const modelMatch = agentContent.match(/^model:\s*(\w+)/m);
220
+ const descMatch = agentContent.match(/^description:\s*(.{1,30})/m);
221
+ const model = modelMatch ? modelMatch[1] : "sonnet";
222
+ const desc = descMatch ? descMatch[1].trim() : PLUGIN_NAME;
223
+
224
+ // Check if already in agents table
225
+ if (content.includes("| `" + agentName + "`")) {
226
+ console.log(agentName + " already in <agents> table");
227
+ continue;
228
+ }
229
+
230
+ // Insert before </agents>
231
+ const newRow = "| " + desc + " | `" + agentName + "` | " + model + " |\n";
232
+ content = content.replace("</agents>", newRow + "</agents>");
233
+ console.log("Added " + agentName + " to <agents> table");
234
+ }
235
+
236
+ fs.writeFileSync(CLAUDE_MD, content);
237
+ console.log("CLAUDE.md updated");
238
+ EOF
239
+ ```
240
+
241
+ Note: Set `PLUGIN_NAME` env var before running.
242
+
243
+ ## Full Command
244
+
245
+ Run both scripts in sequence:
246
+ ```bash
247
+ # 1. Copy files (bash)
248
+ # 2. Update CLAUDE.md (node)
249
+ ```
250
+
251
+ Then: `echo "Restart Claude Code to load: claude -c"`
252
+
253
+ ## List Available Plugins
254
+
255
+ If no plugin name provided, list available:
256
+ ```bash
257
+ PROJECT_ROOT="$(pwd)"
258
+ MARKETPLACE_PATH="$PROJECT_ROOT/hailer-marketplace"
259
+ echo "Available plugins in marketplace:"
260
+ jq -r '.plugins[] | " \(.name) v\(.version) - \(.description)"' "$MARKETPLACE_PATH/.claude-plugin/marketplace.json"
261
+ ```
@@ -0,0 +1,42 @@
1
+ ---
2
+ description: List available plugins from local marketplace
3
+ ---
4
+
5
+ # List Available Marketplace Plugins
6
+
7
+ Shows all plugins available in the local marketplace clone.
8
+
9
+ ## Usage
10
+
11
+ ```
12
+ /list-plugins
13
+ ```
14
+
15
+ ## Execution
16
+
17
+ ```bash
18
+ PROJECT_ROOT="$(pwd)"
19
+ MARKETPLACE_PATH="$PROJECT_ROOT/hailer-marketplace"
20
+
21
+ if [ ! -d "$MARKETPLACE_PATH/.git" ]; then
22
+ echo "Marketplace not found at $MARKETPLACE_PATH"
23
+ echo "Run /marketplace-setup first"
24
+ exit 1
25
+ fi
26
+
27
+ # Pull latest before listing
28
+ cd "$MARKETPLACE_PATH" && git pull origin main --quiet && cd - > /dev/null
29
+ echo "Pulled latest from marketplace"
30
+ echo ""
31
+
32
+ jq -r '.plugins[] | " \(.name)@\(.version) - \(.description)"' \
33
+ "$MARKETPLACE_PATH/.claude-plugin/marketplace.json"
34
+ ```
35
+
36
+ ## Output (ALWAYS SHOW TO USER)
37
+
38
+ After running the execution script, ALWAYS present the results to the user:
39
+
40
+ 1. Show total count: "**X plugins available in marketplace**"
41
+ 2. List each plugin with name, version, and description
42
+ 3. Remind user they can install with `/install-plugin <name>`
@@ -0,0 +1,33 @@
1
+ ---
2
+ description: Clone or pull the Hailer marketplace repo
3
+ ---
4
+
5
+ # Marketplace Setup
6
+
7
+ Clone if missing, pull if exists. Always uses project root directory.
8
+
9
+ ## Execution
10
+
11
+ ```bash
12
+ # Get project root (where CLAUDE.md is)
13
+ PROJECT_ROOT="$(pwd)"
14
+
15
+ # Marketplace path is always relative to project root
16
+ MARKETPLACE_PATH="$PROJECT_ROOT/hailer-marketplace"
17
+
18
+ echo "Project root: $PROJECT_ROOT"
19
+ echo "Marketplace path: $MARKETPLACE_PATH"
20
+
21
+ if [ -d "$MARKETPLACE_PATH/.git" ]; then
22
+ cd "$MARKETPLACE_PATH" && git pull origin main
23
+ echo "Pulled latest"
24
+ else
25
+ git clone git@github.com:Bdolf/Hailer-Marketplace.git "$MARKETPLACE_PATH"
26
+ echo "Cloned marketplace to $MARKETPLACE_PATH"
27
+ fi
28
+ ```
29
+
30
+ ## Important
31
+
32
+ The marketplace is always cloned to `./hailer-marketplace` in the project root.
33
+ Commands should use absolute paths when working with the marketplace.
@@ -0,0 +1,55 @@
1
+ ---
2
+ description: Publish a plugin to the Hailer marketplace with pre-validation
3
+ ---
4
+
5
+ # Publish Plugin to Marketplace
6
+
7
+ ## Usage
8
+
9
+ ```
10
+ /publish-plugin <name>
11
+ /publish-plugin <source-path>
12
+ ```
13
+
14
+ ## Execution
15
+
16
+ **Spawn the publisher agent** with the plugin info:
17
+
18
+ ```
19
+ Task(
20
+ subagent_type="agent-marketplace-publisher",
21
+ prompt={
22
+ "task": "publish",
23
+ "plugin": {
24
+ "name": "<plugin-name>",
25
+ "type": "agent|skill|hook",
26
+ "source_path": "<path-to-source-file>",
27
+ "description": "<description>",
28
+ "author": "<author>"
29
+ }
30
+ }
31
+ )
32
+ ```
33
+
34
+ ## Argument Handling
35
+
36
+ If argument is a **path** (contains `/` or `.md`):
37
+ - `source_path`: use argument directly
38
+ - `name`: extract from filename (e.g., `agent-my-agent.md` → `my-agent`)
39
+ - `type`: detect from path (`/agents/` → agent, `/skills/` → skill)
40
+
41
+ If argument is a **name**:
42
+ - Look for `.claude/agents/agent-{name}.md`
43
+ - Or ask user for source path
44
+
45
+ ## Flow
46
+
47
+ 1. Orchestrator spawns `agent-marketplace-publisher`
48
+ 2. Publisher checks if plugin exists, suggests version
49
+ 3. Publisher returns `needs_confirmation` with version suggestion
50
+ 4. User confirms version
51
+ 5. Publisher creates branch, commits, pushes
52
+ 6. Publisher returns `trigger_review`
53
+ 7. Orchestrator spawns `agent-marketplace-reviewer`
54
+ 8. Reviewer validates and merges to main
55
+ 9. Done - plugin published
@@ -0,0 +1,87 @@
1
+ ---
2
+ description: Uninstall a plugin from .claude/ folder
3
+ ---
4
+
5
+ # Uninstall Plugin
6
+
7
+ Removes plugin files from project's `.claude/` folder and updates CLAUDE.md.
8
+
9
+ ## Usage
10
+
11
+ ```
12
+ /uninstall-plugin <plugin-name>
13
+ ```
14
+
15
+ ## Execution Script
16
+
17
+ ```bash
18
+ #!/bin/bash
19
+
20
+ PLUGIN_NAME="$1"
21
+ TARGET_CLAUDE=".claude"
22
+
23
+ if [ -z "$PLUGIN_NAME" ]; then
24
+ echo "Currently installed plugins:"
25
+ ls -1 "$TARGET_CLAUDE/agents/"agent-*.md 2>/dev/null | xargs -I{} basename {} .md | sed 's/^agent-//' | sort -u
26
+ exit 0
27
+ fi
28
+
29
+ # Remove agents matching plugin name
30
+ REMOVED=0
31
+ for f in "$TARGET_CLAUDE/agents/agent-$PLUGIN_NAME"*.md; do
32
+ if [ -f "$f" ]; then
33
+ rm -v "$f"
34
+ REMOVED=$((REMOVED + 1))
35
+ fi
36
+ done
37
+
38
+ # Remove skills matching plugin name
39
+ if [ -d "$TARGET_CLAUDE/skills/$PLUGIN_NAME" ]; then
40
+ rm -rv "$TARGET_CLAUDE/skills/$PLUGIN_NAME"
41
+ REMOVED=$((REMOVED + 1))
42
+ fi
43
+
44
+ # Remove hooks matching plugin name
45
+ find "$TARGET_CLAUDE/hooks" -name "${PLUGIN_NAME}*.cjs" -exec rm -v {} \; 2>/dev/null && REMOVED=$((REMOVED + 1))
46
+
47
+ if [ $REMOVED -eq 0 ]; then
48
+ echo "No files found for plugin '$PLUGIN_NAME'"
49
+ exit 1
50
+ fi
51
+
52
+ echo "Files removed. Updating CLAUDE.md..."
53
+ ```
54
+
55
+ ## Update CLAUDE.md (Node.js)
56
+
57
+ Run after file removal to remove agent from the main `<agents>` table:
58
+
59
+ ```bash
60
+ node << EOF
61
+ const fs = require("fs");
62
+
63
+ const PLUGIN_NAME = "$PLUGIN_NAME";
64
+ const CLAUDE_MD = "CLAUDE.md";
65
+
66
+ let content = fs.readFileSync(CLAUDE_MD, "utf-8");
67
+
68
+ // Remove lines containing this agent from the <agents> table
69
+ // Match: | ... | \`agent-PLUGIN_NAME...\` | ... |
70
+ const pattern = new RegExp("^\\\\|.*\\\`agent-" + PLUGIN_NAME + "[^\\\`]*\\\`.*\\\\|$\\\\n?", "gm");
71
+ const newContent = content.replace(pattern, "");
72
+
73
+ if (newContent !== content) {
74
+ fs.writeFileSync(CLAUDE_MD, newContent);
75
+ console.log("Removed agent-" + PLUGIN_NAME + " from <agents> table");
76
+ } else {
77
+ console.log("agent-" + PLUGIN_NAME + " not found in <agents> table");
78
+ }
79
+ EOF
80
+ ```
81
+
82
+ ## Full Command
83
+
84
+ Run both scripts in sequence, then:
85
+ ```
86
+ echo "Plugin uninstalled. Restart Claude Code: claude -c"
87
+ ```
@@ -52,10 +52,11 @@ function processHook(data) {
52
52
  process.exit(0);
53
53
  }
54
54
 
55
- const lowerPrompt = prompt.toLowerCase();
56
-
57
55
  // Detect task types that benefit from questions
56
+ // Order matters - more specific patterns first
58
57
  const taskPatterns = [
58
+ { pattern: /publish.*market|market.*publish|update.*market|market.*update|market.*icon|market.*listing/i, type: 'marketplace-publish', questions: ['New listing or update existing? (need productId for update)', 'Metadata only or new version with code?', 'Do you have an icon? (must upload with isPublic: true)'], skill: 'marketplace-publishing' },
59
+ { pattern: /publish|deploy|release|update.*app|app.*update|republish/i, type: 'app-publish', questions: ['Workspace only OR marketplace too?', 'Which app? (path or name)', 'Version bump needed?'], skill: 'publish-hailer-app' },
59
60
  { pattern: /build|create|make.*app/i, type: 'app', questions: ['What data to display?', 'What layout/components?', 'What user actions needed?'] },
60
61
  { pattern: /create|add.*insight|report/i, type: 'insight', questions: ['What metrics/aggregations?', 'Which workflows to query?', 'Any filters needed?'] },
61
62
  { pattern: /import|create.*activit|bulk/i, type: 'data', questions: ['Which workflow?', 'What field values?', 'How many records?'] },
@@ -66,6 +67,7 @@ function processHook(data) {
66
67
  const matched = taskPatterns.find(p => p.pattern.test(prompt));
67
68
 
68
69
  if (matched) {
70
+ const skillLine = matched.skill ? `\nLoad skill: ${matched.skill}` : '';
69
71
  const output = `
70
72
  <interactive-mode>
71
73
  BEFORE STARTING: Consider asking clarifying questions.
@@ -75,7 +77,7 @@ Suggested questions to ask user:
75
77
  ${matched.questions.map(q => `- ${q}`).join('\n')}
76
78
 
77
79
  Use AskUserQuestion tool if requirements are unclear.
78
- Gather specifics before spawning agents or making changes.
80
+ Gather specifics before spawning agents or making changes.${skillLine}
79
81
  </interactive-mode>
80
82
  `;
81
83
  console.log(output);
@@ -0,0 +1,155 @@
1
+ # Marketplace Publishing Skill
2
+
3
+ <when-to-use>
4
+ User wants to publish an app to marketplace, update marketplace listing, or change marketplace app icon/metadata.
5
+ </when-to-use>
6
+
7
+ <prerequisites>
8
+ - `productId` - Marketplace product ID (24-char hex)
9
+ - `appId` - The app being published (24-char hex)
10
+ - User must be Network Admin of the workspace
11
+ </prerequisites>
12
+
13
+ <option-a>
14
+ ## Update Metadata Only (No Version Change)
15
+
16
+ For updating icon, name, description without publishing new code:
17
+
18
+ <step-1>
19
+ **Upload icon as PUBLIC file**
20
+ ```javascript
21
+ upload_files({
22
+ files: [{ path: "/path/to/icon.png", isPublic: true }]
23
+ })
24
+ ```
25
+ </step-1>
26
+
27
+ <critical>
28
+ `isPublic: true` is REQUIRED for marketplace icons to display!
29
+ </critical>
30
+
31
+ <step-2>
32
+ **Update product metadata**
33
+ ```javascript
34
+ publish_app({
35
+ appId: "<appId>",
36
+ productId: "<productId>", // REQUIRED for update
37
+ title: "App Name",
38
+ description: "Description",
39
+ version: "1.0.0", // Current version (no bump needed)
40
+ versionDescription: "Release notes",
41
+ publisher: "Publisher Name",
42
+ iconFileId: "<public-file-id>"
43
+ })
44
+ ```
45
+ </step-2>
46
+ </option-a>
47
+
48
+ <option-b>
49
+ ## Publish New Version (Code Changes)
50
+
51
+ For publishing new app code with version bump:
52
+
53
+ <step-1>
54
+ **Upload icon as PUBLIC file (if changing)**
55
+ ```javascript
56
+ upload_files({
57
+ files: [{ path: "/path/to/icon.png", isPublic: true }]
58
+ })
59
+ ```
60
+ </step-1>
61
+
62
+ <step-2>
63
+ **Update manifest.json**
64
+ - Bump version (must be > current, e.g., "1.0.31" → "1.0.32")
65
+ - Update versionDescription
66
+ </step-2>
67
+
68
+ <step-3>
69
+ **Publish app bundle**
70
+ ```javascript
71
+ publish_hailer_app({
72
+ projectDirectory: "/path/to/app",
73
+ publishToMarket: true
74
+ })
75
+ ```
76
+ </step-3>
77
+
78
+ <step-4>
79
+ **Update product metadata**
80
+ ```javascript
81
+ publish_app({
82
+ appId: "<appId>",
83
+ productId: "<productId>",
84
+ title: "App Name",
85
+ description: "Description",
86
+ version: "1.0.32",
87
+ versionDescription: "What's new",
88
+ publisher: "Publisher Name",
89
+ iconFileId: "<public-file-id>"
90
+ })
91
+ ```
92
+ </step-4>
93
+ </option-b>
94
+
95
+ <option-c>
96
+ ## First-Time Marketplace Listing
97
+
98
+ For apps not yet in marketplace:
99
+
100
+ <step-1>
101
+ **Upload icon as PUBLIC**
102
+ ```javascript
103
+ upload_files({
104
+ files: [{ path: "/path/to/icon.png", isPublic: true }]
105
+ })
106
+ ```
107
+ </step-1>
108
+
109
+ <step-2>
110
+ **Publish app with marketplace flag**
111
+ ```javascript
112
+ publish_hailer_app({
113
+ projectDirectory: "/path/to/app",
114
+ publishToMarket: true
115
+ })
116
+ ```
117
+ Returns a `targetId`.
118
+ </step-2>
119
+
120
+ <step-3>
121
+ **Create marketplace listing**
122
+ ```javascript
123
+ publish_app({
124
+ appId: "<appId>",
125
+ versionId: "<targetId>", // From step 2
126
+ title: "App Name",
127
+ description: "Description",
128
+ version: "1.0.0",
129
+ versionDescription: "Initial release",
130
+ publisher: "Publisher Name",
131
+ iconFileId: "<public-file-id>"
132
+ })
133
+ ```
134
+ </step-3>
135
+
136
+ <note>
137
+ Creating new listings requires admin permissions.
138
+ </note>
139
+ </option-c>
140
+
141
+ <common-errors>
142
+ | Error | Cause | Fix |
143
+ |-------|-------|-----|
144
+ | Icon shows placeholder | File not public | Re-upload with `isPublic: true` |
145
+ | "You have to be a network admin" | Missing permissions | Get admin access |
146
+ | "Version not greater than previous" | Version too low | Bump version higher |
147
+ | POST /app/publish fails | Needs file upload | Use `publish_hailer_app` for code changes |
148
+ </common-errors>
149
+
150
+ <api-reference>
151
+ - `v3.product.update` - Updates metadata (name, description, icon, images)
152
+ - `v3.product.create` - Creates new listing (admin only)
153
+ - `POST /app/publish` - Uploads app bundle (requires .tgz file)
154
+ - `v3.app.product.install` - Installs/updates in workspace
155
+ </api-reference>