dealdesk-plugin 0.0.0-stage → 0.1.0

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.
Files changed (50) hide show
  1. package/.agents/plugins/marketplace.json +24 -0
  2. package/.claude-plugin/marketplace.json +29 -0
  3. package/.claude-plugin/plugin.json +18 -0
  4. package/.codex-plugin/plugin.json +30 -0
  5. package/.cursor-plugin/marketplace.json +29 -0
  6. package/.cursor-plugin/mcp.json +11 -0
  7. package/.cursor-plugin/plugin.json +18 -0
  8. package/.mcp.json +8 -0
  9. package/AGENTS.md +13 -0
  10. package/LICENSE +21 -0
  11. package/README.md +124 -3
  12. package/assets/logo-animated.svg +50 -0
  13. package/assets/logo.svg +8 -0
  14. package/bin/dealdesk-plugin.js +717 -0
  15. package/commands/dealdesk-craft-quote.md +15 -0
  16. package/commands/dealdesk-create-card.md +14 -0
  17. package/commands/dealdesk-directory-activity.md +12 -0
  18. package/commands/dealdesk-evaluate-configuration.md +10 -0
  19. package/commands/dealdesk-export-analysis.md +10 -0
  20. package/commands/dealdesk-find-or-create-customer.md +12 -0
  21. package/commands/dealdesk-help.md +62 -0
  22. package/commands/dealdesk-log-card-activity.md +13 -0
  23. package/commands/dealdesk-publish-portfolio.md +13 -0
  24. package/commands/dealdesk-quote-from-portfolio.md +12 -0
  25. package/commands/dealdesk-share-quote.md +13 -0
  26. package/mcp.json +9 -0
  27. package/package.json +59 -6
  28. package/plugin.json +36 -0
  29. package/scripts/bump-version.js +56 -0
  30. package/scripts/package-plugin-zip.js +130 -0
  31. package/scripts/test-project-scope.js +29 -0
  32. package/scripts/validate-npm-release.js +23 -0
  33. package/scripts/validate-skills.js +56 -0
  34. package/scripts/version-files.js +10 -0
  35. package/scripts/zip-archive.js +139 -0
  36. package/skills/README.md +30 -0
  37. package/skills/dealdesk-craft-quote/SKILL.md +21 -0
  38. package/skills/dealdesk-create-card/SKILL.md +59 -0
  39. package/skills/dealdesk-directory-activity/SKILL.md +14 -0
  40. package/skills/dealdesk-evaluate-configuration/SKILL.md +10 -0
  41. package/skills/dealdesk-export-analysis/SKILL.md +11 -0
  42. package/skills/dealdesk-find-or-create-customer/SKILL.md +14 -0
  43. package/skills/dealdesk-help/SKILL.md +63 -0
  44. package/skills/dealdesk-log-card-activity/SKILL.md +36 -0
  45. package/skills/dealdesk-manage-cases/SKILL.md +29 -0
  46. package/skills/dealdesk-manage-orders/SKILL.md +12 -0
  47. package/skills/dealdesk-portfolio-browse/SKILL.md +12 -0
  48. package/skills/dealdesk-publish-portfolio/SKILL.md +16 -0
  49. package/skills/dealdesk-quote-from-portfolio/SKILL.md +15 -0
  50. package/skills/dealdesk-share-quote/SKILL.md +16 -0
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: dealdesk-craft-quote
3
+ description: Craft or refine a classic DealDesk quote — clone a past quote, draft fresh, or use portfolio-evaluated pricing when asked.
4
+ ---
5
+
6
+ # Craft an excellent classic quote
7
+
8
+ Follow the `dealdesk-craft-quote` skill (classic-first).
9
+
10
+ 1. Confirm company, contact, and what the quote is about.
11
+ 2. Ask clone vs copy-a-liked-quote vs create fresh.
12
+ 3. Consult the published portfolio; confirm groups / selection / optional / `quantityEditable`.
13
+ 4. Create or clone, then refine with `replace_quote_items`.
14
+ 5. Use portfolio CPQ create/update only when asked for evaluated pricing (`quoteLayout` when useful).
15
+ 6. Offer share link or PDF. Never delete through MCP.
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: dealdesk-create-card
3
+ description: Create or update a DealDesk desk card. Soft-close with a stage change. Never delete.
4
+ ---
5
+
6
+ # Create or update a desk card
7
+
8
+ Follow the `dealdesk-create-card` skill.
9
+
10
+ 1. Read the current card with `dealdesk.get_card` when a card id is known; otherwise search with `dealdesk.list_cards`.
11
+ 2. Create with `dealdesk.create_card` or update with `dealdesk.patch_card`.
12
+ 3. Put the main body in `description`. Use `deskDescription` only for a desk-local note. Use `dealdesk.add_card_note` for structured notes, not for email.
13
+ 4. Link Customer Directory with `companyId` / `contactId` on create or patch (`null` clears). Clearing company clears contact. Linked case and quote customers are updated automatically.
14
+ 5. Soft-close by setting a closed stage. Do not delete.
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: dealdesk-directory-activity
3
+ description: Read a company or contact commercial summary and add or patch structured directory notes.
4
+ ---
5
+
6
+ # Directory notes and commercial summary
7
+
8
+ Follow the `dealdesk-directory-activity` skill.
9
+
10
+ 1. Resolve the company or contact.
11
+ 2. Call `dealdesk.get_company_commercial_summary` or `dealdesk.get_contact_commercial_summary` before advising on pipeline.
12
+ 3. Add or patch notes with title and body. Never store email as a directory note; never delete notes through MCP.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: dealdesk-evaluate-configuration
3
+ description: Evaluate a DealDesk configuration without creating a quote.
4
+ ---
5
+
6
+ # Evaluate a configuration
7
+
8
+ Follow the `dealdesk-evaluate-configuration` skill.
9
+
10
+ Call `dealdesk.portfolio_evaluate`. Report blockers, totals, and the rule trace from the tool result. Do not create a quote unless the user asks for one.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: dealdesk-export-analysis
3
+ description: Export DealDesk data for analysis. Do not page through unbounded lists.
4
+ ---
5
+
6
+ # Export for analysis
7
+
8
+ Follow the `dealdesk-export-analysis` skill.
9
+
10
+ Call `dealdesk.export_intelligence`. Do not walk `list_cards` or `list_quotes` in a loop to build a workbook.
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: dealdesk-find-or-create-customer
3
+ description: Find a DealDesk company or contact, and create one only when no match exists.
4
+ ---
5
+
6
+ # Find or create a customer
7
+
8
+ Follow the `dealdesk-find-or-create-customer` skill.
9
+
10
+ 1. Search with `dealdesk.list_companies` or `dealdesk.list_contacts`.
11
+ 2. Create only when no suitable match exists.
12
+ 3. Companies use `legalName` (or `name`). Contacts use `displayName` (or `name`). Leave `externalId` empty unless the user supplied one.
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: dealdesk-help
3
+ description: Explain what DealDesk can do in this host. Use when the user asks for help, capabilities, available commands, or how to use the DealDesk plugin.
4
+ ---
5
+
6
+ # DealDesk help
7
+
8
+ Help the user use DealDesk. Prefer live discovery over memory.
9
+
10
+ ## First steps
11
+
12
+ 1. Call `dealdesk.list_skills` and summarize the returned skills in plain language.
13
+ 2. If important tools are missing from the current tool list, call `dealdesk.discover` (omit `domain` for the domain list, or pass a domain such as `desk`, `quotes`, `portfolio`, `directory`, `export`, or `portfolio-authoring`).
14
+ 3. Then answer with the command map below and the live skill list.
15
+
16
+ If MCP is not connected yet, still explain the command map and say which auth path to use.
17
+
18
+ ## Slash commands
19
+
20
+ | Command | What it does |
21
+ | --- | --- |
22
+ | `/dealdesk-help` | This overview |
23
+ | `/dealdesk-create-card` | Create or update a desk card (including company/contact link) |
24
+ | `/dealdesk-log-card-activity` | Log inbound or outbound email on a card timeline |
25
+ | `/dealdesk-craft-quote` | Craft or refine a classic quote (clone or fresh draft) |
26
+ | `/dealdesk-quote-from-portfolio` | Evaluate a configuration and create a quote from the portfolio |
27
+ | `/dealdesk-evaluate-configuration` | Evaluate a configuration without creating a quote |
28
+ | `/dealdesk-directory-activity` | Company/contact commercial summary and notes |
29
+ | `/dealdesk-find-or-create-customer` | Find or create a company or contact |
30
+ | `/dealdesk-share-quote` | Quote status and customer share links (classic + rich) |
31
+ | `/dealdesk-export-analysis` | Bounded Deal Intelligence export |
32
+ | `/dealdesk-publish-portfolio` | Preview then confirm portfolio publish |
33
+
34
+ Hosts may prefix commands with the plugin name (for example `/dealdesk:dealdesk-help`).
35
+
36
+ ## Status updates and email
37
+
38
+ - Email: use `dealdesk.log_email` (or `/dealdesk-log-card-activity`). Never put email in `add_card_note`.
39
+ - Other timeline work: use `dealdesk.create_status_update` for `comment`, `task`, and touchpoints (`phone_call`, `meeting`, `email_incoming`, `email_outgoing`, `misc`).
40
+ - Read the timeline with `dealdesk.list_status_updates`. Update with `dealdesk.patch_status_update`.
41
+ - Structured desk notes: `dealdesk.add_card_note` / `dealdesk.patch_card_note`.
42
+
43
+ ## Quotes and shares
44
+
45
+ - Status and acceptance: `dealdesk.get_quote` (also returns existing shares).
46
+ - Line items without enrichment: `dealdesk.get_quote_summary`.
47
+ - Customer links: `create_quote_share` → `classicUrl` (standard) and `experienceUrl` (rich experience). Soft-close with `patch_quote_share` `isOpen=false`.
48
+
49
+ ## Hard limits
50
+
51
+ - Delete is not available through MCP. Soft-close cards by changing stage; soft-close shares with `isOpen=false`.
52
+ - Portfolio publish needs preview, then `portfolio_publish` with `confirm=true`.
53
+ - Do not invent prices. Prefer portfolio evaluation and published revision ids.
54
+ - Prefer `/dealdesk-export-analysis` over paging through large lists forever.
55
+ - Skill-gated tools need `dealdesk.discover` with the matching domain first (for example `orders`, `portfolio`, `portfolio-authoring`).
56
+ - Remind the user that DealDesk tool results may disclose customer data to the AI host and other tools in the conversation, and that they remain responsible for that data.
57
+
58
+ ## Auth
59
+
60
+ - Sign-in is OAuth. The user signs in on the agenthouse page, chooses the project, and grants DealDesk access.
61
+ - Remote URL: `https://api.agenthouse.org/mcp/dealdesk` (ChatGPT, Claude, Codex, Cursor).
62
+ - The Cursor plugin and the local connector open that same page. An API key is only for automation that cannot open a browser (`AGENTHOUSE_API_KEY`, and `AGENTHOUSE_PROJECT_ID` when the account has more than one project).
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: dealdesk-log-card-activity
3
+ description: Log inbound or outbound email on a DealDesk card timeline. Never store email as a card note.
4
+ ---
5
+
6
+ # Log email on a card
7
+
8
+ Follow the `dealdesk-log-card-activity` skill.
9
+
10
+ 1. Resolve the desk card (`dealdesk.list_cards` / `dealdesk.get_card`).
11
+ 2. Call `dealdesk.log_email` with `direction` (`incoming` or `outgoing`), `from`, `to`, `subject`, `text`, and `cardId`.
12
+ 3. Do not call `dealdesk.add_card_note` for email. That tool is for internal structured notes only.
13
+ 4. For a comment, task, call, or meeting, use `dealdesk.create_status_update` instead.
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: dealdesk-publish-portfolio
3
+ description: Preview a DealDesk portfolio publish, then confirm only when blockers are clear.
4
+ ---
5
+
6
+ # Publish the portfolio
7
+
8
+ Follow the `dealdesk-publish-portfolio` skill.
9
+
10
+ 1. Call `dealdesk.discover` with domain `portfolio-authoring` if publish tools are not listed yet.
11
+ 2. Call `dealdesk.initialize_portfolio_draft` only when no draft exists.
12
+ 3. Call `dealdesk.portfolio_publish_preview` and show blockers.
13
+ 4. Call `dealdesk.portfolio_publish` with `confirm=true` only when blockers are clear and the user has confirmed. Never publish without that flag.
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: dealdesk-quote-from-portfolio
3
+ description: Create a DealDesk quote from the published portfolio after a side-effect free evaluation.
4
+ ---
5
+
6
+ # Quote from the published portfolio
7
+
8
+ Follow the `dealdesk-quote-from-portfolio` skill.
9
+
10
+ 1. Confirm a published revision with `dealdesk.portfolio_summary` when needed.
11
+ 2. Evaluate with `dealdesk.portfolio_evaluate`. Do not invent prices.
12
+ 3. Create or update with `dealdesk.create_quote_from_configuration` / `dealdesk.update_quote_from_configuration` using the published revision ids and the same configuration.
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: dealdesk-share-quote
3
+ description: Get a DealDesk quote status or customer share links (classic and rich experience). Soft-close with isOpen=false.
4
+ ---
5
+
6
+ # Quote status and share links
7
+
8
+ Follow the `dealdesk-share-quote` skill.
9
+
10
+ 1. Call `dealdesk.get_quote` for status, acceptance, and existing shares.
11
+ 2. Call `dealdesk.create_quote_share` when a new customer link is needed.
12
+ 3. Prefer `classicUrl` for the standard share and `experienceUrl` for the rich experience Desk.
13
+ 4. Soft-close with `dealdesk.patch_quote_share` and `isOpen=false`. Never delete shares through MCP.
package/mcp.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
3
+ "mcpServers": {
4
+ "dealdesk": {
5
+ "type": "streamable-http",
6
+ "url": "https://api.agenthouse.org/mcp/dealdesk"
7
+ }
8
+ }
9
+ }
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
- {
2
- "name": "dealdesk-plugin",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "dealdesk-plugin",
3
+ "version": "0.1.0",
4
+ "description": "DealDesk plugin for agenthouse — marketplace-installable skills and MCP tools for ChatGPT, Claude, Cursor, and Codex",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/agenthouse-org/dealdesk-plugin",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/agenthouse-org/dealdesk-plugin.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/agenthouse-org/dealdesk-plugin/issues"
13
+ },
14
+ "keywords": [
15
+ "agenthouse",
16
+ "dealdesk",
17
+ "plugin",
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "cursor",
21
+ "claude",
22
+ "chatgpt",
23
+ "codex"
24
+ ],
25
+ "bin": {
26
+ "dealdesk-plugin": "./bin/dealdesk-plugin.js",
27
+ "dealdesk-mcp": "./bin/dealdesk-plugin.js"
28
+ },
29
+ "files": [
30
+ "bin",
31
+ "skills",
32
+ "commands",
33
+ "scripts",
34
+ "assets",
35
+ "plugin.json",
36
+ "mcp.json",
37
+ ".mcp.json",
38
+ ".codex-plugin",
39
+ ".claude-plugin",
40
+ ".cursor-plugin",
41
+ ".agents",
42
+ "README.md",
43
+ "LICENSE",
44
+ "AGENTS.md"
45
+ ],
46
+ "engines": {
47
+ "node": ">=20"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "registry": "https://registry.npmjs.org/"
52
+ },
53
+ "scripts": {
54
+ "start": "node bin/dealdesk-plugin.js",
55
+ "test": "node scripts/validate-skills.js && node scripts/test-project-scope.js && node scripts/package-plugin-zip.js",
56
+ "package:zip": "node scripts/package-plugin-zip.js",
57
+ "version:bump": "node scripts/bump-version.js"
58
+ }
59
+ }
package/plugin.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
+ "name": "dealdesk",
4
+ "version": "0.1.0",
5
+ "description": "DealDesk skills and MCP tools for desk cards, email logs, quotes, customers, and portfolio publish.",
6
+ "author": {
7
+ "name": "agenthouse",
8
+ "url": "https://agenthouse.org"
9
+ },
10
+ "homepage": "https://agenthouse.org",
11
+ "repository": "https://github.com/agenthouse-org/dealdesk-plugin",
12
+ "license": "MIT",
13
+ "keywords": ["agenthouse", "dealdesk", "mcp", "quotes", "crm"],
14
+ "logo": "assets/logo.svg",
15
+ "extensions": {
16
+ "com.openai": {
17
+ "interface": {
18
+ "displayName": "DealDesk",
19
+ "shortDescription": "Desk cards, quotes, customers",
20
+ "logo": "./assets/logo.svg",
21
+ "composerIcon": "./assets/logo.svg",
22
+ "longDescription": "Operate DealDesk from ChatGPT or Codex: create and update desk cards, log email on the card timeline, evaluate configurations, create quotes from the published portfolio, find or create customers, export for analysis, and publish a portfolio only after preview and confirm. Tools run on the agenthouse MCP endpoint. Delete is not available.",
23
+ "developerName": "agenthouse",
24
+ "category": "Productivity",
25
+ "capabilities": ["Read", "Write"],
26
+ "websiteURL": "https://agenthouse.org",
27
+ "privacyPolicyURL": "https://agenthouse.org/privacy/",
28
+ "defaultPrompt": [
29
+ "List open DealDesk cards for my project.",
30
+ "Log this inbound email on the matching desk card.",
31
+ "Create a quote from the published portfolio for this configuration."
32
+ ]
33
+ }
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const versionFiles = require('./version-files');
7
+
8
+ const kind = process.argv[2];
9
+ if (!['patch', 'minor', 'major'].includes(kind)) {
10
+ process.stderr.write('Usage: node scripts/bump-version.js <patch|minor|major>\n');
11
+ process.exit(1);
12
+ }
13
+
14
+ const root = path.join(__dirname, '..');
15
+
16
+ function readVersion(rel) {
17
+ const text = fs.readFileSync(path.join(root, rel), 'utf8');
18
+ const match = text.match(/"version"\s*:\s*"(\d+\.\d+\.\d+)"/);
19
+ if (!match) throw new Error(`No semantic version in ${rel}`);
20
+ return { text, version: match[1], token: match[0] };
21
+ }
22
+
23
+ function bump(version, level) {
24
+ const parts = version.split('.').map((part) => Number(part));
25
+ if (parts.some((part) => !Number.isInteger(part))) {
26
+ throw new Error(`Invalid version ${version}`);
27
+ }
28
+ if (level === 'major') return `${parts[0] + 1}.0.0`;
29
+ if (level === 'minor') return `${parts[0]}.${parts[1] + 1}.0`;
30
+ return `${parts[0]}.${parts[1]}.${parts[2] + 1}`;
31
+ }
32
+
33
+ const current = readVersion(versionFiles[0]);
34
+ for (const rel of versionFiles.slice(1)) {
35
+ const found = readVersion(rel);
36
+ if (found.version !== current.version) {
37
+ throw new Error(`${rel} is ${found.version}; expected ${current.version}`);
38
+ }
39
+ }
40
+
41
+ const next = bump(current.version, kind);
42
+ for (const rel of versionFiles) {
43
+ const found = readVersion(rel);
44
+ const updated = found.text.replace(found.token, `"version": "${next}"`);
45
+ if (updated === found.text) throw new Error(`Version was not replaced in ${rel}`);
46
+ fs.writeFileSync(path.join(root, rel), updated);
47
+ }
48
+
49
+ if (process.env.GITHUB_OUTPUT) {
50
+ fs.appendFileSync(
51
+ process.env.GITHUB_OUTPUT,
52
+ `version=${next}\nprevious=${current.version}\n`,
53
+ );
54
+ }
55
+
56
+ process.stdout.write(`${current.version} -> ${next}\n`);
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const versionFiles = require('./version-files');
7
+ const { createZip, readZip } = require('./zip-archive');
8
+
9
+ const root = path.join(__dirname, '..');
10
+ const MCP_URL = 'https://api.agenthouse.org/mcp/dealdesk';
11
+
12
+ const INCLUDE = [
13
+ 'plugin.json',
14
+ 'mcp.json',
15
+ '.mcp.json',
16
+ 'README.md',
17
+ 'LICENSE',
18
+ 'AGENTS.md',
19
+ 'bin',
20
+ 'assets',
21
+ 'skills',
22
+ 'commands',
23
+ '.codex-plugin',
24
+ '.claude-plugin',
25
+ '.cursor-plugin',
26
+ '.agents',
27
+ ];
28
+
29
+ function readJson(rel) {
30
+ return JSON.parse(fs.readFileSync(path.join(root, rel), 'utf8'));
31
+ }
32
+
33
+ function fail(message) {
34
+ throw new Error(message);
35
+ }
36
+
37
+ function zipPath(rel) {
38
+ return rel.split(path.sep).join('/');
39
+ }
40
+
41
+ function walk(rel) {
42
+ const abs = path.join(root, rel);
43
+ const stat = fs.statSync(abs);
44
+ if (stat.isFile()) return [zipPath(rel)];
45
+ const names = [];
46
+ for (const entry of fs.readdirSync(abs, { withFileTypes: true })) {
47
+ if (entry.name === '.DS_Store') continue;
48
+ names.push(...walk(path.join(rel, entry.name)));
49
+ }
50
+ return names;
51
+ }
52
+
53
+ function assertVersions(version) {
54
+ for (const rel of versionFiles) {
55
+ const found = readJson(rel).version;
56
+ if (found !== version) fail(`${rel} version is ${found}; expected ${version}`);
57
+ }
58
+ }
59
+
60
+ function assertSkills() {
61
+ const skillsRoot = path.join(root, 'skills');
62
+ const dirs = fs.readdirSync(skillsRoot, { withFileTypes: true })
63
+ .filter((entry) => entry.isDirectory());
64
+ if (dirs.length === 0) fail('No skills to package');
65
+ for (const dir of dirs) {
66
+ const rel = `skills/${dir.name}/SKILL.md`;
67
+ const text = fs.readFileSync(path.join(root, rel), 'utf8');
68
+ const front = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
69
+ if (!front) fail(`${rel} is missing YAML front matter`);
70
+ const skillName = (front[1].match(/^name:\s*(.+)\s*$/m) || [])[1];
71
+ const description = (front[1].match(/^description:\s*(.+)\s*$/m) || [])[1];
72
+ if (skillName !== dir.name) fail(`${rel} name must match its folder`);
73
+ if (!description) fail(`${rel} is missing a description`);
74
+ if (description.length > 1024) fail(`${rel} description is longer than 1024 characters`);
75
+ if (`dealdesk:${dir.name}`.length > 64) fail(`dealdesk:${dir.name} is longer than 64 characters`);
76
+ if (!text.slice(front[0].length).trim()) fail(`${rel} has no instructions`);
77
+ }
78
+ }
79
+
80
+ function main() {
81
+ const pkg = readJson('package.json');
82
+ assertVersions(pkg.version);
83
+ assertSkills();
84
+
85
+ const manifest = readJson('plugin.json');
86
+ if (manifest.name !== 'dealdesk') fail(`plugin name must be dealdesk, found ${manifest.name}`);
87
+ if (manifest.version !== pkg.version) fail('plugin.json version does not match package.json');
88
+
89
+ const mcp = readJson('mcp.json');
90
+ const server = mcp.mcpServers && mcp.mcpServers.dealdesk;
91
+ if (!server || server.type !== 'streamable-http' || server.url !== MCP_URL) {
92
+ fail(`mcp.json must declare dealdesk as streamable-http at ${MCP_URL}`);
93
+ }
94
+
95
+ const logo = fs.readFileSync(path.join(root, 'assets', 'logo.svg'), 'utf8');
96
+ if (!/viewBox="0 0 128 128"/.test(logo)) fail('assets/logo.svg must be a square 128×128 SVG');
97
+
98
+ const names = INCLUDE.flatMap((rel) => walk(rel)).sort();
99
+ const files = names.map((name) => ({
100
+ name,
101
+ data: fs.readFileSync(path.join(root, ...name.split('/'))),
102
+ }));
103
+
104
+ const zip = createZip(files);
105
+ const outDir = path.join(root, 'dist');
106
+ fs.mkdirSync(outDir, { recursive: true });
107
+ const outName = `dealdesk-plugin-${pkg.version}.zip`;
108
+ fs.writeFileSync(path.join(outDir, outName), zip);
109
+
110
+ const packed = readZip(zip).map((entry) => entry.name).sort();
111
+ if (packed.join('\n') !== names.join('\n')) fail('ZIP entries diverged from the plugin package');
112
+ for (const required of ['plugin.json', 'mcp.json', '.mcp.json', 'bin/dealdesk-plugin.js', 'assets/logo.svg']) {
113
+ if (!packed.includes(required)) fail(`ZIP is missing ${required}`);
114
+ }
115
+ if (!packed.some((name) => name.startsWith('skills/') && name.endsWith('/SKILL.md'))) {
116
+ fail('ZIP is missing skills');
117
+ }
118
+ if (packed.some((name) => name.includes('\\') || name.split('/').includes('..'))) {
119
+ fail('ZIP contains an unsafe path');
120
+ }
121
+
122
+ process.stdout.write(`${outName}\n${packed.map((name) => ` ${name}`).join('\n')}\n`);
123
+ }
124
+
125
+ try {
126
+ main();
127
+ } catch (error) {
128
+ process.stderr.write(`${error.message}\n`);
129
+ process.exit(1);
130
+ }
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const assert = require('assert');
4
+ const { pickProjectId } = require('../bin/dealdesk-plugin');
5
+
6
+ const many = pickProjectId({
7
+ projectId: 'AGENTHOUSE01',
8
+ projectIds: ['AGENTHOUSE01', 'NERI001']
9
+ });
10
+ assert.strictEqual(many.ambiguous, true);
11
+ assert.deepStrictEqual(many.ids, ['AGENTHOUSE01', 'NERI001']);
12
+
13
+ const snake = pickProjectId({
14
+ project_id: 'AGENTHOUSE01',
15
+ project_ids: ['AGENTHOUSE01', 'NERI001']
16
+ });
17
+ assert.strictEqual(snake.ambiguous, true);
18
+
19
+ const single = pickProjectId({
20
+ projectId: 'DEMO01',
21
+ projectIds: ['DEMO01']
22
+ });
23
+ assert.strictEqual(single.id, 'DEMO01');
24
+ assert.ok(!single.ambiguous);
25
+
26
+ const primaryOnly = pickProjectId({ projectId: 'DEMO01' });
27
+ assert.strictEqual(primaryOnly.id, 'DEMO01');
28
+
29
+ console.log('ok - pickProjectId treats multiple projectIds as ambiguous');
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const assert = require('assert');
5
+
6
+ const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
7
+ assert.equal(pkg.name, 'dealdesk-plugin', 'Unexpected npm package name');
8
+ assert.equal(pkg.publishConfig && pkg.publishConfig.access, 'public');
9
+ assert.equal(pkg.publishConfig && pkg.publishConfig.registry, 'https://registry.npmjs.org/');
10
+ assert.match(pkg.version, /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/, 'Invalid release version');
11
+
12
+ if (process.env.GITHUB_EVENT_NAME === 'release') {
13
+ assert.equal(process.env.RELEASE_TAG, `v${pkg.version}`, 'Release tag must match package.json version');
14
+ assert.equal(
15
+ process.env.RELEASE_PRERELEASE === 'true',
16
+ pkg.version.includes('-'),
17
+ 'GitHub prerelease flag must match the npm prerelease version',
18
+ );
19
+ } else {
20
+ assert.equal(process.env.GITHUB_EVENT_NAME, 'workflow_dispatch', 'Unsupported publishing event');
21
+ }
22
+
23
+ process.stdout.write(`Validated ${pkg.name}@${pkg.version}\n`);
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * SKILL.md consumability check:
6
+ * - each skill must be <= 100 lines, OR
7
+ * - contain a "## Contents" section (for longer progressive-disclosure skills)
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ const skillsRoot = path.join(__dirname, '..', 'skills');
14
+ const dirs = fs.readdirSync(skillsRoot, { withFileTypes: true }).filter((d) => d.isDirectory());
15
+
16
+ let failed = 0;
17
+ const rows = [];
18
+
19
+ for (const dir of dirs) {
20
+ const skillPath = path.join(skillsRoot, dir.name, 'SKILL.md');
21
+ if (!fs.existsSync(skillPath)) {
22
+ rows.push({ skill: dir.name, ok: false, reason: 'missing SKILL.md' });
23
+ failed += 1;
24
+ continue;
25
+ }
26
+ if (!/^dealdesk-[a-z0-9-]{1,54}$/.test(dir.name)) {
27
+ rows.push({ skill: dir.name, ok: false, reason: 'folder must be dealdesk-<name>' });
28
+ failed += 1;
29
+ continue;
30
+ }
31
+ const text = fs.readFileSync(skillPath, 'utf8');
32
+ const name = (text.match(/^name:\s*(.+)\s*$/m) || [])[1];
33
+ if (name !== dir.name) {
34
+ rows.push({ skill: dir.name, ok: false, reason: `frontmatter name ${name || '(missing)'} != folder` });
35
+ failed += 1;
36
+ continue;
37
+ }
38
+ const lines = text.split(/\r?\n/).length;
39
+ const hasContents = /^## Contents\s*$/m.test(text);
40
+ const ok = lines <= 100 || hasContents;
41
+ const reason = lines <= 100
42
+ ? `<=100 lines (${lines})`
43
+ : hasContents
44
+ ? `has ## Contents (${lines} lines)`
45
+ : `FAIL: ${lines} lines, no ## Contents`;
46
+ if (!ok) failed += 1;
47
+ rows.push({ skill: dir.name, ok, reason });
48
+ }
49
+
50
+ for (const row of rows) {
51
+ const mark = row.ok ? 'PASS' : 'FAIL';
52
+ process.stdout.write(`${mark}\t${row.skill}\t${row.reason}\n`);
53
+ }
54
+
55
+ process.stdout.write(`\n${rows.length - failed} passed, ${failed} failed\n`);
56
+ process.exit(failed ? 1 : 0);
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ /** Manifests that must share one semantic version. */
4
+ module.exports = [
5
+ 'package.json',
6
+ 'plugin.json',
7
+ '.codex-plugin/plugin.json',
8
+ '.claude-plugin/plugin.json',
9
+ '.cursor-plugin/plugin.json',
10
+ ];