@indigoai-us/hq-cli 5.136.0 → 5.137.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/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.137.0] — 2026-09-23
6
+ ### Added
7
+
8
+ - Added `hq channels members <channel>` to print channel members for human and machine-principal sessions, with a `--json` format for scripts.
9
+
5
10
  ## [5.136.0] — 2026-09-23
6
11
 
7
12
  ### Added
@@ -2388,6 +2388,22 @@ export declare const COMMAND_CATALOG: readonly [{
2388
2388
  readonly arguments: readonly [];
2389
2389
  readonly options: readonly [];
2390
2390
  readonly subcommands: readonly [];
2391
+ }, {
2392
+ readonly name: "members";
2393
+ readonly description: "Print the members of one of your DM channels.";
2394
+ readonly aliases: readonly [];
2395
+ readonly hidden: false;
2396
+ readonly usage: "[options] <channel>";
2397
+ readonly arguments: readonly [{
2398
+ readonly name: "channel";
2399
+ readonly required: true;
2400
+ readonly variadic: false;
2401
+ }];
2402
+ readonly options: readonly [{
2403
+ readonly flags: "--json";
2404
+ readonly description: "Output the member array as JSON.";
2405
+ }];
2406
+ readonly subcommands: readonly [];
2391
2407
  }];
2392
2408
  }, {
2393
2409
  readonly name: "onboard";
@@ -3067,6 +3067,27 @@ export const COMMAND_CATALOG = [
3067
3067
  "arguments": [],
3068
3068
  "options": [],
3069
3069
  "subcommands": []
3070
+ },
3071
+ {
3072
+ "name": "members",
3073
+ "description": "Print the members of one of your DM channels.",
3074
+ "aliases": [],
3075
+ "hidden": false,
3076
+ "usage": "[options] <channel>",
3077
+ "arguments": [
3078
+ {
3079
+ "name": "channel",
3080
+ "required": true,
3081
+ "variadic": false
3082
+ }
3083
+ ],
3084
+ "options": [
3085
+ {
3086
+ "flags": "--json",
3087
+ "description": "Output the member array as JSON."
3088
+ }
3089
+ ],
3090
+ "subcommands": []
3070
3091
  }
3071
3092
  ]
3072
3093
  },
@@ -1,5 +1,5 @@
1
1
  import { Command } from "commander";
2
- import { type ChannelSummary } from "./dm.js";
2
+ import { type CallerIdentity, type ChannelMember, type ChannelSummary } from "./dm.js";
3
3
  /**
4
4
  * Human label for a channel row. Named channels (personal/company) show their
5
5
  * name + a `hq dm <slug>` hint; unnamed group DMs are identified by their
@@ -12,5 +12,13 @@ export declare function describeChannel(c: ChannelSummary): string;
12
12
  * fetched list) so the formatting is unit-testable without network/auth.
13
13
  */
14
14
  export declare function formatChannelsList(channels: ChannelSummary[]): string;
15
+ /**
16
+ * Remove the caller from a channel roster before it is used for mentions.
17
+ * Machine principals identify themselves with the JWT uid; human sessions can
18
+ * also match the roster's email when the uid claim is absent.
19
+ */
20
+ export declare function excludeCallerFromChannelMembers(members: ChannelMember[], caller: CallerIdentity): ChannelMember[];
21
+ /** Render the roster as either dm-bind's tab-separated format or JSON. */
22
+ export declare function formatChannelMembers(members: ChannelMember[], json: boolean): string;
15
23
  export declare function registerChannelsCommand(program: Command): void;
16
24
  //# sourceMappingURL=channels.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  import { ensureCognitoToken } from "../utils/cognito-session.js";
3
3
  import { vaultApiFetch } from "../utils/vault-api.js";
