@operatorkit/hs 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.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # @operatorkit/hs
2
+
3
+ A command-line interface for the [HelpScout](https://www.helpscout.com/) API. Manage mailboxes, conversations, customers, tags, users, workflows, webhooks, and knowledge base content from the terminal.
4
+
5
+ Ships with an embedded [MCP](https://modelcontextprotocol.io/) server for AI-assisted workflows.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ # Run directly (no install)
11
+ npx -y @operatorkit/hs version
12
+
13
+ # Global install
14
+ npm i -g @operatorkit/hs
15
+ ```
16
+
17
+ On install, the matching platform binary is downloaded from GitHub Releases. Supported: `linux`, `darwin`, `windows` on `amd64`/`arm64`.
18
+
19
+ ## Quick start
20
+
21
+ ```bash
22
+ # Authenticate (Inbox API — OAuth2 app credentials)
23
+ npx -y @operatorkit/hs inbox auth login
24
+
25
+ # Authenticate (Docs API — API key)
26
+ npx -y @operatorkit/hs docs auth login
27
+
28
+ # List mailboxes
29
+ npx -y @operatorkit/hs inbox mailboxes list
30
+
31
+ # List conversations
32
+ npx -y @operatorkit/hs inbox conversations list --status active
33
+
34
+ # Search articles
35
+ npx -y @operatorkit/hs docs articles search --query "getting started"
36
+ ```
37
+
38
+ ## API coverage
39
+
40
+ ### Inbox API (`hs inbox ...`)
41
+
42
+ Full CRUD for all Inbox API resources:
43
+
44
+ - **Conversations** — list, get, create, update, delete, tags, custom fields, snooze
45
+ - **Threads** — list, reply, note, create (chat/customer/phone), update, source, source-rfc822
46
+ - **Customers** — list, get, create, update, overwrite, delete
47
+ - **Mailboxes** — list, get, folders, custom fields, routing
48
+ - **Tags** — list, get
49
+ - **Users** — list, get, me, delete, status
50
+ - **Teams** — list, members
51
+ - **Organizations** — list, get, create, update, delete, conversations, customers, properties
52
+ - **Workflows** — list, run, update-status
53
+ - **Webhooks** — list, get, create, update, delete
54
+ - **Saved Replies** — list, get, create, update, delete
55
+ - **Reports** — chats, company, conversations, customers, docs, email, productivity, ratings, users
56
+ - **Properties** — customer properties, conversation properties
57
+ - **Ratings** — get
58
+ - **Attachments** — upload, list, get, delete
59
+
60
+ ### Docs API (`hs docs ...`)
61
+
62
+ Full CRUD for all Docs API resources:
63
+
64
+ - **Articles** — list, search, get, create, update, delete, upload, revisions, drafts, related, view count
65
+ - **Categories** — list, get, create, update, reorder, delete
66
+ - **Collections** — list, get, create, update, delete
67
+ - **Sites** — list, get, create, update, delete, restrictions
68
+ - **Redirects** — list, get, find, create, update, delete
69
+ - **Assets** — article upload, settings upload
70
+
71
+ ## MCP server
72
+
73
+ Start the embedded MCP server for AI-assisted workflows:
74
+
75
+ ```bash
76
+ npx -y @operatorkit/hs mcp -t stdio
77
+ ```
78
+
79
+ 124 MCP tools auto-discovered from the command tree (85 inbox + 39 docs). Management commands (`auth`, `config`, `permissions`) are excluded.
80
+
81
+ ### MCP client config
82
+
83
+ ```json
84
+ {
85
+ "mcpServers": {
86
+ "helpscout": {
87
+ "command": "npx",
88
+ "args": ["-y", "@operatorkit/hs", "mcp", "-t", "stdio"],
89
+ "env": {
90
+ "HS_INBOX_APP_ID": "your-app-id",
91
+ "HS_INBOX_APP_SECRET": "your-app-secret",
92
+ "HS_DOCS_API_KEY": "your-docs-api-key",
93
+ "HS_INBOX_PERMISSIONS": "*:read"
94
+ }
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ ## Output formats
101
+
102
+ | Format | Flag | Description |
103
+ |--------|------|-------------|
104
+ | Table | `--format table` | Human-readable table (default) |
105
+ | JSON | `--format json` | Clean JSON — HAL noise stripped, HTML→markdown, fields normalized |
106
+ | JSON-full | `--format json-full` | Raw API response, pretty-printed |
107
+ | CSV | `--format csv` | RFC 4180 CSV with headers |
108
+
109
+ ## Safety features
110
+
111
+ - **PII redaction** — deterministic, layered pipeline (structured fields + free-text + source payloads). Modes: `off`, `customers`, `all`.
112
+ - **Permissions** — allowlist-based `resource:operation` pairs restrict which actions are permitted. Set via `HS_INBOX_PERMISSIONS` / `HS_DOCS_PERMISSIONS`.
113
+ - **Rate limiting** — built-in rate limiters respect API quotas (Inbox: 200/min, Docs: 2000/10min).
114
+
115
+ ## Environment variables
116
+
117
+ | Variable | Description |
118
+ |----------|-------------|
119
+ | `HS_INBOX_APP_ID` | Inbox API App ID |
120
+ | `HS_INBOX_APP_SECRET` | Inbox API App Secret |
121
+ | `HS_DOCS_API_KEY` | Docs API key |
122
+ | `HS_FORMAT` | Default output format |
123
+ | `HS_INBOX_PII_MODE` | PII redaction mode: `off`, `customers`, `all` |
124
+ | `HS_INBOX_PERMISSIONS` | Inbox permission policy |
125
+ | `HS_DOCS_PERMISSIONS` | Docs permission policy |
126
+
127
+ ## Links
128
+
129
+ - [GitHub](https://github.com/operator-kit/hs-cli)
130
+ - [Issues](https://github.com/operator-kit/hs-cli/issues)
131
+ - [HelpScout API docs](https://developer.helpscout.com/)
132
+
133
+ ## License
134
+
135
+ [MIT](https://github.com/operator-kit/hs-cli/blob/main/LICENSE)
package/bin/hs.js ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { spawn } = require("node:child_process");
6
+
7
+ function resolveTarget() {
8
+ const platformMap = {
9
+ linux: "linux",
10
+ darwin: "darwin",
11
+ win32: "windows"
12
+ };
13
+ const archMap = {
14
+ x64: "amd64",
15
+ arm64: "arm64"
16
+ };
17
+
18
+ const os = platformMap[process.platform];
19
+ const arch = archMap[process.arch];
20
+ if (!os || !arch) {
21
+ throw new Error(
22
+ `Unsupported platform/arch: ${process.platform}/${process.arch}`
23
+ );
24
+ }
25
+ return { os, arch };
26
+ }
27
+
28
+ function resolveBinaryPath() {
29
+ const target = resolveTarget();
30
+ const exe = process.platform === "win32" ? "hs.exe" : "hs";
31
+ return path.join(__dirname, "..", "dist", `${target.os}-${target.arch}`, exe);
32
+ }
33
+
34
+ function main() {
35
+ const binaryPath = resolveBinaryPath();
36
+ if (!fs.existsSync(binaryPath)) {
37
+ console.error(
38
+ "hs binary is not installed. Reinstall package: npm i -g @operatorkit/hs"
39
+ );
40
+ process.exit(1);
41
+ }
42
+
43
+ const child = spawn(binaryPath, process.argv.slice(2), {
44
+ stdio: "inherit"
45
+ });
46
+
47
+ child.on("error", (err) => {
48
+ console.error(`Failed to start hs: ${err.message}`);
49
+ process.exit(1);
50
+ });
51
+
52
+ child.on("exit", (code, signal) => {
53
+ if (signal) {
54
+ process.kill(process.pid, signal);
55
+ return;
56
+ }
57
+ process.exit(code ?? 1);
58
+ });
59
+ }
60
+
61
+ main();
package/bin/install.js ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const os = require("node:os");
6
+ const https = require("node:https");
7
+ const { pipeline } = require("node:stream/promises");
8
+
9
+ const AdmZip = require("adm-zip");
10
+ const tar = require("tar");
11
+
12
+ const REPO_OWNER = "operator-kit";
13
+ const REPO_NAME = "hs-cli";
14
+
15
+ function resolveTarget() {
16
+ const platformMap = {
17
+ linux: "linux",
18
+ darwin: "darwin",
19
+ win32: "windows"
20
+ };
21
+ const archMap = {
22
+ x64: "amd64",
23
+ arm64: "arm64"
24
+ };
25
+
26
+ const osName = platformMap[process.platform];
27
+ const archName = archMap[process.arch];
28
+ if (!osName || !archName) {
29
+ throw new Error(`Unsupported platform/arch: ${process.platform}/${process.arch}`);
30
+ }
31
+
32
+ return {
33
+ os: osName,
34
+ arch: archName,
35
+ archiveExt: osName === "windows" ? ".zip" : ".tar.gz",
36
+ binaryName: osName === "windows" ? "hs.exe" : "hs"
37
+ };
38
+ }
39
+
40
+ function packageVersion() {
41
+ const pkgPath = path.join(__dirname, "..", "package.json");
42
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
43
+ return String(pkg.version || "").trim();
44
+ }
45
+
46
+ function isDevVersion(version) {
47
+ return (
48
+ !version ||
49
+ version === "0.0.0-development" ||
50
+ version === "0.0.0" ||
51
+ version.includes("dev")
52
+ );
53
+ }
54
+
55
+ function releaseAssetURL(version, target) {
56
+ const tag = `v${version}`;
57
+ const asset = `hs_${tag}_${target.os}_${target.arch}${target.archiveExt}`;
58
+ return {
59
+ tag,
60
+ asset,
61
+ url: `https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/${tag}/${asset}`
62
+ };
63
+ }
64
+
65
+ function downloadFile(url, destination, redirects = 5) {
66
+ return new Promise((resolve, reject) => {
67
+ https.get(url, (response) => {
68
+ if (
69
+ response.statusCode &&
70
+ response.statusCode >= 300 &&
71
+ response.statusCode < 400 &&
72
+ response.headers.location
73
+ ) {
74
+ if (redirects <= 0) {
75
+ reject(new Error(`Too many redirects while downloading ${url}`));
76
+ return;
77
+ }
78
+ response.resume();
79
+ resolve(downloadFile(response.headers.location, destination, redirects - 1));
80
+ return;
81
+ }
82
+
83
+ if (response.statusCode !== 200) {
84
+ const code = response.statusCode || "unknown";
85
+ response.resume();
86
+ reject(new Error(`Download failed (${code}): ${url}`));
87
+ return;
88
+ }
89
+
90
+ const file = fs.createWriteStream(destination);
91
+ pipeline(response, file)
92
+ .then(() => resolve())
93
+ .catch(reject);
94
+ }).on("error", reject);
95
+ });
96
+ }
97
+
98
+ async function extractArchive(archivePath, target, distDir) {
99
+ if (target.archiveExt === ".zip") {
100
+ const zip = new AdmZip(archivePath);
101
+ zip.extractAllTo(distDir, true);
102
+ return;
103
+ }
104
+ await tar.x({
105
+ file: archivePath,
106
+ cwd: distDir
107
+ });
108
+ }
109
+
110
+ function findFile(rootDir, fileName) {
111
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
112
+ for (const entry of entries) {
113
+ const entryPath = path.join(rootDir, entry.name);
114
+ if (entry.isFile() && entry.name === fileName) {
115
+ return entryPath;
116
+ }
117
+ if (entry.isDirectory()) {
118
+ const nested = findFile(entryPath, fileName);
119
+ if (nested) {
120
+ return nested;
121
+ }
122
+ }
123
+ }
124
+ return "";
125
+ }
126
+
127
+ async function install() {
128
+ const version = packageVersion();
129
+ if (isDevVersion(version)) {
130
+ console.log("Skipping hs binary download for development package version.");
131
+ return;
132
+ }
133
+
134
+ const target = resolveTarget();
135
+ const release = releaseAssetURL(version, target);
136
+ const distDir = path.join(__dirname, "..", "dist", `${target.os}-${target.arch}`);
137
+ await fs.promises.mkdir(distDir, { recursive: true });
138
+
139
+ const tempArchivePath = path.join(
140
+ os.tmpdir(),
141
+ `hs-${target.os}-${target.arch}-${Date.now()}${target.archiveExt}`
142
+ );
143
+
144
+ console.log(`Downloading hs ${release.tag} (${target.os}/${target.arch})...`);
145
+ await downloadFile(release.url, tempArchivePath);
146
+ await extractArchive(tempArchivePath, target, distDir);
147
+ fs.unlinkSync(tempArchivePath);
148
+
149
+ const binaryPath = path.join(distDir, target.binaryName);
150
+ if (!fs.existsSync(binaryPath)) {
151
+ const discovered = findFile(distDir, target.binaryName);
152
+ if (!discovered) {
153
+ throw new Error(`Binary ${target.binaryName} not found in downloaded archive.`);
154
+ }
155
+ fs.copyFileSync(discovered, binaryPath);
156
+ }
157
+
158
+ if (target.binaryName === "hs") {
159
+ await fs.promises.chmod(binaryPath, 0o755);
160
+ }
161
+
162
+ console.log(`Installed hs binary to ${binaryPath}`);
163
+ }
164
+
165
+ install().catch((err) => {
166
+ console.error(`Failed to install hs binary: ${err.message}`);
167
+ process.exit(1);
168
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@operatorkit/hs",
3
+ "version": "0.1.0",
4
+ "description": "HelpScout CLI with embedded MCP server",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/operator-kit/hs-cli",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/operator-kit/hs-cli.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/operator-kit/hs-cli/issues"
13
+ },
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "bin": {
18
+ "hs": "bin/hs.js"
19
+ },
20
+ "files": [
21
+ "bin",
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "scripts": {
26
+ "postinstall": "node ./bin/install.js"
27
+ },
28
+ "dependencies": {
29
+ "adm-zip": "^0.5.16",
30
+ "tar": "^7.4.3"
31
+ }
32
+ }