@usesocial/cli 0.3.1 → 0.3.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @usesocial/cli
2
2
 
3
+ ## 0.3.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#12](https://github.com/usesocial/monorepo/pull/12) [`a85b525`](https://github.com/usesocial/monorepo/commit/a85b525d36908cf25da92a0538b77ac39eb817c6) Thanks [@CyrusNuevoDia](https://github.com/CyrusNuevoDia)! - Read structured write payloads from stdin as JSON objects and remove `--body` from stdin-backed post, comment, and message commands. Plain stdin still becomes body text; valid non-object JSON now fails fast instead of being sent.
8
+
3
9
  ## 0.3.1
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -74,8 +74,7 @@ connected account.
74
74
  ### Body text
75
75
 
76
76
  Commands that create a post, comment, message, or connection note read the body
77
- from **stdin** — there is no text argument. Pipe it in, or pass a raw `--body`
78
- JSON object:
77
+ from **stdin** — there is no text argument. Pipe it in:
79
78
 
80
79
  ```sh
81
80
  echo "gm" | social x post
@@ -83,8 +82,9 @@ social linkedin post < draft.md
83
82
  social linkedin requests send @handle < note.txt # optional connection note
84
83
  ```
85
84
 
86
- On an interactive terminal with nothing piped (and no `--body`), these commands
87
- fail fast rather than hang.
85
+ On an interactive terminal with nothing piped, required body-text commands fail
86
+ fast rather than hang. To send advanced payload fields, pipe a JSON object via
87
+ stdin; if stdin parses as JSON but is not an object, the command rejects it.
88
88
 
89
89
  ### Top level
90
90
 
package/dist/index.mjs CHANGED
@@ -12971,6 +12971,7 @@ const positionalDisplayKeyFor$1 = (op, param) => op.config.paramLabels?.[param.n
12971
12971
  const bodyTextDisplayKeyFor$1 = (op) => op.config.bodyTextArg === void 0 ? void 0 : op.config.paramLabels?.[op.config.bodyTextArg] ?? op.config.bodyTextArg;
12972
12972
  const hasStdinBodyText$1 = (op) => op.config.bodyTextArg !== void 0;
12973
12973
  const stdinBodyTextRequired$1 = (op) => hasStdinBodyText$1(op) && op.config.bodyTextRequired !== false;
12974
+ const hasBodyOption = (op) => op.hasBody && !hasStdinBodyText$1(op);
12974
12975
  const idempotencyFor$1 = (op) => {
12975
12976
  if (op.endpoint.capability === "read") return {
12976
12977
  safe: true,
@@ -13062,7 +13063,7 @@ const exampleCommandFor$1 = (op, commandName, root) => {
13062
13063
  parts.push(param.in === "path" || param.kind !== "array" ? hint : `${hint}...`);
13063
13064
  }
13064
13065
  if (hasStdinBodyText$1(op)) return `echo "..." | ${parts.join(" ")}`;
13065
- if (op.hasBody) parts.push("--body", "'{...}'");
13066
+ if (hasBodyOption(op)) parts.push("--body", "'{...}'");
13066
13067
  return parts.join(" ");
13067
13068
  };
13068
13069
  const operationModeFor$1 = (op) => {
@@ -13106,7 +13107,7 @@ const helpDescriptionFor$1 = (op, commandName, root) => {
13106
13107
  ...root ? [] : [op.config.group],
13107
13108
  commandName
13108
13109
  ].join(" ");
13109
- return `${description} | ${usage}${hasStdinBodyText$1(op) ? ` | body text is read from stdin: \`echo "..." | ${label}\` (or pass --body)` : ""}`;
13110
+ return `${description} | ${usage}${hasStdinBodyText$1(op) ? ` | body text is read from stdin: \`echo "..." | ${label}\`` : ""}`;
13110
13111
  };
13111
13112
  const commandPathFor = (op) => `linkedin ${op.config.root ? op.config.command : `${op.config.group} ${op.config.command}`}`;
13112
13113
  const contractInputSchemaName = (op) => op.hasBody ? `${op.schemaBaseName}ContractInput` : `${op.schemaBaseName}Input`;
@@ -13160,7 +13161,7 @@ const buildArgs$1 = (op) => {
13160
13161
  description: param.description
13161
13162
  };
13162
13163
  }
13163
- if (op.hasBody) args.body = {
13164
+ if (hasBodyOption(op)) args.body = {
13164
13165
  type: "string",
13165
13166
  description: "JSON request body"
13166
13167
  };
@@ -13252,7 +13253,7 @@ const buildBody = (deps, op, input) => {
13252
13253
  const bodyTextArg = op.config.bodyTextArg;
13253
13254
  const bodyParamEntries = Object.entries(op.config.bodyParams ?? {});
13254
13255
  if (!bodyTextArg && bodyParamEntries.length === 0) return input.body === void 0 ? void 0 : deps.parseJSONObject("--body", input.body);
13255
- const bodyFromJSON = input.body === void 0 ? void 0 : deps.parseJSONObject("--body", input.body);
13256
+ const bodyFromJSON = input.body === void 0 ? void 0 : deps.parseJSONObject(hasStdinBodyText$1(op) ? "stdin" : "--body", input.body);
13256
13257
  const bodyText = bodyTextArg && input[bodyTextArg] !== void 0 ? deps.parseBodyText(`<${bodyTextDisplayKeyFor$1(op) ?? bodyTextArg}>`, input[bodyTextArg]) : void 0;
13257
13258
  const bodyParamValues = /* @__PURE__ */ new Map();
13258
13259
  for (const [name, param] of bodyParamEntries) {
@@ -13266,7 +13267,7 @@ const buildBody = (deps, op, input) => {
13266
13267
  }
13267
13268
  if (bodyTextArg && bodyText !== void 0) body[bodyTextArg] = bodyText;
13268
13269
  if (op.config.bodyTextRequired !== false && (Boolean(bodyTextArg) || bodyParamEntries.some(([, param]) => param.required)) && bodyFromJSON === void 0) {
13269
- if ((bodyTextArg ? bodyText === void 0 : false) || bodyParamEntries.some(([name, param]) => param.required && bodyParamValues.get(name) === void 0)) throw new UsageError(bodyTextArg && bodyParamEntries.length === 0 ? `Pass <${bodyTextDisplayKeyFor$1(op) ?? bodyTextArg}> or --body.` : "Pass required body positional arguments or --body.");
13270
+ if ((bodyTextArg ? bodyText === void 0 : false) || bodyParamEntries.some(([name, param]) => param.required && bodyParamValues.get(name) === void 0)) throw new UsageError(bodyTextArg && bodyParamEntries.length === 0 ? `Pass <${bodyTextDisplayKeyFor$1(op) ?? bodyTextArg}>.` : "Pass required body positional arguments or --body.");
13270
13271
  }
13271
13272
  return body;
13272
13273
  };
@@ -13276,7 +13277,7 @@ const checkBodyRequired = (op, args) => {
13276
13277
  const bodyTextArg = op.config.bodyTextArg;
13277
13278
  if (bodyTextArg && op.config.bodyTextRequired !== false) expressions.push(args[bodyTextDisplayKeyFor$1(op) ?? bodyTextArg] === void 0);
13278
13279
  for (const [name, param] of Object.entries(op.config.bodyParams ?? {})) if (param.required) expressions.push(args[flagName(bodyParamArg(name, param))] === void 0);
13279
- if (expressions.length > 0 && typeof args.body !== "string" && expressions.some(Boolean)) throw new UsageError(op.config.bodyTextArg && Object.keys(op.config.bodyParams ?? {}).length === 0 ? `Pass <${bodyTextDisplayKeyFor$1(op) ?? op.config.bodyTextArg}> or --body.` : "Pass required body positional arguments or --body.");
13280
+ if (expressions.length > 0 && typeof args.body !== "string" && expressions.some(Boolean)) throw new UsageError(op.config.bodyTextArg && Object.keys(op.config.bodyParams ?? {}).length === 0 ? `Pass <${bodyTextDisplayKeyFor$1(op) ?? op.config.bodyTextArg}>.` : "Pass required body positional arguments or --body.");
13280
13281
  };
13281
13282
  const dataWithAuditableWriteTarget = (op, data, body) => {
13282
13283
  if (op.schemaBaseName !== "PostsReact") return data;
@@ -13289,18 +13290,27 @@ const dataWithAuditableWriteTarget = (op, data, body) => {
13289
13290
  };
13290
13291
  };
13291
13292
  const trimTrailingNewline$1 = (value) => value.endsWith("\r\n") ? value.slice(0, -2) : value.endsWith("\n") ? value.slice(0, -1) : value;
13292
- const noBodyTextError$1 = (commandLabel) => new UsageError(`No body text. Pipe it via stdin: \`echo "..." | ${commandLabel}\` (or pass --body '<json>').`);
13293
+ const noBodyTextError$1 = (commandLabel) => new UsageError(`No body text. Pipe it via stdin: \`echo "..." | ${commandLabel}\`.`);
13294
+ const parseStdinBody$1 = (deps, bodyTextName, bodyTextKey, value) => {
13295
+ try {
13296
+ JSON.parse(value);
13297
+ } catch {
13298
+ return { [bodyTextKey]: deps.parseBodyText(`<${bodyTextName}>`, value) };
13299
+ }
13300
+ return deps.parseJSONObject("stdin", value);
13301
+ };
13293
13302
  const resolveBodyTextFromStdin$1 = async (op, deps, args, commandLabel) => {
13294
13303
  if (!hasStdinBodyText$1(op)) return;
13295
- const bodyTextArg = bodyTextDisplayKeyFor$1(op) ?? op.config.bodyTextArg;
13304
+ const bodyTextArg = op.config.bodyTextArg;
13296
13305
  if (bodyTextArg === void 0) return;
13297
- const required = stdinBodyTextRequired$1(op) && typeof args.body !== "string";
13306
+ const displayTextArg = bodyTextDisplayKeyFor$1(op) ?? bodyTextArg;
13307
+ const required = stdinBodyTextRequired$1(op);
13298
13308
  if (deps.isStdinTTY()) {
13299
13309
  if (required) throw noBodyTextError$1(commandLabel);
13300
13310
  return;
13301
13311
  }
13302
13312
  const piped = trimTrailingNewline$1(await deps.readStdin());
13303
- if (piped.length > 0) args[bodyTextArg] = piped;
13313
+ if (piped.length > 0) args.body = JSON.stringify(parseStdinBody$1(deps, displayTextArg, bodyTextArg, piped));
13304
13314
  else if (required) throw noBodyTextError$1(commandLabel);
13305
13315
  };
13306
13316
  const runOperation$1 = async (op, contract, deps, args, rawArgs = []) => {
@@ -13989,7 +13999,7 @@ const buildMessage$1 = (deps, sendCommand) => {
13989
13999
  return defineCommand({
13990
14000
  meta: commandMeta$2({
13991
14001
  name: "message",
13992
- description: "Send a LinkedIn message to a person or conversation [write] | usage: `social linkedin message <target> < body-text` | body text is read from stdin: `echo \"...\" | social linkedin message <target>` (or pass --body)",
14002
+ description: "Send a LinkedIn message to a person or conversation [write] | usage: `social linkedin message <target> < body-text` | body text is read from stdin: `echo \"...\" | social linkedin message <target>`",
13993
14003
  method: "POST",
13994
14004
  capability: "write",
13995
14005
  mutates: true,
@@ -14007,10 +14017,6 @@ const buildMessage$1 = (deps, sendCommand) => {
14007
14017
  description: "LinkedIn person or conversation target: @handle, profile URL, profile_id:<id>, chat_id:<id>, or a messaging thread URL",
14008
14018
  valueHint: "target"
14009
14019
  },
14010
- body: {
14011
- type: "string",
14012
- description: "JSON request body"
14013
- },
14014
14020
  account: {
14015
14021
  type: "string",
14016
14022
  description: "Account selector: @handle or profile_id:<id>; overrides config default"
@@ -14018,6 +14024,7 @@ const buildMessage$1 = (deps, sendCommand) => {
14018
14024
  },
14019
14025
  run: async ({ args }) => {
14020
14026
  const positionals = Array.isArray(args._) ? args._.map(String) : [];
14027
+ if (typeof args.body === "string") throw new UsageError("Unsupported option --body for linkedin message.");
14021
14028
  if (positionals[1] === "delete" || positionals[1] === "edit") {
14022
14029
  const verbCommand = positionals[1] === "delete" ? deleteCommand : editCommand;
14023
14030
  await verbCommand.run?.({
@@ -14033,23 +14040,13 @@ const buildMessage$1 = (deps, sendCommand) => {
14033
14040
  return;
14034
14041
  }
14035
14042
  const target = args.target ?? (Array.isArray(args._) && args._.length > 0 ? args._[0] : void 0);
14036
- const hasBody = typeof args.body === "string";
14037
- let textInput;
14038
- if (deps.isStdinTTY()) {
14039
- if (!hasBody) throw noBodyTextError$1("social linkedin message <target>");
14040
- } else {
14041
- const piped = trimTrailingNewline$1(await deps.readStdin());
14042
- if (piped.length > 0) textInput = piped;
14043
- else if (!hasBody) throw noBodyTextError$1("social linkedin message <target>");
14044
- }
14043
+ let body;
14044
+ if (deps.isStdinTTY()) throw noBodyTextError$1("social linkedin message <target>");
14045
+ const piped = trimTrailingNewline$1(await deps.readStdin());
14046
+ if (piped.length > 0) body = parseStdinBody$1(deps, "text", "text", piped);
14047
+ else throw noBodyTextError$1("social linkedin message <target>");
14045
14048
  if (target === void 0) throw new UsageError("Pass a conversation or person target.");
14046
- const bodyFromJSON = typeof args.body === "string" ? deps.parseJSONObject("--body", args.body) : void 0;
14047
- const text = textInput === void 0 ? void 0 : deps.parseBodyText("<text>", textInput);
14048
- const body = text === void 0 ? bodyFromJSON : {
14049
- ...bodyFromJSON ?? {},
14050
- text
14051
- };
14052
- if (body === void 0 || typeof body.text !== "string") throw new UsageError("Pass <text> or --body.");
14049
+ if (body === void 0 || typeof body.text !== "string") throw new UsageError("Pass body text via stdin.");
14053
14050
  const account = typeof args.account === "string" ? args.account : void 0;
14054
14051
  const classified = await deps.classifyMessageTarget("target", target, {
14055
14052
  platform: "linkedin",
@@ -17155,14 +17152,14 @@ const operationModeFor = (op) => {
17155
17152
  if (op.endpoint.method === "DELETE") return "destructive";
17156
17153
  return op.endpoint.mutates ? "write" : "read";
17157
17154
  };
17158
- const signaturePartFor = (op, name, arg) => {
17155
+ const signaturePartFor = (_op, name, arg) => {
17159
17156
  if (!isObjectRecord$1(arg)) return;
17160
17157
  if (arg.type === "positional") {
17161
17158
  const valueHint = typeof arg.valueHint === "string" ? arg.valueHint : name;
17162
17159
  return arg.required === true ? `<${valueHint}>` : `[${valueHint}]`;
17163
17160
  }
17164
17161
  if (name === "limit") return "--limit N";
17165
- if (name === "body" && !hasStdinBodyText(op)) return "--body JSON";
17162
+ if (name === "body") return "--body JSON";
17166
17163
  };
17167
17164
  const stdinSignaturePart = (op) => hasStdinBodyText(op) ? "< body-text" : void 0;
17168
17165
  const usageFor = (op, commandName, group) => {
@@ -17191,7 +17188,7 @@ const helpDescriptionFor = (op, commandName, group) => {
17191
17188
  group,
17192
17189
  commandName
17193
17190
  ].filter(Boolean).join(" ");
17194
- return `${description} | ${usage}${hasStdinBodyText(op) ? ` | body text is read from stdin: \`echo "..." | ${label}\` (or pass --body)` : ""}`;
17191
+ return `${description} | ${usage}${hasStdinBodyText(op) ? ` | body text is read from stdin: \`echo "..." | ${label}\`` : ""}`;
17195
17192
  };
17196
17193
  const contractFor$1 = (op, commandName, group) => {
17197
17194
  const mutates = op.endpoint.mutates;
@@ -17237,7 +17234,7 @@ const buildArgs = (op) => {
17237
17234
  description: param.description
17238
17235
  };
17239
17236
  }
17240
- if (op.hasBody) args.body = {
17237
+ if (op.hasBody && !hasStdinBodyText(op)) args.body = {
17241
17238
  type: "string",
17242
17239
  description: "JSON request body"
17243
17240
  };
@@ -17383,18 +17380,27 @@ const withEchoedTargetId = (op, data, body) => {
17383
17380
  };
17384
17381
  };
17385
17382
  const trimTrailingNewline = (value) => value.endsWith("\r\n") ? value.slice(0, -2) : value.endsWith("\n") ? value.slice(0, -1) : value;
17386
- const noBodyTextError = (commandLabel) => new UsageError(`No body text. Pipe it via stdin: \`echo "..." | ${commandLabel}\` (or pass --body '<json>').`);
17383
+ const noBodyTextError = (commandLabel) => new UsageError(`No body text. Pipe it via stdin: \`echo "..." | ${commandLabel}\`.`);
17384
+ const parseStdinBody = (deps, bodyTextName, bodyTextKey, value) => {
17385
+ try {
17386
+ JSON.parse(value);
17387
+ } catch {
17388
+ return { [bodyTextKey]: deps.parseBodyText(`<${bodyTextName}>`, value) };
17389
+ }
17390
+ return deps.parseJSONObject("stdin", value);
17391
+ };
17387
17392
  const resolveBodyTextFromStdin = async (op, deps, args, commandLabel) => {
17388
17393
  if (!hasStdinBodyText(op)) return;
17389
- const bodyTextArg = bodyTextDisplayKeyFor(op) ?? op.config.bodyTextArg;
17394
+ const bodyTextArg = op.config.bodyTextArg;
17390
17395
  if (bodyTextArg === void 0) return;
17391
- const required = stdinBodyTextRequired(op) && args.body === void 0;
17396
+ const displayTextArg = bodyTextDisplayKeyFor(op) ?? bodyTextArg;
17397
+ const required = stdinBodyTextRequired(op);
17392
17398
  if (deps.isStdinTTY()) {
17393
17399
  if (required) throw noBodyTextError(commandLabel);
17394
17400
  return;
17395
17401
  }
17396
17402
  const piped = trimTrailingNewline(await deps.readStdin());
17397
- if (piped.length > 0) args[bodyTextArg] = piped;
17403
+ if (piped.length > 0) args.body = JSON.stringify(parseStdinBody(deps, displayTextArg, bodyTextArg, piped));
17398
17404
  else if (required) throw noBodyTextError(commandLabel);
17399
17405
  };
17400
17406
  const runOperation = async (op, contract, deps, args, rawArgs = []) => {
@@ -17483,7 +17489,7 @@ const runOperation = async (op, contract, deps, args, rawArgs = []) => {
17483
17489
  };
17484
17490
  let body;
17485
17491
  if (op.hasBody) {
17486
- const bodyFromJSON = input.body === void 0 ? void 0 : deps.parseJSONObject("--body", input.body);
17492
+ const bodyFromJSON = input.body === void 0 ? void 0 : deps.parseJSONObject(hasStdinBodyText(op) ? "stdin" : "--body", input.body);
17487
17493
  const textArg = op.config.bodyTextArg;
17488
17494
  if (textArg) {
17489
17495
  const displayTextArg = bodyTextDisplayKeyFor(op) ?? textArg;
@@ -17946,7 +17952,7 @@ const buildMessage = (deps) => {
17946
17952
  return defineCommand({
17947
17953
  meta: commandMeta$1({
17948
17954
  name: "message",
17949
- description: "Send a DM to a participant or conversation [write] | usage: `social x message <recipients> < body-text` | body text is read from stdin: `echo \"...\" | social x message <recipients>` (or pass --body)",
17955
+ description: "Send a DM to a participant or conversation [write] | usage: `social x message <recipients> < body-text` | body text is read from stdin: `echo \"...\" | social x message <recipients>`",
17950
17956
  method: "POST",
17951
17957
  capability: "write",
17952
17958
  mutates: true,
@@ -17960,36 +17966,23 @@ const buildMessage = (deps) => {
17960
17966
  description: "Recipient(s): chat URL, chat_id:<id>, @handle, profile URL, or profile_id:<id>; comma-separate profiles for a group",
17961
17967
  valueHint: "recipients"
17962
17968
  },
17963
- body: {
17964
- type: "string",
17965
- description: "JSON request body"
17966
- },
17967
17969
  account: {
17968
17970
  type: "string",
17969
17971
  description: "Account selector: @handle or profile_id:<id>; overrides config default"
17970
17972
  }
17971
17973
  },
17972
17974
  run: async ({ args }) => {
17975
+ if (typeof args.body === "string") throw new UsageError("Unsupported option --body for x message.");
17973
17976
  const recipientsInput = args.recipients ?? args.target ?? (Array.isArray(args._) && args._.length > 0 ? args._[0] : void 0);
17974
- const hasBody = typeof args.body === "string";
17975
- let textInput;
17976
- if (deps.isStdinTTY()) {
17977
- if (!hasBody) throw noBodyTextError("social x message <recipients>");
17978
- } else {
17979
- const piped = trimTrailingNewline(await deps.readStdin());
17980
- if (piped.length > 0) textInput = piped;
17981
- else if (!hasBody) throw noBodyTextError("social x message <recipients>");
17982
- }
17977
+ let body;
17978
+ if (deps.isStdinTTY()) throw noBodyTextError("social x message <recipients>");
17979
+ const piped = trimTrailingNewline(await deps.readStdin());
17980
+ if (piped.length > 0) body = parseStdinBody(deps, "text", "text", piped);
17981
+ else throw noBodyTextError("social x message <recipients>");
17983
17982
  if (recipientsInput === void 0) throw new UsageError("Pass a conversation or user target.");
17984
17983
  const recipients = String(recipientsInput).split(",").map((recipient) => recipient.trim()).filter((recipient) => recipient.length > 0);
17985
17984
  if (recipients.length === 0) throw new UsageError("Pass at least one recipient.");
17986
- const bodyFromJSON = typeof args.body === "string" ? deps.parseJSONObject("--body", args.body) : void 0;
17987
- const text = textInput === void 0 ? void 0 : deps.parseBodyText("--text", textInput);
17988
- const body = text === void 0 ? bodyFromJSON : {
17989
- ...bodyFromJSON ?? {},
17990
- text
17991
- };
17992
- if (body === void 0) throw new UsageError("Pass <text> or --body.");
17985
+ if (body === void 0) throw noBodyTextError("social x message <recipients>");
17993
17986
  const resolved = [];
17994
17987
  const account = typeof args.account === "string" ? args.account : void 0;
17995
17988
  const selectedAccount = await deps.resolveAccount({
@@ -18018,12 +18011,13 @@ const buildMessage = (deps) => {
18018
18011
  })));
18019
18012
  for (const resolution of participantResolutions) resolved.push(resolution.record);
18020
18013
  const participant_ids = participantResolutions.map((resolution) => resolution.id);
18014
+ const { text, ...bodyFields } = body;
18021
18015
  requestPath = "x/dm_conversations";
18022
18016
  requestBody = {
18023
18017
  conversation_type: "Group",
18024
- ...bodyFromJSON ?? {},
18025
18018
  participant_ids,
18026
- ...text === void 0 ? {} : { message: { text } }
18019
+ ...bodyFields,
18020
+ ...typeof text === "string" ? { message: { text } } : {}
18027
18021
  };
18028
18022
  }
18029
18023
  const response = await deps.api.post(requestPath, {
@@ -21064,7 +21058,7 @@ const createInstance = (defaults) => {
21064
21058
  const ky = createInstance();
21065
21059
  //#endregion
21066
21060
  //#region package.json
21067
- var version$1 = "0.3.1";
21061
+ var version$1 = "0.3.2";
21068
21062
  //#endregion
21069
21063
  //#region src/lib/env.ts
21070
21064
  const URLWithTrailingSlash = url().transform(ensureTrailingSlash);
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "access": "public"
12
12
  },
13
13
  "private": false,
14
- "version": "0.3.1",
14
+ "version": "0.3.2",
15
15
  "type": "module",
16
16
  "engines": {
17
17
  "node": ">=22.5.0"