4
- import { channelSlug } from "./dm.js";
4
+ import { callerFromToken, channelSlug, fetchChannelMembers, fetchChannels, matchChannelsByName, } from "./dm.js";
5
5
  /**
6
6
  * Human label for a channel row. Named channels (personal/company) show their
7
7
  * name + a `hq dm <slug>` hint; unnamed group DMs are identified by their
@@ -42,6 +42,31 @@ export function formatChannelsList(channels) {
42
42
  }
43
43
  return lines.join("\n");
44
44
  }
45
+ /**
46
+ * Remove the caller from a channel roster before it is used for mentions.
47
+ * Machine principals identify themselves with the JWT uid; human sessions can
48
+ * also match the roster's email when the uid claim is absent.
49
+ */
50
+ export function excludeCallerFromChannelMembers(members, caller) {
51
+ return members.filter((member) => {
52
+ if (caller.personUid && member.personUid === caller.personUid)
53
+ return false;
54
+ if (caller.email &&
55
+ member.email &&
56
+ member.email.trim().toLowerCase() === caller.email) {
57
+ return false;
58
+ }
59
+ return true;
60
+ });
61
+ }
62
+ /** Render the roster as either dm-bind's tab-separated format or JSON. */
63
+ export function formatChannelMembers(members, json) {
64
+ if (json)
65
+ return JSON.stringify(members);
66
+ return members
67
+ .map((member) => `${member.displayName ?? ""}\t${member.personUid}`)
68
+ .join("\n");
69
+ }
45
70
  async function runChannelsList() {
46
71
  try {
47
72
  const token = await ensureCognitoToken();
@@ -64,6 +89,29 @@ async function runChannelsList() {
64
89
  process.exit(1);
65
90
  }
66
91
  }
92
+ async function runChannelsMembers(channelName, opts) {
93
+ try {
94
+ const token = await ensureCognitoToken();
95
+ const channels = await fetchChannels(token);
96
+ const matches = matchChannelsByName(channels, channelName);
97
+ if (matches.length === 0) {
98
+ console.error(chalk.red(`No channel named '${channelName}' — run \`hq channels\` to see your channels.`));
99
+ process.exit(1);
100
+ }
101
+ if (matches.length > 1) {
102
+ const scopes = matches.map((match) => match.scope ?? "?").join(", ");
103
+ console.error(chalk.red(`'${channelName}' matches ${matches.length} channels (${scopes}) — this is ambiguous.`));
104
+ process.exit(1);
105
+ }
106
+ const members = await fetchChannelMembers(token, matches[0].channelId);
107
+ const visibleMembers = excludeCallerFromChannelMembers(members, callerFromToken(token));
108
+ console.log(formatChannelMembers(visibleMembers, opts.json === true));
109
+ }
110
+ catch (err) {
111
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
112
+ process.exit(1);
113
+ }
114
+ }
67
115
  export function registerChannelsCommand(program) {
68
116
  const channels = program
69
117
  .command("channels")
@@ -74,5 +122,12 @@ export function registerChannelsCommand(program) {
74
122
  .action(async () => {
75
123
  await runChannelsList();
76
124
  });
125
+ channels
126
+ .command("members <channel>")
127
+ .description("Print the members of one of your DM channels.")
128
+ .option("--json", "Output the member array as JSON.")
129
+ .action(async (channelName, opts) => {
130
+ await runChannelsMembers(channelName, opts);
131
+ });
77
132
  }
78
133
  //# sourceMappingURL=channels.js.map
@@ -192,6 +192,12 @@ export declare function buildConnectionActionBody(identifier: string, matched: C
192
192
  };
193
193
  /** Fetch the caller's channels (GET /v1/notify/channels). */
194
194
  export declare function fetchChannels(token: string): Promise<ChannelSummary[]>;
195
+ /**
196
+ * Fetch a channel's roster (GET /v1/notify/channels/{id}/members). Any member
197
+ * who can read the channel may read its members, so this is the same gate the
198
+ * post itself passes. Used to resolve `@name` mentions into participant uids.
199
+ */
200
+ export declare function fetchChannelMembers(token: string, channelId: string): Promise<ChannelMember[]>;
195
201
  /** One incoming DM as returned by GET /v1/notify/inbox. */
196
202
  export interface DmInboxEvent {
197
203
  eventId: string;
@@ -503,7 +503,7 @@ export async function fetchChannels(token) {
503
503
  * who can read the channel may read its members, so this is the same gate the
504
504
  * post itself passes. Used to resolve `@name` mentions into participant uids.
505
505
  */
506
- async function fetchChannelMembers(token, channelId) {
506
+ export async function fetchChannelMembers(token, channelId) {
507
507
  const res = await vaultApiFetch({
508
508
  token,
509
509
  path: `/v1/notify/channels/${encodeURIComponent(channelId)}/members`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.136.0",
3
+ "version": "5.137.0",
4
4
  "description": "HQ by Indigo management CLI \u2014 modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {