@indigoai-us/hq-cli 5.345.166 → 5.345.168
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 +4 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
- package/dist/command-catalog.generated.d.ts +6 -2
- package/dist/command-catalog.generated.js +7 -2
- package/dist/commands/access.js +2 -0
- package/dist/commands/agents.js +2 -2
- package/dist/commands/core-delegate-w1b.js +2 -1
- package/dist/commands/core-dm-bind-native.js +3 -1
- package/dist/commands/dm.d.ts +13 -2
- package/dist/commands/dm.js +42 -9
- package/dist/lib/agent-kit/mcp/tools.js +5 -6
- package/dist/lib/notify-recipient.d.ts +15 -0
- package/dist/lib/notify-recipient.js +15 -0
- package/dist/lib/secret-scan/repo-config.d.ts +8 -2
- package/dist/lib/secret-scan/repo-config.js +12 -6
- package/dist/lib/secret-scan/staged.d.ts +6 -0
- package/dist/lib/secret-scan/staged.js +21 -3
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.168] — 2026-10-24
|
|
6
|
+
- `hq dm` now picks the default audience from the recipient when no audience flag is passed. A 1:1 DM to a person (email, `prs_` uid, or a bare name that resolves to a person) sends `audience: "human"`. A 1:1 DM to an agent (`agt_` uid), a group DM and a channel post send `audience: "agent"`, so script and lane messages stay out of people's conversations. Scheduled sends (`--at`, `--in`) follow the same rule. Pass `--for-human` (alias for `--audience human`) to tag any message for people; an explicit `--audience human|agent|both` wins over `--for-human`, with a warning on stderr, and is validated on every path. `hq core delegate send` tags the pickup DM `audience: "both"`, `hq dm-bind post` keeps the channel default (`audience: "agent"`) for its automated status posts, and the agent MCP `hq_dm_send` tool, `hq agents message` and the `hq access` grant request apply the recipient rule. Reading commands (inbox, thread, channel) are unchanged, and `hq dm forward` does not set an audience.
|
|
7
|
+
- `hq scan secrets` no longer exits 2 when a repo's `.gitleaks.toml` allowlist is rejected. In staged and file scans it now prints a fixed warning that quotes no part of the file and scans with no repo allowlist, so a real key is still reported (exit 1) and a commit that replaces a bad allowlist is not blocked. An unknown `--allowlist-rev` still exits 2.
|
|
8
|
+
|
|
5
9
|
## [5.345.166] — 2026-10-24
|
|
6
10
|
- `hq integrations approve` now says why an approved call did not run. When an app refuses it, the CLI prints the app's own reason, such as Slack's refusal to send into a Slack Connect channel. Before this it printed only "failed (rejected)". With an older server it prints the rejection code instead. `hq integrations pending --history` adds a `Why:` line for decided calls that did not run. The approve command printed after a queued call keeps `--company` when you passed it. Before printing, the reason is cleaned of terminal escape codes, credentials and credential-bearing links, and it is capped at 500 characters. Feedback 15e7593c-50e5-4e6a-ae3a-342d618363c0.
|
|
7
11
|
- The bundled sync engine now uses hq-cloud 6.18.101, the current npm `latest`.
|
|
@@ -283,7 +283,7 @@ if ! jq '.status = "sending"' "$MANIFEST" > "$TMP_MANIFEST" || ! mv "$TMP_MANIFE
|
|
|
283
283
|
rm -f "$TMP_MANIFEST"
|
|
284
284
|
die "could not persist sending state; no DM attempted"
|
|
285
285
|
fi
|
|
286
|
-
DM_OUT="$(hq dm "$PRINCIPAL" "$HEADLINE" --prompt-file "$PROMPT" --details-file "$BRIEF")" \
|
|
286
|
+
DM_OUT="$(hq dm "$PRINCIPAL" "$HEADLINE" --audience both --prompt-file "$PROMPT" --details-file "$BRIEF")" \
|
|
287
287
|
|| die "hq dm delivery outcome is uncertain; status is sending, inspect DM history before retrying"
|
|
288
288
|
|
|
289
289
|
EVENT_ID="$(printf '%s' "$DM_OUT" | grep -oE 'eventId[: ]+[A-Za-z0-9_-]+' | head -1 | sed -E 's/eventId[: ]+//' || true)"
|
|
@@ -3987,7 +3987,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3987
3987
|
}];
|
|
3988
3988
|
}, {
|
|
3989
3989
|
readonly name: "dm";
|
|
3990
|
-
readonly description: "Send and read direct messages, and manage connection requests.";
|
|
3990
|
+
readonly description: "Send and read direct messages, and manage connection requests. Messages to a person send to them; messages to an agent, a channel or a group are tagged for agents and folded away from people by default. Pass --for-human or --audience human|agent|both to override.";
|
|
3991
3991
|
readonly aliases: readonly [];
|
|
3992
3992
|
readonly hidden: false;
|
|
3993
3993
|
readonly helpOption: true;
|
|
@@ -4054,7 +4054,11 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
4054
4054
|
readonly hidden: false;
|
|
4055
4055
|
}, {
|
|
4056
4056
|
readonly flags: "--audience <human|agent|both>";
|
|
4057
|
-
readonly description: "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both";
|
|
4057
|
+
readonly description: "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both. Default: human for a person, agent for an agent, a channel or a group. Wins over --for-human.";
|
|
4058
|
+
readonly hidden: false;
|
|
4059
|
+
}, {
|
|
4060
|
+
readonly flags: "--for-human";
|
|
4061
|
+
readonly description: "Tag the message for people (alias for --audience human).";
|
|
4058
4062
|
readonly hidden: false;
|
|
4059
4063
|
}, {
|
|
4060
4064
|
readonly flags: "--attach <path>";
|
|
@@ -4827,7 +4827,7 @@ export const COMMAND_CATALOG = [
|
|
|
4827
4827
|
},
|
|
4828
4828
|
{
|
|
4829
4829
|
"name": "dm",
|
|
4830
|
-
"description": "Send and read direct messages, and manage connection requests.",
|
|
4830
|
+
"description": "Send and read direct messages, and manage connection requests. Messages to a person send to them; messages to an agent, a channel or a group are tagged for agents and folded away from people by default. Pass --for-human or --audience human|agent|both to override.",
|
|
4831
4831
|
"aliases": [],
|
|
4832
4832
|
"hidden": false,
|
|
4833
4833
|
"helpOption": true,
|
|
@@ -4908,7 +4908,12 @@ export const COMMAND_CATALOG = [
|
|
|
4908
4908
|
},
|
|
4909
4909
|
{
|
|
4910
4910
|
"flags": "--audience <human|agent|both>",
|
|
4911
|
-
"description": "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both",
|
|
4911
|
+
"description": "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both. Default: human for a person, agent for an agent, a channel or a group. Wins over --for-human.",
|
|
4912
|
+
"hidden": false
|
|
4913
|
+
},
|
|
4914
|
+
{
|
|
4915
|
+
"flags": "--for-human",
|
|
4916
|
+
"description": "Tag the message for people (alias for --audience human).",
|
|
4912
4917
|
"hidden": false
|
|
4913
4918
|
},
|
|
4914
4919
|
{
|
package/dist/commands/access.js
CHANGED
|
@@ -25,6 +25,7 @@ import { listJournals, syncDoctor, } from "@indigoai-us/hq-cloud";
|
|
|
25
25
|
import { listActiveMembers } from "./members.js";
|
|
26
26
|
import { aclLockedBy } from "./acl-lock.js";
|
|
27
27
|
import { buildDmBody } from "./dm.js";
|
|
28
|
+
import { defaultAudienceFor, recipientKindOf } from "../lib/notify-recipient.js";
|
|
28
29
|
import { peekIdToken } from "../utils/id-token.js";
|
|
29
30
|
import { findRecentAccessRequest, formatTimeAgo, recordAccessRequest, } from "../utils/access-requests.js";
|
|
30
31
|
import { recordAccessOutcome } from "../utils/access-outcomes.js";
|
|
@@ -1155,6 +1156,7 @@ async function buildLiveAccessDeps(slug, hqRoot) {
|
|
|
1155
1156
|
prompt: msg.prompt,
|
|
1156
1157
|
details: msg.details,
|
|
1157
1158
|
now: Date.now(),
|
|
1159
|
+
audience: defaultAudienceFor(recipientKindOf(msg.recipient)),
|
|
1158
1160
|
});
|
|
1159
1161
|
const res = await vaultApiFetch({
|
|
1160
1162
|
token,
|
package/dist/commands/agents.js
CHANGED
|
@@ -42,7 +42,7 @@ import { consoleOrigin } from "./integrations-connect.js";
|
|
|
42
42
|
import { launchSessionManagerPlugin, SessionManagerPluginLaunchError, TerminalSessionTimeoutError, } from "@indigoai-us/hq-cloud/outposts/node";
|
|
43
43
|
import { peekIdToken } from "../utils/id-token.js";
|
|
44
44
|
import { canOfferTeamUpgrade, formatUpgradeRefusal, isPlanGateError, offerUpgradeLink, } from "../utils/plan-gate-error.js";
|
|
45
|
-
import { AGENT_UID_PATTERN, isAgentUid } from "../lib/notify-recipient.js";
|
|
45
|
+
import { AGENT_UID_PATTERN, defaultAudienceFor, isAgentUid } from "../lib/notify-recipient.js";
|
|
46
46
|
import { AGENT_MODEL_PROVIDERS, modelSelectionErrorMessage, readAgentModelCatalog, validateAgentModelSelection, } from "../lib/agents/model-catalog.js";
|
|
47
47
|
import { fetchCompanyPricingPlan, isUsageOnly, usageOnlyBoxSentence, usageOnlyCardRequiredLines, } from "../lib/billing/usage-only.js";
|
|
48
48
|
import { BUNDLED_USAGE_CARD, fetchUsageCard, } from "../lib/billing/usage-card.js";
|
|
@@ -1139,7 +1139,7 @@ export async function sendAgentDm(token, agentUid, message) {
|
|
|
1139
1139
|
token,
|
|
1140
1140
|
path: "/v1/notify/dm",
|
|
1141
1141
|
method: "POST",
|
|
1142
|
-
body: { toPersonUid: agentUid, body: message },
|
|
1142
|
+
body: { toPersonUid: agentUid, body: message, audience: defaultAudienceFor("agent") },
|
|
1143
1143
|
});
|
|
1144
1144
|
}
|
|
1145
1145
|
/** Read the two-way DM conversation with an agent (most recent `limit`). */
|
|
@@ -1079,7 +1079,8 @@ function runSend(args, context) {
|
|
|
1079
1079
|
catch {
|
|
1080
1080
|
return fail("send", "could not persist sending state; no DM attempted");
|
|
1081
1081
|
}
|
|
1082
|
-
|
|
1082
|
+
// The pickup DM is read by the person and acted on by their agent.
|
|
1083
|
+
const dm = runHq(["dm", principal, headline, "--audience", "both", "--prompt-file", prompt, "--details-file", brief], context);
|
|
1083
1084
|
if (dm.code !== 0)
|
|
1084
1085
|
return fail("send", "hq dm delivery outcome is uncertain; status is sending, inspect DM history before retrying");
|
|
1085
1086
|
const event = /eventId[: ]+([A-Za-z0-9_-]+)/.exec(dm.stdout ?? "")?.[1] ?? "";
|
|
@@ -579,6 +579,8 @@ async function post(d, args) {
|
|
|
579
579
|
const versionError = requireCliVersion(d);
|
|
580
580
|
if (versionError)
|
|
581
581
|
return versionError;
|
|
582
|
+
// An automated status post keeps the channel default (agent audience); only a
|
|
583
|
+
// reply to a message a person wrote is for humans.
|
|
582
584
|
const call = ["dm", ch, ...(root ? ["--thread", root] : []), cliMessage];
|
|
583
585
|
const sent = d.runHq(call, { ...d.env, HQ_ROOT: d.root });
|
|
584
586
|
if (sent.code !== 0)
|
|
@@ -594,7 +596,7 @@ async function post(d, args) {
|
|
|
594
596
|
const mentions = mentionsJson(d, sid, names);
|
|
595
597
|
if (!mentions.length)
|
|
596
598
|
return fail("no mention resolved to a channel member id — not posting an update that notifies nobody");
|
|
597
|
-
const payload = { body: postedMessage, mentions };
|
|
599
|
+
const payload = { body: postedMessage, mentions, audience: "agent" };
|
|
598
600
|
if (root)
|
|
599
601
|
payload.rootEventId = root;
|
|
600
602
|
try {
|
package/dist/commands/dm.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { defaultAudienceFor, recipientKindOf, type DmRecipientKind } from "../lib/notify-recipient.js";
|
|
3
|
+
export { defaultAudienceFor, recipientKindOf, type DmRecipientKind };
|
|
2
4
|
import { type DmAttachmentWire } from "./dm-attach.js";
|
|
3
5
|
export interface DmRecipient {
|
|
4
6
|
toEmail?: string;
|
|
@@ -158,7 +160,8 @@ export declare function resolveMentions(tokens: string[], members: ChannelMember
|
|
|
158
160
|
* Returns null on anything that doesn't match. Pure → unit-testable.
|
|
159
161
|
*/
|
|
160
162
|
export declare function parseDuration(input: string): number | null;
|
|
161
|
-
type DmAudience
|
|
163
|
+
import type { DmAudience } from "../lib/notify-recipient.js";
|
|
164
|
+
export type { DmAudience };
|
|
162
165
|
export interface DmSendBody {
|
|
163
166
|
toEmail?: string;
|
|
164
167
|
toPersonUid?: string;
|
|
@@ -233,6 +236,15 @@ export declare function fetchChannels(token: string): Promise<ChannelSummary[]>;
|
|
|
233
236
|
* post itself passes. Used to resolve `@name` mentions into participant uids.
|
|
234
237
|
*/
|
|
235
238
|
export declare function fetchChannelMembers(token: string, channelId: string): Promise<ChannelMember[]>;
|
|
239
|
+
/**
|
|
240
|
+
* Resolve the audience for a send. The default comes from the recipient kind
|
|
241
|
+
* (`defaultAudienceFor`); `--for-human` maps to `human`; an explicit
|
|
242
|
+
* `--audience` wins over `--for-human` (with a warning) and is validated.
|
|
243
|
+
*/
|
|
244
|
+
export declare function resolveSendAudience(opts: {
|
|
245
|
+
audience?: string;
|
|
246
|
+
forHuman?: boolean;
|
|
247
|
+
}, recipientKind: DmRecipientKind, warn?: (msg: string) => void): string;
|
|
236
248
|
/** One incoming DM as returned by GET /v1/notify/inbox. */
|
|
237
249
|
export interface DmInboxEvent {
|
|
238
250
|
eventId: string;
|
|
@@ -411,5 +423,4 @@ export declare function formatChannelTranscript(messages: ChannelMessageItem[],
|
|
|
411
423
|
export declare function agentCallerUid(token: string, machine?: () => boolean): string | null;
|
|
412
424
|
export declare const AGENT_THREAD_UNAVAILABLE: string;
|
|
413
425
|
export declare function registerDmCommand(program: Command): void;
|
|
414
|
-
export {};
|
|
415
426
|
//# sourceMappingURL=dm.d.ts.map
|
package/dist/commands/dm.js
CHANGED
|
@@ -5,7 +5,8 @@ import { resolveDestinationCompanyUid, vaultApiFetch } from "../utils/vault-api.
|
|
|
5
5
|
import { isMachineIdentity } from "@indigoai-us/hq-cloud";
|
|
6
6
|
import { peekIdToken } from "../utils/id-token.js";
|
|
7
7
|
import { redactAgentInboxItem as redactInboxCredentials } from "../lib/agent-kit/inbox-redact.js";
|
|
8
|
-
import { EMAIL_PATTERN, RECIPIENT_UID_PATTERN } from "../lib/notify-recipient.js";
|
|
8
|
+
import { EMAIL_PATTERN, RECIPIENT_UID_PATTERN, defaultAudienceFor, recipientKindOf, } from "../lib/notify-recipient.js";
|
|
9
|
+
export { defaultAudienceFor, recipientKindOf };
|
|
9
10
|
import { dmForwardCliEnabled } from "./dm-forward-flag.js";
|
|
10
11
|
import { attachmentScopeForChannel, formatUploadedAttachments, personalScopeForCaller, uploadDmAttachments, validateAttachmentPaths, } from "./dm-attach.js";
|
|
11
12
|
import { ChannelNotFoundError, suggestChannels, withChannelSuggestions, } from "./channel-suggest.js";
|
|
@@ -555,7 +556,7 @@ async function runConnectionAction(action, identifier) {
|
|
|
555
556
|
* the server's idempotent participant-key dedupe) a group channel, then posts
|
|
556
557
|
* the message into it. Reuses the same vault API client + auth as the 1:1 path.
|
|
557
558
|
*/
|
|
558
|
-
async function runGroupSend(recipients, message) {
|
|
559
|
+
async function runGroupSend(recipients, message, audience) {
|
|
559
560
|
try {
|
|
560
561
|
const participants = [];
|
|
561
562
|
for (const r of recipients) {
|
|
@@ -601,7 +602,7 @@ async function runGroupSend(recipients, message) {
|
|
|
601
602
|
token,
|
|
602
603
|
path: `/v1/notify/channels/${encodeURIComponent(channelId)}/messages`,
|
|
603
604
|
method: "POST",
|
|
604
|
-
body: { body },
|
|
605
|
+
body: { body, ...(audience ? { audience } : {}) },
|
|
605
606
|
});
|
|
606
607
|
if (!sendRes.ok) {
|
|
607
608
|
const err = (await sendRes.json().catch(() => ({})));
|
|
@@ -810,11 +811,41 @@ async function runChannelSend(channelName, message, opts) {
|
|
|
810
811
|
process.exit(1);
|
|
811
812
|
}
|
|
812
813
|
}
|
|
813
|
-
|
|
814
|
+
function explicitAudience(opts) {
|
|
815
|
+
const explicit = opts.audience !== undefined && opts.audience !== "" ? opts.audience : undefined;
|
|
816
|
+
if (explicit !== undefined && !DM_AUDIENCE_VALUES.includes(explicit)) {
|
|
817
|
+
throw new Error("Invalid --audience '" + explicit + "': must be human, agent, or both.");
|
|
818
|
+
}
|
|
819
|
+
return explicit;
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Resolve the audience for a send. The default comes from the recipient kind
|
|
823
|
+
* (`defaultAudienceFor`); `--for-human` maps to `human`; an explicit
|
|
824
|
+
* `--audience` wins over `--for-human` (with a warning) and is validated.
|
|
825
|
+
*/
|
|
826
|
+
export function resolveSendAudience(opts, recipientKind, warn = (m) => console.error(chalk.yellow(m))) {
|
|
827
|
+
const explicit = explicitAudience(opts);
|
|
828
|
+
if (explicit !== undefined) {
|
|
829
|
+
if (opts.forHuman) {
|
|
830
|
+
warn("Both --audience " + explicit + " and --for-human were passed; using --audience " + explicit + ".");
|
|
831
|
+
}
|
|
832
|
+
return explicit;
|
|
833
|
+
}
|
|
834
|
+
return opts.forHuman ? "human" : defaultAudienceFor(recipientKind);
|
|
835
|
+
}
|
|
836
|
+
async function runDmSend(recipient, message, rawOpts) {
|
|
837
|
+
// Validate an explicit --audience before any network call; the default is
|
|
838
|
+
// applied once the recipient kind is known.
|
|
839
|
+
explicitAudience(rawOpts);
|
|
840
|
+
const opts = { ...rawOpts };
|
|
841
|
+
const withAudience = (kind) => ({
|
|
842
|
+
...rawOpts,
|
|
843
|
+
audience: resolveSendAudience(rawOpts, kind),
|
|
844
|
+
});
|
|
814
845
|
// --channel <name> is an explicit channel target; the positional carries the
|
|
815
846
|
// message (recipient slot), e.g. `hq dm --channel vyg-dev "hello"`.
|
|
816
847
|
if (opts.channel !== undefined) {
|
|
817
|
-
await runChannelSend(opts.channel, message ?? recipient,
|
|
848
|
+
await runChannelSend(opts.channel, message ?? recipient, withAudience("channel"));
|
|
818
849
|
return;
|
|
819
850
|
}
|
|
820
851
|
if (recipient === undefined) {
|
|
@@ -837,7 +868,7 @@ async function runDmSend(recipient, message, opts) {
|
|
|
837
868
|
process.exit(1);
|
|
838
869
|
return;
|
|
839
870
|
}
|
|
840
|
-
await runGroupSend(group, message);
|
|
871
|
+
await runGroupSend(group, message, withAudience("group").audience);
|
|
841
872
|
return;
|
|
842
873
|
}
|
|
843
874
|
// A bare name or #hash addresses a named DM channel.
|
|
@@ -849,12 +880,13 @@ async function runDmSend(recipient, message, opts) {
|
|
|
849
880
|
? null
|
|
850
881
|
: matchContactByName(await fetchContacts(await ensureCognitoToken()), channelName);
|
|
851
882
|
if (!person?.personUid) {
|
|
852
|
-
await runChannelSend(channelName, message,
|
|
883
|
+
await runChannelSend(channelName, message, withAudience("channel"));
|
|
853
884
|
return;
|
|
854
885
|
}
|
|
855
886
|
console.log(chalk.dim(`Resolved '${channelName}' to person ${person.displayName || person.email || person.personUid} (${person.personUid}). Use '#${channelName}' or --channel ${channelName} to post to a channel instead.`));
|
|
856
887
|
recipient = person.personUid;
|
|
857
888
|
}
|
|
889
|
+
Object.assign(opts, withAudience(recipientKindOf(recipient)));
|
|
858
890
|
if (opts.thread !== undefined) {
|
|
859
891
|
console.error(chalk.red("--thread is only supported for channel messages (hq dm <channel> --thread <eventId|last> \"…\")."));
|
|
860
892
|
process.exit(1);
|
|
@@ -1697,7 +1729,7 @@ async function runDmChannel(target, opts) {
|
|
|
1697
1729
|
export function registerDmCommand(program) {
|
|
1698
1730
|
const dm = program
|
|
1699
1731
|
.command("dm")
|
|
1700
|
-
.description("Send and read direct messages, and manage connection requests.");
|
|
1732
|
+
.description("Send and read direct messages, and manage connection requests. Messages to a person send to them; messages to an agent, a channel or a group are tagged for agents and folded away from people by default. Pass --for-human or --audience human|agent|both to override.");
|
|
1701
1733
|
dm
|
|
1702
1734
|
.command("send [recipient] [message]", { isDefault: true, hidden: true })
|
|
1703
1735
|
.description('Send a direct message. RECIPIENT can be a person (email, personUid, or agentUid), a GROUP DM (comma-separated: "a@x.com,b@y.com"), or one of your DM CHANNELS by name — bare (hq dm vyg-dev "hi"), hash form (hq dm "#vyg-dev" "hi"), or via --channel (hq dm --channel vyg-dev "hi"). A person receives a DM as an HQ Sync notification; an agent receives it in its durable box inbox. If you aren\'t connected yet, it sends a connection request that holds your message. See your channels with `hq channels`.\n\nCHANNEL MENTIONS: HQ mentions are structured — the server never reads `@name` out of your text, so a channel message has to carry the participants alongside the body. When you write `@name` in a channel message, this command looks the name up in the channel roster (case-insensitive on display name and slug/handle; quote multi-word names as @"Jacob Posel") and sends it as a real mention, which notifies people and wakes agents. If a name is unknown or matches more than one member, the send FAILS with the candidates instead of posting something that mentions nobody. A bare "@" or an email address in your prose is never treated as a mention. Pass --no-mentions to post the text literally.\n\nCHANNEL THREADS: `hq dm <channel> --thread <eventId> "…"` replies under an existing root (the request carries rootEventId). `--thread last` replies to your most recent top-level message in that channel. Copy a root eventId from `hq dm channel <name>`, which prints each root\'s id and indents replies underneath.')
|
|
@@ -1710,7 +1742,8 @@ export function registerDmCommand(program) {
|
|
|
1710
1742
|
.option("--details-file <path>", "Read the details from a file (1:1 DMs; channel sends also supported)")
|
|
1711
1743
|
.option("--at <iso>", "Schedule delivery at an ISO8601 time (store-and-forward; delivered within ~60s of the time; 1:1 DMs only)")
|
|
1712
1744
|
.option("--in <duration>", "Schedule delivery after a relative delay: 30s, 10m, 2h, 1d (1:1 DMs only)")
|
|
1713
|
-
.option("--audience <human|agent|both>", "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both")
|
|
1745
|
+
.option("--audience <human|agent|both>", "Tag who this message is for: human (a person must read it), agent (progress/status, hidden from humans by default), or both. Default: human for a person, agent for an agent, a channel or a group. Wins over --for-human.")
|
|
1746
|
+
.option("--for-human", "Tag the message for people (alias for --audience human).")
|
|
1714
1747
|
.option("--attach <path>", "Upload a local file and attach it to the message (repeatable, up to 5 files, 25 MB each). 1:1 DMs and named channels; the file lands in the conversation's vault scope and the recipient can open it. Not available for group DMs or scheduled sends.", (value, previous = []) => [...previous, value])
|
|
1715
1748
|
.action(async (recipient, message, opts) => {
|
|
1716
1749
|
await runDmSend(recipient, message, opts);
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { CLI_VERSION } from "../../../cli-version.js";
|
|
19
19
|
import { peekIdToken } from "../../../utils/id-token.js";
|
|
20
|
-
import { isRecipientUid } from "../../../lib/notify-recipient.js";
|
|
20
|
+
import { defaultAudienceFor, isAgentUid, isRecipientUid } from "../../../lib/notify-recipient.js";
|
|
21
21
|
import { redactAgentInboxItem } from "../inbox-redact.js";
|
|
22
22
|
import { markInboxDone, mergeAgentInbox, readDoneIds, summarizeInboxEntry } from "../inbox-state.js";
|
|
23
23
|
import { AttachmentCompanyUnresolvedError, fetchChatAttachment } from "./attachments.js";
|
|
@@ -214,13 +214,13 @@ export function buildAgentMcpTools(clients) {
|
|
|
214
214
|
},
|
|
215
215
|
{
|
|
216
216
|
name: "hq_dm_send",
|
|
217
|
-
description: "Send an HQ message as this agent to a person (email or prs_…), an agent (agt_…), or a channel or group DM (chn_…). When replying to an inbox item that has a channelId, pass that channelId as `to` so the answer lands in the conversation it came from. Use audience='agent' for progress/status messages that only agents need to see; audience='human' for messages a person must read; audience='both' when unsure.",
|
|
217
|
+
description: "Send an HQ message as this agent to a person (email or prs_…), an agent (agt_…), or a channel or group DM (chn_…). When replying to an inbox item that has a channelId, pass that channelId as `to` so the answer lands in the conversation it came from. Use audience='agent' for progress/status messages that only agents need to see; audience='human' for messages a person must read; audience='both' when unsure. Default: human for a person, agent for an agent, a channel or a group.",
|
|
218
218
|
inputSchema: {
|
|
219
219
|
type: "object",
|
|
220
220
|
properties: {
|
|
221
221
|
to: { type: "string", description: "Email, prs_… or agt_… uid, or a chn_… channel id" },
|
|
222
222
|
message: { type: "string" },
|
|
223
|
-
audience: { type: "string", enum: ["human", "agent", "both"], description: "Who this message is for (default: human)" },
|
|
223
|
+
audience: { type: "string", enum: ["human", "agent", "both"], description: "Who this message is for (default: human for a person, agent for an agent, channel or group)" },
|
|
224
224
|
},
|
|
225
225
|
required: ["to", "message"],
|
|
226
226
|
additionalProperties: false,
|
|
@@ -238,7 +238,7 @@ export function buildAgentMcpTools(clients) {
|
|
|
238
238
|
const token = await clients.getToken();
|
|
239
239
|
const res = await clients.apiJson(token, `/v1/notify/channels/${encodeURIComponent(to)}/messages`, {
|
|
240
240
|
method: "POST",
|
|
241
|
-
body: { body: message,
|
|
241
|
+
body: { body: message, audience: audience ?? defaultAudienceFor("channel") },
|
|
242
242
|
});
|
|
243
243
|
if (res.status < 200 || res.status >= 300) {
|
|
244
244
|
return errorResult(`Channel send failed (${res.status}): ${describeError(res.body)}`);
|
|
@@ -252,8 +252,7 @@ export function buildAgentMcpTools(clients) {
|
|
|
252
252
|
: (() => {
|
|
253
253
|
throw new McpToolInputError('"to" must be an email, prs_… or agt_… uid, or a chn_… channel id');
|
|
254
254
|
})();
|
|
255
|
-
|
|
256
|
-
body.audience = audience;
|
|
255
|
+
body.audience = audience ?? defaultAudienceFor(isAgentUid(to) ? "agent" : "person");
|
|
257
256
|
const token = await clients.getToken();
|
|
258
257
|
const res = await clients.apiJson(token, "/v1/notify/dm", { method: "POST", body });
|
|
259
258
|
if (res.status < 200 || res.status >= 300) {
|
|
@@ -4,4 +4,19 @@ export declare const AGENT_UID_PATTERN: RegExp;
|
|
|
4
4
|
export declare const EMAIL_PATTERN: RegExp;
|
|
5
5
|
export declare function isRecipientUid(value: string): boolean;
|
|
6
6
|
export declare function isAgentUid(value: string): boolean;
|
|
7
|
+
/** Who a notify message is for. */
|
|
8
|
+
export type DmAudience = "human" | "agent" | "both";
|
|
9
|
+
/** What a send is addressed to, for the default audience. */
|
|
10
|
+
export type DmRecipientKind = "person" | "agent" | "channel" | "group";
|
|
11
|
+
/**
|
|
12
|
+
* Classify a single 1:1 recipient (email, prs_ or agt_ uid) for the default
|
|
13
|
+
* audience. Anything that is not an agent uid is a person.
|
|
14
|
+
*/
|
|
15
|
+
export declare function recipientKindOf(recipient: string): "person" | "agent";
|
|
16
|
+
/**
|
|
17
|
+
* Default audience by recipient: a 1:1 message to a person is for that
|
|
18
|
+
* person (`human`); a message to an agent, a channel or a group is tagged
|
|
19
|
+
* `agent` and folded away from people by default.
|
|
20
|
+
*/
|
|
21
|
+
export declare function defaultAudienceFor(kind: DmRecipientKind): DmAudience;
|
|
7
22
|
//# sourceMappingURL=notify-recipient.d.ts.map
|
|
@@ -8,4 +8,19 @@ export function isRecipientUid(value) {
|
|
|
8
8
|
export function isAgentUid(value) {
|
|
9
9
|
return AGENT_UID_PATTERN.test(value.trim());
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Classify a single 1:1 recipient (email, prs_ or agt_ uid) for the default
|
|
13
|
+
* audience. Anything that is not an agent uid is a person.
|
|
14
|
+
*/
|
|
15
|
+
export function recipientKindOf(recipient) {
|
|
16
|
+
return /^agt_/.test(recipient.trim()) ? "agent" : "person";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Default audience by recipient: a 1:1 message to a person is for that
|
|
20
|
+
* person (`human`); a message to an agent, a channel or a group is tagged
|
|
21
|
+
* `agent` and folded away from people by default.
|
|
22
|
+
*/
|
|
23
|
+
export function defaultAudienceFor(kind) {
|
|
24
|
+
return kind === "person" ? "human" : "agent";
|
|
25
|
+
}
|
|
11
26
|
//# sourceMappingURL=notify-recipient.js.map
|
|
@@ -26,13 +26,19 @@
|
|
|
26
26
|
* there, because a pattern can always be widened, alone or combined with other
|
|
27
27
|
* entries, to hide a real credential.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* An allowlist that is not acceptable is an InvalidAllowlistError. The scan
|
|
30
|
+
* then warns and runs with no repo allowlist at all (stricter, never looser),
|
|
31
|
+
* so a rejected file cannot block the commit that replaces it. Other config
|
|
32
|
+
* problems, such as an unknown --allowlist-rev, are a ScanConfigError and the
|
|
33
|
+
* scan exits 2.
|
|
31
34
|
*/
|
|
32
35
|
import { type Probe } from "./probes.js";
|
|
33
36
|
import { type CompiledAllowlist } from "./rules.js";
|
|
34
37
|
export declare class ScanConfigError extends Error {
|
|
35
38
|
}
|
|
39
|
+
/** The repo's .gitleaks.toml cannot be used; the scan ignores it (see the top of the file). */
|
|
40
|
+
export declare class InvalidAllowlistError extends ScanConfigError {
|
|
41
|
+
}
|
|
36
42
|
export declare const REPO_CONFIG_FILE = ".gitleaks.toml";
|
|
37
43
|
/**
|
|
38
44
|
* The first probe credential the allowlist would hide, judged as the detector
|
|
@@ -26,8 +26,11 @@
|
|
|
26
26
|
* there, because a pattern can always be widened, alone or combined with other
|
|
27
27
|
* entries, to hide a real credential.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* An allowlist that is not acceptable is an InvalidAllowlistError. The scan
|
|
30
|
+
* then warns and runs with no repo allowlist at all (stricter, never looser),
|
|
31
|
+
* so a rejected file cannot block the commit that replaces it. Other config
|
|
32
|
+
* problems, such as an unknown --allowlist-rev, are a ScanConfigError and the
|
|
33
|
+
* scan exits 2.
|
|
31
34
|
*/
|
|
32
35
|
import { execFileSync } from "node:child_process";
|
|
33
36
|
import { createHash } from "node:crypto";
|
|
@@ -38,6 +41,9 @@ import { credentialProbes } from "./probes.js";
|
|
|
38
41
|
import { compileAllowlist, compiledRules } from "./rules.js";
|
|
39
42
|
export class ScanConfigError extends Error {
|
|
40
43
|
}
|
|
44
|
+
/** The repo's .gitleaks.toml cannot be used; the scan ignores it (see the top of the file). */
|
|
45
|
+
export class InvalidAllowlistError extends ScanConfigError {
|
|
46
|
+
}
|
|
41
47
|
export const REPO_CONFIG_FILE = ".gitleaks.toml";
|
|
42
48
|
const KEYS = new Set(["description", "condition", "regexTarget", "paths", "regexes", "stopwords", "commits", "targetRules"]);
|
|
43
49
|
const MIN_PATH_PREFIX = 3;
|
|
@@ -68,7 +74,7 @@ export function suppressedProbe(al) {
|
|
|
68
74
|
return probes.find((p, i) => probeBaseline[i] && scanFragment(probeFragment(p.line), rules).length === 0);
|
|
69
75
|
}
|
|
70
76
|
function fail(file, where, msg) {
|
|
71
|
-
throw new
|
|
77
|
+
throw new InvalidAllowlistError(`${file}: ${where}: ${msg}`);
|
|
72
78
|
}
|
|
73
79
|
function stringList(file, where, key, v) {
|
|
74
80
|
if (v === undefined)
|
|
@@ -292,7 +298,7 @@ function parseToml(text, file) {
|
|
|
292
298
|
return parse(text);
|
|
293
299
|
}
|
|
294
300
|
catch (err) {
|
|
295
|
-
throw new
|
|
301
|
+
throw new InvalidAllowlistError(`${file}: could not be parsed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`);
|
|
296
302
|
}
|
|
297
303
|
}
|
|
298
304
|
function git(repoRoot, args) {
|
|
@@ -313,10 +319,10 @@ function entry(out, blobField) {
|
|
|
313
319
|
}
|
|
314
320
|
function readEntry(repoRoot, e, where) {
|
|
315
321
|
if (e.mode === "120000")
|
|
316
|
-
throw new
|
|
322
|
+
throw new InvalidAllowlistError(`${REPO_CONFIG_FILE} (${where}) is a symlink; commit a regular file`);
|
|
317
323
|
const text = git(repoRoot, ["cat-file", "blob", e.blob]);
|
|
318
324
|
if (text === null)
|
|
319
|
-
throw new
|
|
325
|
+
throw new InvalidAllowlistError(`${REPO_CONFIG_FILE} (${where}) could not be read from git`);
|
|
320
326
|
return text;
|
|
321
327
|
}
|
|
322
328
|
/**
|
|
@@ -13,6 +13,12 @@ export interface ParsedDiff {
|
|
|
13
13
|
export declare function parseUnifiedDiff(diff: string): ParsedDiff;
|
|
14
14
|
export declare function stagedDiff(cwd: string): string;
|
|
15
15
|
export declare function repoRoot(cwd: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* The repo's allowlists, or none when its .gitleaks.toml is rejected. The
|
|
18
|
+
* warning is fixed text: a validation message can quote a path or other
|
|
19
|
+
* entry, and an entry can hold a credential.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ALLOWLIST_IGNORED_WARNING = "HQ secret scan: warning: the repo .gitleaks.toml allowlist was rejected; ignoring it and scanning with no repo allowlist";
|
|
16
22
|
/**
|
|
17
23
|
* Built-in rules plus the repo's own .gitleaks.toml allowlists. Diff paths are
|
|
18
24
|
* relative to the repo root, as gitleaks' allowlist paths are.
|
|
@@ -5,7 +5,7 @@ import { execFileSync } from "node:child_process";
|
|
|
5
5
|
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
6
6
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { scanFragment, scanPath } from "./detect.js";
|
|
8
|
-
import { loadCommittedAllowlists, loadRepoAllowlists } from "./repo-config.js";
|
|
8
|
+
import { InvalidAllowlistError, loadCommittedAllowlists, loadRepoAllowlists } from "./repo-config.js";
|
|
9
9
|
import { compiledRules } from "./rules.js";
|
|
10
10
|
export class ScanInputError extends Error {
|
|
11
11
|
}
|
|
@@ -101,13 +101,31 @@ export function repoRoot(cwd) {
|
|
|
101
101
|
throw new ScanInputError(`could not find the repo root: ${detail || "git rev-parse failed"}`);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* The repo's allowlists, or none when its .gitleaks.toml is rejected. The
|
|
106
|
+
* warning is fixed text: a validation message can quote a path or other
|
|
107
|
+
* entry, and an entry can hold a credential.
|
|
108
|
+
*/
|
|
109
|
+
export const ALLOWLIST_IGNORED_WARNING = "HQ secret scan: warning: the repo .gitleaks.toml allowlist was rejected; ignoring it and scanning with no repo allowlist";
|
|
110
|
+
function repoAllowlistsOrNone(load) {
|
|
111
|
+
try {
|
|
112
|
+
return load();
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
if (!(err instanceof InvalidAllowlistError))
|
|
116
|
+
throw err;
|
|
117
|
+
process.stderr.write(`${ALLOWLIST_IGNORED_WARNING}\n`);
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
104
121
|
/**
|
|
105
122
|
* Built-in rules plus the repo's own .gitleaks.toml allowlists. Diff paths are
|
|
106
123
|
* relative to the repo root, as gitleaks' allowlist paths are.
|
|
107
124
|
*/
|
|
108
125
|
export function scanStaged(cwd) {
|
|
109
126
|
const diff = stagedDiff(cwd);
|
|
110
|
-
const
|
|
127
|
+
const root = repoRoot(cwd);
|
|
128
|
+
const rules = { ...compiledRules(), repo: repoAllowlistsOrNone(() => loadRepoAllowlists(root)) };
|
|
111
129
|
const { fragments, binaryFiles } = parseUnifiedDiff(diff);
|
|
112
130
|
return [...fragments.flatMap((f) => scanFragment(f, rules)), ...binaryFiles.flatMap((f) => scanPath(f, rules))];
|
|
113
131
|
}
|
|
@@ -145,7 +163,7 @@ function matchPath(p, repo, snapshotRoot) {
|
|
|
145
163
|
}
|
|
146
164
|
export function scanFiles(paths, opts = {}) {
|
|
147
165
|
const root = opts.repo !== undefined ? repoRoot(opts.repo) : undefined;
|
|
148
|
-
const rules = root !== undefined ? { ...compiledRules(), repo: loadCommittedAllowlists(root, opts.allowlistRev) } : compiledRules();
|
|
166
|
+
const rules = root !== undefined ? { ...compiledRules(), repo: repoAllowlistsOrNone(() => loadCommittedAllowlists(root, opts.allowlistRev)) } : compiledRules();
|
|
149
167
|
const repo = root !== undefined ? real(root) : undefined;
|
|
150
168
|
const snapshotRoot = opts.snapshotRoot !== undefined ? real(opts.snapshotRoot) : undefined;
|
|
151
169
|
const out = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.345.
|
|
3
|
+
"version": "5.345.168",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -114,11 +114,11 @@
|
|
|
114
114
|
"*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}": "eslint --cache --no-error-on-unmatched-pattern"
|
|
115
115
|
},
|
|
116
116
|
"optionalDependencies": {
|
|
117
|
-
"@indigoai-us/hq-hook-win32-x64": "5.345.
|
|
118
|
-
"@indigoai-us/hq-hook-win32-arm64": "5.345.
|
|
119
|
-
"@indigoai-us/hq-hook-darwin-x64": "5.345.
|
|
120
|
-
"@indigoai-us/hq-hook-darwin-arm64": "5.345.
|
|
121
|
-
"@indigoai-us/hq-hook-linux-x64": "5.345.
|
|
122
|
-
"@indigoai-us/hq-hook-linux-arm64": "5.345.
|
|
117
|
+
"@indigoai-us/hq-hook-win32-x64": "5.345.168",
|
|
118
|
+
"@indigoai-us/hq-hook-win32-arm64": "5.345.168",
|
|
119
|
+
"@indigoai-us/hq-hook-darwin-x64": "5.345.168",
|
|
120
|
+
"@indigoai-us/hq-hook-darwin-arm64": "5.345.168",
|
|
121
|
+
"@indigoai-us/hq-hook-linux-x64": "5.345.168",
|
|
122
|
+
"@indigoai-us/hq-hook-linux-arm64": "5.345.168"
|
|
123
123
|
}
|
|
124
124
|
}
|