highspot-cli 0.1.1 → 0.2.1

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 CHANGED
@@ -31,6 +31,12 @@ export HIGHSPOT_API_KEY_ID=hs_key_id_xxx
31
31
  export HIGHSPOT_API_KEY_SECRET=hs_key_secret_xxx
32
32
  ```
33
33
 
34
+ Or provide a precomputed Basic auth header directly:
35
+
36
+ ```bash
37
+ export HIGHSPOT_BASIC_AUTH="Basic <base64(id:secret)>"
38
+ ```
39
+
34
40
  Optional:
35
41
 
36
42
  ```bash
@@ -44,6 +50,10 @@ It is not implied by the API key:
44
50
  - `hs-user` sets an explicit user context for requests where impersonation is needed.
45
51
  - CLI flag precedence still applies, so `--hs-user` overrides `HIGHSPOT_HS_USER`.
46
52
 
53
+ Auth precedence:
54
+ - `HIGHSPOT_BASIC_AUTH` is used directly when set.
55
+ - Otherwise, `HIGHSPOT_API_KEY_ID` + `HIGHSPOT_API_KEY_SECRET` are used to compute `Authorization: Basic ...`.
56
+
47
57
  ## Config Files
48
58
 
49
59
  Config precedence (highest to lowest):
@@ -63,6 +73,7 @@ Example `.highspot-cli.json`:
63
73
  "maxRetries": 3,
64
74
  "retryDelayMs": 1200,
65
75
  "timeoutMs": 30000,
76
+ "basicAuth": "Basic <base64(id:secret)>",
66
77
  "apiKeyId": "hs_key_id_xxx",
67
78
  "apiKeySecret": "hs_key_secret_xxx"
68
79
  }
@@ -72,8 +83,7 @@ Example `.highspot-cli.json`:
72
83
 
73
84
  ```bash
74
85
  highspot search <query>
75
- highspot item <item-id>
76
- highspot content <item-id>
86
+ highspot get <item-id>
77
87
  highspot me
78
88
  ```
79
89
 
@@ -94,6 +104,15 @@ Global flags:
94
104
  - `--no-input`
95
105
  - `--no-color`
96
106
 
107
+ `get` command flags:
108
+
109
+ - `--format <value>`
110
+ - `--start <value>`
111
+ - `--meta-only` (skip content download)
112
+ - `-o, --output <path>` (explicit file path)
113
+ - `--output-dir <path>` (directory for auto-saved binary files)
114
+ - `-f, --force` (overwrite existing output file)
115
+
97
116
  Exit codes:
98
117
 
99
118
  - `0` success
@@ -105,8 +124,11 @@ Exit codes:
105
124
  ```bash
106
125
  highspot search "GoGuardian Teacher" --limit 10
107
126
  highspot search "Beacon" --sort-by date_added --plain
108
- highspot item it_abc123
109
- highspot content it_abc123 --format text/plain --plain
127
+ highspot get it_abc123 --meta-only
128
+ highspot get it_abc123 --format text/plain --plain
129
+ highspot get it_abc123
130
+ highspot get it_abc123 --output ./custom-filename.pdf
131
+ highspot get it_abc123 --output-dir ./downloads
110
132
  highspot me --json
111
133
  highspot search "Fleet" --dry-run
112
134
  ```
@@ -115,6 +137,9 @@ Behavior notes:
115
137
 
116
138
  - Prompts are not used; `--no-input` is accepted for automation consistency.
117
139
  - Primary data goes to stdout, errors go to stderr.
140
+ - `get` always fetches `/items/{id}` metadata first, then fetches `/items/{id}/content` unless `--meta-only` is set.
141
+ - Binary content is automatically saved to disk using Highspot `content_name` (canonical filename) when available.
142
+ - Use `--output` to force a specific filename/path, or `--output-dir` to control where auto-saved binaries are written.
118
143
 
119
144
  ## Development
120
145
 
@@ -0,0 +1,263 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, writeFile } from "node:fs/promises";
4
+ import { dirname, extname, join, resolve } from "node:path";
5
+ import { Args } from "@oclif/core";
6
+ import { BaseCommand } from "../lib/command.js";
7
+ import { contentFlags, globalFlags } from "../lib/flags.js";
8
+ export default class Get extends BaseCommand {
9
+ static description = "Fetch item metadata and content in one command";
10
+ static examples = [
11
+ "highspot get it_abc123",
12
+ "highspot get it_abc123 --meta-only",
13
+ "highspot get it_abc123 --output ./discover-guide.pdf",
14
+ "highspot get it_abc123 --output-dir ./downloads",
15
+ "highspot get it_abc123 --format text/plain --plain",
16
+ ];
17
+ static args = {
18
+ itemId: Args.string({
19
+ description: "Highspot item id",
20
+ required: false,
21
+ }),
22
+ };
23
+ static flags = {
24
+ ...globalFlags,
25
+ ...contentFlags,
26
+ };
27
+ async run() {
28
+ const { args, flags } = await this.parse(Get);
29
+ this.ensureOutputFlags(flags);
30
+ this.ensureVerbosityFlags(flags);
31
+ if (!args.itemId) {
32
+ this.fail("itemId is required", 2, flags);
33
+ }
34
+ const payload = {
35
+ operations: [
36
+ { endpoint: `/items/${encodeURIComponent(args.itemId)}` },
37
+ ...(flags["meta-only"]
38
+ ? []
39
+ : [
40
+ {
41
+ endpoint: `/items/${encodeURIComponent(args.itemId)}/content`,
42
+ query: {
43
+ ...(flags.format ? { format: flags.format } : {}),
44
+ ...(flags.start ? { start: flags.start } : {}),
45
+ },
46
+ },
47
+ ]),
48
+ ],
49
+ output: {
50
+ ...(flags.output ? { output: flags.output } : {}),
51
+ ...(flags["output-dir"] ? { outputDir: flags["output-dir"] } : {}),
52
+ force: flags.force,
53
+ },
54
+ headers: {
55
+ ...(flags["hs-user"] ? { "hs-user": flags["hs-user"] } : {}),
56
+ },
57
+ };
58
+ if (flags["dry-run"]) {
59
+ this.printJson(payload);
60
+ return;
61
+ }
62
+ try {
63
+ const client = this.clientFromFlags(flags);
64
+ const itemData = await client.getItem({
65
+ itemId: args.itemId,
66
+ hsUser: this.effectiveHsUser(flags),
67
+ });
68
+ const item = asItemRecord(itemData.item);
69
+ if (flags["meta-only"]) {
70
+ if (this.outputMode(flags) === "plain") {
71
+ writeItemPlain(item);
72
+ return;
73
+ }
74
+ this.printJson({ item });
75
+ return;
76
+ }
77
+ const contentData = await client.getItemContent({
78
+ itemId: args.itemId,
79
+ format: flags.format,
80
+ start: flags.start,
81
+ hsUser: this.effectiveHsUser(flags),
82
+ });
83
+ const explicitOutputPath = flags.output
84
+ ? resolve(process.cwd(), flags.output)
85
+ : undefined;
86
+ if (contentData.kind === "binary") {
87
+ const binaryOutputPath = explicitOutputPath ??
88
+ resolveBinaryOutputPath({
89
+ outputDir: flags["output-dir"],
90
+ item,
91
+ itemId: args.itemId,
92
+ contentType: contentData.contentType,
93
+ });
94
+ const fileResult = await writeBytesToFile({
95
+ bytes: contentData.bytes,
96
+ contentType: contentData.contentType,
97
+ force: flags.force,
98
+ item,
99
+ itemId: args.itemId,
100
+ outputPath: binaryOutputPath,
101
+ });
102
+ if (this.outputMode(flags) === "plain") {
103
+ writeFileResultPlain(fileResult);
104
+ return;
105
+ }
106
+ this.printJson(fileResult);
107
+ return;
108
+ }
109
+ if (explicitOutputPath) {
110
+ const fileResult = await writeInlineContentToFile({
111
+ content: contentData,
112
+ force: flags.force,
113
+ item,
114
+ itemId: args.itemId,
115
+ outputPath: explicitOutputPath,
116
+ });
117
+ if (this.outputMode(flags) === "plain") {
118
+ writeFileResultPlain(fileResult);
119
+ return;
120
+ }
121
+ this.printJson(fileResult);
122
+ return;
123
+ }
124
+ if (this.outputMode(flags) === "plain") {
125
+ writeInlineContentPlain(contentData);
126
+ return;
127
+ }
128
+ this.printJson({
129
+ item,
130
+ mode: "inline",
131
+ isBinary: false,
132
+ contentType: contentData.contentType,
133
+ content: contentData.content,
134
+ });
135
+ }
136
+ catch (error) {
137
+ this.handleError(error, flags);
138
+ }
139
+ }
140
+ }
141
+ function asItemRecord(value) {
142
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
143
+ return {};
144
+ }
145
+ return value;
146
+ }
147
+ function writeItemPlain(item) {
148
+ const id = asString(item.id);
149
+ const url = asString(item.url);
150
+ const title = asString(item.title);
151
+ const contentType = asString(item.content_type);
152
+ const contentName = asString(item.content_name);
153
+ process.stdout.write(`${id}\t${url}\t${title}\t${contentType}\t${contentName}\n`);
154
+ }
155
+ function writeInlineContentPlain(content) {
156
+ if (content.kind === "text") {
157
+ process.stdout.write(content.content);
158
+ if (!content.content.endsWith("\n")) {
159
+ process.stdout.write("\n");
160
+ }
161
+ return;
162
+ }
163
+ if (content.kind === "json") {
164
+ process.stdout.write(`${JSON.stringify(content.content, null, 2)}\n`);
165
+ return;
166
+ }
167
+ throw new Error("Binary content must be written to a file.");
168
+ }
169
+ function writeFileResultPlain(result) {
170
+ process.stdout.write(`${result.itemId}\t${result.outputPath}\t${result.contentType}\t${result.sizeBytes}\t${result.sha256}\n`);
171
+ }
172
+ function resolveBinaryOutputPath(args) {
173
+ const outputDir = args.outputDir
174
+ ? resolve(process.cwd(), args.outputDir)
175
+ : process.cwd();
176
+ const fileName = preferredBinaryFileName(args.item, args.itemId, args.contentType);
177
+ return join(outputDir, fileName);
178
+ }
179
+ function preferredBinaryFileName(item, itemId, contentType) {
180
+ const candidates = [
181
+ item.content_name,
182
+ item.filename,
183
+ item.file_name,
184
+ item.name,
185
+ item.title,
186
+ ]
187
+ .map((value) => asString(value).trim())
188
+ .filter((value) => value.length > 0);
189
+ const baseCandidate = candidates[0] ?? itemId;
190
+ const safeBase = sanitizeFileName(baseCandidate) || itemId;
191
+ if (extname(safeBase)) {
192
+ return safeBase;
193
+ }
194
+ const extension = extensionFromContentType(contentType) ?? "bin";
195
+ return `${safeBase}.${extension}`;
196
+ }
197
+ function sanitizeFileName(input) {
198
+ const invalidChars = new Set(["\\", "/", ":", "*", "?", '"', "<", ">", "|"]);
199
+ let output = "";
200
+ for (const char of input) {
201
+ const code = char.charCodeAt(0);
202
+ if (code >= 0 && code < 32) {
203
+ output += "_";
204
+ continue;
205
+ }
206
+ output += invalidChars.has(char) ? "_" : char;
207
+ }
208
+ return output.replace(/\s+/g, " ").trim();
209
+ }
210
+ function extensionFromContentType(contentType) {
211
+ const normalized = contentType.toLowerCase();
212
+ if (normalized.includes("pdf")) {
213
+ return "pdf";
214
+ }
215
+ if (normalized.includes("json")) {
216
+ return "json";
217
+ }
218
+ if (normalized.includes("csv")) {
219
+ return "csv";
220
+ }
221
+ if (normalized.startsWith("text/")) {
222
+ return "txt";
223
+ }
224
+ return undefined;
225
+ }
226
+ async function writeBytesToFile(args) {
227
+ await ensureWriteablePath(args.outputPath, args.force);
228
+ await writeFile(args.outputPath, args.bytes);
229
+ return {
230
+ contentType: args.contentType,
231
+ item: args.item,
232
+ itemId: args.itemId,
233
+ mode: "file",
234
+ outputPath: args.outputPath,
235
+ sha256: createHash("sha256").update(args.bytes).digest("hex"),
236
+ sizeBytes: args.bytes.byteLength,
237
+ };
238
+ }
239
+ async function writeInlineContentToFile(args) {
240
+ await ensureWriteablePath(args.outputPath, args.force);
241
+ const text = args.content.kind === "text"
242
+ ? args.content.content
243
+ : `${JSON.stringify(args.content.content, null, 2)}\n`;
244
+ await writeFile(args.outputPath, text, "utf8");
245
+ return {
246
+ contentType: args.content.contentType,
247
+ item: args.item,
248
+ itemId: args.itemId,
249
+ mode: "file",
250
+ outputPath: args.outputPath,
251
+ sha256: createHash("sha256").update(text, "utf8").digest("hex"),
252
+ sizeBytes: Buffer.byteLength(text, "utf8"),
253
+ };
254
+ }
255
+ async function ensureWriteablePath(path, force) {
256
+ await mkdir(dirname(path), { recursive: true });
257
+ if (existsSync(path) && !force) {
258
+ throw new Error(`Output file already exists: ${path}. Use --force to overwrite.`);
259
+ }
260
+ }
261
+ function asString(value) {
262
+ return typeof value === "string" ? value : "";
263
+ }
package/dist/lib/api.js CHANGED
@@ -1,4 +1,3 @@
1
- import { Buffer } from "node:buffer";
2
1
  const TEXTUAL_CONTENT_TYPE_HINTS = [
3
2
  "application/xml",
4
3
  "application/xhtml+xml",
@@ -40,7 +39,7 @@ export class HighspotClient {
40
39
  #retryDelayMs;
41
40
  #timeoutMs;
42
41
  constructor(config) {
43
- this.#authorizationHeader = `Basic ${Buffer.from(`${config.apiKeyId}:${config.apiKeySecret}`).toString("base64")}`;
42
+ this.#authorizationHeader = config.authorizationHeader;
44
43
  this.#endpoint = sanitizeEndpoint(config.endpoint);
45
44
  this.#hsUser = config.hsUser;
46
45
  this.#maxRetries = config.maxRetries;
@@ -160,8 +159,8 @@ export class HighspotClient {
160
159
  if (contentType.includes("json")) {
161
160
  const payload = (await response.json());
162
161
  return {
162
+ kind: "json",
163
163
  content: payload,
164
- contentEncoding: "json",
165
164
  contentType,
166
165
  isBinary: false,
167
166
  isJson: true,
@@ -171,8 +170,8 @@ export class HighspotClient {
171
170
  if (!looksTextual(contentType)) {
172
171
  const binary = await response.arrayBuffer();
173
172
  return {
174
- content: Buffer.from(binary).toString("base64"),
175
- contentEncoding: "base64",
173
+ kind: "binary",
174
+ bytes: new Uint8Array(binary),
176
175
  contentLength: binary.byteLength,
177
176
  contentType,
178
177
  isBinary: true,
@@ -182,8 +181,8 @@ export class HighspotClient {
182
181
  }
183
182
  const textContent = await response.text();
184
183
  return {
184
+ kind: "text",
185
185
  content: textContent,
186
- contentEncoding: "utf8",
187
186
  contentLength: textContent.length,
188
187
  contentType,
189
188
  isBinary: false,
@@ -43,6 +43,9 @@ export class BaseCommand extends Command {
43
43
  this.exit(exitCode);
44
44
  }
45
45
  handleError(error, flags) {
46
+ if (error instanceof Error && /^EEXIT:\s*\d+/.test(error.message)) {
47
+ throw error;
48
+ }
46
49
  const mode = this.outputMode(flags);
47
50
  if (error instanceof ApiError) {
48
51
  writeError(mode, formatApiError(error));
@@ -51,7 +54,7 @@ export class BaseCommand extends Command {
51
54
  if (error instanceof ConfigError) {
52
55
  writeError(mode, {
53
56
  error: error.message,
54
- hint: "Set HIGHSPOT_API_KEY_ID and HIGHSPOT_API_KEY_SECRET, or configure .highspot-cli.json.",
57
+ hint: "Set HIGHSPOT_BASIC_AUTH, or set HIGHSPOT_API_KEY_ID and HIGHSPOT_API_KEY_SECRET, or configure .highspot-cli.json.",
55
58
  });
56
59
  this.exit(2);
57
60
  }
@@ -1,3 +1,4 @@
1
+ import { Buffer } from "node:buffer";
1
2
  import { existsSync, readFileSync } from "node:fs";
2
3
  import { join } from "node:path";
3
4
  export class ConfigError extends Error {
@@ -43,6 +44,9 @@ function normalize(config) {
43
44
  maxRetries: toInteger(config.maxRetries),
44
45
  retryDelayMs: toInteger(config.retryDelayMs),
45
46
  timeoutMs: toInteger(config.timeoutMs),
47
+ basicAuth: typeof config.basicAuth === "string"
48
+ ? config.basicAuth.trim()
49
+ : undefined,
46
50
  apiKeyId: typeof config.apiKeyId === "string" ? config.apiKeyId.trim() : undefined,
47
51
  apiKeySecret: typeof config.apiKeySecret === "string"
48
52
  ? config.apiKeySecret.trim()
@@ -77,10 +81,38 @@ function fromEnv() {
77
81
  maxRetries: process.env.HIGHSPOT_MAX_RETRIES,
78
82
  retryDelayMs: process.env.HIGHSPOT_RETRY_DELAY_MS,
79
83
  timeoutMs: process.env.HIGHSPOT_TIMEOUT_MS,
84
+ basicAuth: process.env.HIGHSPOT_BASIC_AUTH,
80
85
  apiKeyId: process.env.HIGHSPOT_API_KEY_ID,
81
86
  apiKeySecret: process.env.HIGHSPOT_API_KEY_SECRET,
82
87
  });
83
88
  }
89
+ function normalizeAuthorizationHeader(value) {
90
+ const trimmed = value?.trim();
91
+ if (!trimmed) {
92
+ return undefined;
93
+ }
94
+ if (/^Basic\s+/i.test(trimmed)) {
95
+ return trimmed;
96
+ }
97
+ return `Basic ${trimmed}`;
98
+ }
99
+ function resolveAuthorizationHeader(config) {
100
+ const explicitBasicAuth = normalizeAuthorizationHeader(config.basicAuth);
101
+ if (explicitBasicAuth) {
102
+ return explicitBasicAuth;
103
+ }
104
+ if (!config.apiKeyId && !config.apiKeySecret) {
105
+ throw new ConfigError("Missing auth configuration. Set HIGHSPOT_BASIC_AUTH, or set both HIGHSPOT_API_KEY_ID and HIGHSPOT_API_KEY_SECRET.");
106
+ }
107
+ if (!config.apiKeyId) {
108
+ throw new ConfigError("Missing HIGHSPOT_API_KEY_ID. Set HIGHSPOT_BASIC_AUTH, or configure apiKeyId with apiKeySecret in .highspot-cli.json.");
109
+ }
110
+ if (!config.apiKeySecret) {
111
+ throw new ConfigError("Missing HIGHSPOT_API_KEY_SECRET. Set HIGHSPOT_BASIC_AUTH, or configure apiKeySecret with apiKeyId in .highspot-cli.json.");
112
+ }
113
+ const token = Buffer.from(`${config.apiKeyId}:${config.apiKeySecret}`).toString("base64");
114
+ return `Basic ${token}`;
115
+ }
84
116
  function overlay(base, next) {
85
117
  return {
86
118
  ...base,
@@ -96,19 +128,13 @@ export function loadResolvedConfig(overrides = {}) {
96
128
  const maxRetries = toInteger(config.maxRetries) ?? DEFAULT_MAX_RETRIES;
97
129
  const retryDelayMs = toInteger(config.retryDelayMs) ?? DEFAULT_RETRY_DELAY_MS;
98
130
  const timeoutMs = toInteger(config.timeoutMs) ?? DEFAULT_TIMEOUT_MS;
99
- if (!config.apiKeyId) {
100
- throw new ConfigError("Missing HIGHSPOT_API_KEY_ID. Set env vars or configure apiKeyId in .highspot-cli.json.");
101
- }
102
- if (!config.apiKeySecret) {
103
- throw new ConfigError("Missing HIGHSPOT_API_KEY_SECRET. Set env vars or configure apiKeySecret in .highspot-cli.json.");
104
- }
131
+ const authorizationHeader = resolveAuthorizationHeader(config);
105
132
  return {
106
133
  endpoint: config.endpoint || DEFAULT_ENDPOINT,
107
134
  hsUser: config.hsUser,
108
135
  maxRetries,
109
136
  retryDelayMs,
110
137
  timeoutMs,
111
- apiKeyId: config.apiKeyId,
112
- apiKeySecret: config.apiKeySecret,
138
+ authorizationHeader,
113
139
  };
114
140
  }
package/dist/lib/flags.js CHANGED
@@ -72,4 +72,20 @@ export const contentFlags = {
72
72
  start: Flags.string({
73
73
  description: "Optional cursor/start token for paginated content",
74
74
  }),
75
+ output: Flags.string({
76
+ char: "o",
77
+ description: "Write content body to a specific file path",
78
+ }),
79
+ "output-dir": Flags.string({
80
+ description: "Directory for auto-saved binary content (default: current working directory)",
81
+ }),
82
+ "meta-only": Flags.boolean({
83
+ description: "Return item metadata only (skip content download)",
84
+ default: false,
85
+ }),
86
+ force: Flags.boolean({
87
+ char: "f",
88
+ description: "Allow overwriting an existing output file",
89
+ default: false,
90
+ }),
75
91
  };
package/dist/lib/help.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Help as OclifHelp } from "@oclif/core";
2
- const AUTH_TEXT = "Requires HIGHSPOT_API_KEY_ID and HIGHSPOT_API_KEY_SECRET via env or config file.";
2
+ const AUTH_TEXT = "Requires HIGHSPOT_BASIC_AUTH, or HIGHSPOT_API_KEY_ID and HIGHSPOT_API_KEY_SECRET, via env or config file.";
3
3
  const CONFIG_TEXT = "Precedence: flags > env > project config (.highspot-cli.json) > user config (~/.config/highspot-cli/config.json) > system config.";
4
4
  export default class Help extends OclifHelp {
5
5
  formatRoot() {
@@ -9,8 +9,8 @@ export default class Help extends OclifHelp {
9
9
  const examples = this.section("EXAMPLES", this.renderList([
10
10
  ["highspot --help"],
11
11
  ['highspot search "GoGuardian Teacher" --limit 5'],
12
- ["highspot item it_abc123 --plain"],
13
- ["highspot content it_abc123 --format text/plain --plain"],
12
+ ["highspot get it_abc123 --meta-only"],
13
+ ["highspot get it_abc123 --output ./discover-guide.pdf"],
14
14
  ["highspot me --json"],
15
15
  ], { indentation: 2, spacer: "\n", stripAnsi: this.opts.stripAnsi }));
16
16
  return `${base}\n\n${auth}\n\n${config}\n\n${examples}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "highspot-cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Agent-first CLI for the Highspot API",
5
5
  "license": "MIT",
6
6
  "author": "Advait Shinde",
@@ -18,7 +18,7 @@
18
18
  "highspot-cli": "dist/bin/highspot.js"
19
19
  },
20
20
  "scripts": {
21
- "build": "tsc -p tsconfig.build.json",
21
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
22
22
  "dev": "npm run build && node dist/bin/highspot.js",
23
23
  "lint": "biome check .",
24
24
  "format": "biome format --write .",
@@ -1,70 +0,0 @@
1
- import { Args } from "@oclif/core";
2
- import { BaseCommand } from "../lib/command.js";
3
- import { contentFlags, globalFlags } from "../lib/flags.js";
4
- export default class Content extends BaseCommand {
5
- static description = "Fetch Highspot item content";
6
- static examples = [
7
- "highspot content it_abc123",
8
- "highspot content it_abc123 --format text/plain --plain",
9
- "highspot content it_abc123 --start cursor-2",
10
- ];
11
- static args = {
12
- itemId: Args.string({
13
- description: "Highspot item id",
14
- required: false,
15
- }),
16
- };
17
- static flags = {
18
- ...globalFlags,
19
- ...contentFlags,
20
- };
21
- async run() {
22
- const { args, flags } = await this.parse(Content);
23
- this.ensureOutputFlags(flags);
24
- this.ensureVerbosityFlags(flags);
25
- if (!args.itemId) {
26
- this.fail("itemId is required", 2, flags);
27
- }
28
- const payload = {
29
- endpoint: `/items/${encodeURIComponent(args.itemId)}/content`,
30
- query: {
31
- ...(flags.format ? { format: flags.format } : {}),
32
- ...(flags.start ? { start: flags.start } : {}),
33
- },
34
- headers: {
35
- ...(flags["hs-user"] ? { "hs-user": flags["hs-user"] } : {}),
36
- },
37
- };
38
- if (flags["dry-run"]) {
39
- this.printJson(payload);
40
- return;
41
- }
42
- try {
43
- const client = this.clientFromFlags(flags);
44
- const data = await client.getItemContent({
45
- itemId: args.itemId,
46
- format: flags.format,
47
- start: flags.start,
48
- hsUser: this.effectiveHsUser(flags),
49
- });
50
- if (this.outputMode(flags) === "plain") {
51
- writeContentPlain(data.content);
52
- return;
53
- }
54
- this.printJson(data);
55
- }
56
- catch (error) {
57
- this.handleError(error, flags);
58
- }
59
- }
60
- }
61
- function writeContentPlain(content) {
62
- if (typeof content === "string") {
63
- process.stdout.write(content);
64
- if (!content.endsWith("\n")) {
65
- process.stdout.write("\n");
66
- }
67
- return;
68
- }
69
- process.stdout.write(`${JSON.stringify(content, null, 2)}\n`);
70
- }
@@ -1,66 +0,0 @@
1
- import { Args } from "@oclif/core";
2
- import { BaseCommand } from "../lib/command.js";
3
- import { globalFlags } from "../lib/flags.js";
4
- export default class Item extends BaseCommand {
5
- static description = "Fetch metadata for a Highspot item";
6
- static examples = [
7
- "highspot item it_abc123",
8
- "highspot item it_abc123 --hs-user user@example.com",
9
- "highspot item it_abc123 --plain",
10
- ];
11
- static args = {
12
- itemId: Args.string({
13
- description: "Highspot item id",
14
- required: false,
15
- }),
16
- };
17
- static flags = {
18
- ...globalFlags,
19
- };
20
- async run() {
21
- const { args, flags } = await this.parse(Item);
22
- this.ensureOutputFlags(flags);
23
- this.ensureVerbosityFlags(flags);
24
- if (!args.itemId) {
25
- this.fail("itemId is required", 2, flags);
26
- }
27
- const payload = {
28
- endpoint: `/items/${encodeURIComponent(args.itemId)}`,
29
- headers: {
30
- ...(flags["hs-user"] ? { "hs-user": flags["hs-user"] } : {}),
31
- },
32
- };
33
- if (flags["dry-run"]) {
34
- this.printJson(payload);
35
- return;
36
- }
37
- try {
38
- const client = this.clientFromFlags(flags);
39
- const data = await client.getItem({
40
- itemId: args.itemId,
41
- hsUser: this.effectiveHsUser(flags),
42
- });
43
- if (this.outputMode(flags) === "plain") {
44
- writeItemPlain(data.item);
45
- return;
46
- }
47
- this.printJson(data);
48
- }
49
- catch (error) {
50
- this.handleError(error, flags);
51
- }
52
- }
53
- }
54
- function writeItemPlain(item) {
55
- if (typeof item !== "object" || item === null || Array.isArray(item)) {
56
- process.stdout.write(`${JSON.stringify(item)}\n`);
57
- return;
58
- }
59
- const record = item;
60
- const id = typeof record.id === "string" ? record.id : "";
61
- const title = typeof record.title === "string" ? record.title : "";
62
- const url = typeof record.url === "string" ? record.url : "";
63
- const contentType = typeof record.content_type === "string" ? record.content_type : "";
64
- const description = typeof record.description === "string" ? record.description : "";
65
- process.stdout.write(`${id}\t${url}\t${title}\t${contentType}\t${description}\n`);
66
- }