@indigoai-us/hq-cli 5.11.0 → 5.12.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/CHANGELOG.md CHANGED
@@ -1,5 +1,64 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.12.1] — 2026-05-09
4
+
5
+ ### Fixed
6
+
7
+ - **CREATED BY column in HQ Console Vault now populates for cloud-synced files.**
8
+ Pulls in `@indigoai-us/hq-cloud@5.11.3`, which stamps `created-by`,
9
+ `created-by-sub`, and `created-at` user metadata on every `PutObject` so the
10
+ vault listing's HEAD fan-out has identity to attribute. Files synced via
11
+ `hq cloud provision`, `hq files share`, and the watcher previously rendered
12
+ as `—` because only the Next.js upload route stamped author metadata.
13
+
14
+ ## [5.12.0] — 2026-05-08
15
+
16
+ ### Added
17
+
18
+ - **`hq feedback` command group** — two subcommands `hq feedback bug` and
19
+ `hq feedback feature` let any logged-in user submit structured feedback without
20
+ leaving the terminal. Submissions are JWT-authenticated via the existing Cognito
21
+ session (no additional login step).
22
+
23
+ - **`--body-file <path>` flag** — pass a pre-written markdown file as the feedback
24
+ body. Multi-line input without shell quoting headaches. Use `--body-file -` to read
25
+ from stdin (useful for piped workflows). Body input is file-based by design (no
26
+ inline string flag).
27
+
28
+ - **`--company <slug>` flag (optional)** — attaches a company context to the submission.
29
+ Resolved automatically from `core/scripts/hq-session.sh get company_slug` inside the
30
+ `/feedback` skill; the flag is omitted when the slug is empty.
31
+
32
+ - **Diagnostics blob** — every submission includes a structured diagnostics payload
33
+ (`cliVersion`, `nodeVersion`, `os.platform`, `os.release`, `command` argv array,
34
+ `cwd`, `recentSentryBreadcrumbs`). Purely plain CLI/process context — no GHQ-OS-aware
35
+ identifiers.
36
+
37
+ - **Slack relay** — the vault-service backend best-effort posts a formatted card to
38
+ `#hq-feedback` after writing the DynamoDB row. Relay is fire-and-forget; Slack
39
+ failures do not affect the HTTP response.
40
+
41
+ ### Example
42
+
43
+ ```bash
44
+ # Interactive: title prompted if not supplied
45
+ hq feedback bug --title "search broken on mobile"
46
+
47
+ # Scripted: body from a pre-written file
48
+ hq feedback feature --title "dark mode" --body-file /tmp/feature-body.md
49
+
50
+ # Piped: read body from stdin
51
+ echo "Steps to reproduce: ..." | hq feedback bug --title "crash on login" --body-file -
52
+ ```
53
+
54
+ ## [5.11.0] — 2026-05-07
55
+
56
+ ### Added
57
+
58
+ - **`--personal` flag for `hq secrets`** — pin a secret to the per-user store rather
59
+ than the company store. See commit `b5ec9bd` ("release: hq-cli@5.11.0 — --personal
60
+ flag for secrets") for details.
61
+
3
62
  ## [5.10.0] — 2026-05-04
4
63
 
5
64
  ### Added
@@ -0,0 +1,2 @@
1
+ export declare const CLI_VERSION = "5.12.1";
2
+ //# sourceMappingURL=cli-version.d.ts.map
@@ -0,0 +1,5 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="f65738b2-fa67-5b68-a0c8-c73fba12c6c5")}catch(e){}}();
3
+ export const CLI_VERSION = "5.12.1";
4
+ //# sourceMappingURL=cli-version.js.map
5
+ //# debugId=f65738b2-fa67-5b68-a0c8-c73fba12c6c5
@@ -0,0 +1,16 @@
1
+ import { Command } from "commander";
2
+ export declare const BODY_MAX_BYTES: number;
3
+ export interface FeedbackResult {
4
+ id: string;
5
+ }
6
+ export interface FeedbackSubmitOptions {
7
+ type: "bug" | "feature";
8
+ title: string;
9
+ body: string;
10
+ company?: string;
11
+ token: string;
12
+ }
13
+ export declare function readBodyFile(bodyFile: string, stdin?: NodeJS.ReadableStream): Promise<string>;
14
+ export declare function submitFeedback(opts: FeedbackSubmitOptions): Promise<FeedbackResult>;
15
+ export declare function registerFeedbackCommand(program: Command): void;
16
+ //# sourceMappingURL=feedback.d.ts.map
@@ -0,0 +1,98 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3e8d95ee-bfb4-5ca3-8177-7bc3ca618bd4")}catch(e){}}();
3
+ import * as fs from "node:fs";
4
+ import chalk from "chalk";
5
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
6
+ import { vaultApiFetch } from "../utils/vault-api.js";
7
+ import { collectDiagnostics } from "../utils/feedback-diagnostics.js";
8
+ export const BODY_MAX_BYTES = 64 * 1024;
9
+ export async function readBodyFile(bodyFile, stdin) {
10
+ if (bodyFile === "-") {
11
+ const stream = stdin ?? process.stdin;
12
+ if (stream.isTTY) {
13
+ throw new Error("--body-file - requires piped stdin (got interactive terminal).");
14
+ }
15
+ return new Promise((resolve, reject) => {
16
+ let data = "";
17
+ stream.setEncoding("utf8");
18
+ stream.on("data", (chunk) => {
19
+ data += chunk;
20
+ });
21
+ stream.on("end", () => resolve(data));
22
+ stream.on("error", reject);
23
+ });
24
+ }
25
+ return fs.promises.readFile(bodyFile, "utf-8");
26
+ }
27
+ export async function submitFeedback(opts) {
28
+ if (opts.body.trim().length === 0) {
29
+ throw new Error("body must not be empty. Provide at least one non-whitespace character.");
30
+ }
31
+ const bodyBytes = Buffer.byteLength(opts.body, "utf8");
32
+ if (bodyBytes > BODY_MAX_BYTES) {
33
+ throw new Error(`Body exceeds 64 KiB limit (${bodyBytes} bytes). Reduce the body size before submitting.`);
34
+ }
35
+ const diagnostics = collectDiagnostics();
36
+ const requestBody = {
37
+ type: opts.type,
38
+ title: opts.title,
39
+ body: opts.body,
40
+ diagnostics,
41
+ };
42
+ if (opts.company) {
43
+ requestBody.company = opts.company;
44
+ }
45
+ const res = await vaultApiFetch({
46
+ token: opts.token,
47
+ path: "/v1/feedback",
48
+ method: "POST",
49
+ body: requestBody,
50
+ });
51
+ if (!res.ok) {
52
+ const data = await res.json().catch(() => ({}));
53
+ const errMsg = data &&
54
+ typeof data === "object" &&
55
+ !Array.isArray(data) &&
56
+ typeof data.error === "string"
57
+ ? data.error
58
+ : res.statusText;
59
+ throw new Error(`Failed to submit feedback: ${errMsg}`);
60
+ }
61
+ const data = (await res.json());
62
+ return { id: data.id };
63
+ }
64
+ function registerSubcommand(feedbackCmd, type) {
65
+ feedbackCmd
66
+ .command(type)
67
+ .description(type === "bug" ? "Report a bug" : "Request a feature")
68
+ .requiredOption("--title <text>", "Short title for the report")
69
+ .requiredOption("--body-file <path>", "Path to a markdown file with the body; use - to read from stdin")
70
+ .option("--company <slug>", "Company slug to associate with the report")
71
+ .action(async (opts) => {
72
+ try {
73
+ const token = await ensureCognitoToken({ interactive: false });
74
+ const body = await readBodyFile(opts.bodyFile);
75
+ const result = await submitFeedback({
76
+ type,
77
+ title: opts.title,
78
+ body,
79
+ company: opts.company,
80
+ token,
81
+ });
82
+ console.log(`Submitted: ${result.id}`);
83
+ }
84
+ catch (err) {
85
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
86
+ process.exit(1);
87
+ }
88
+ });
89
+ }
90
+ export function registerFeedbackCommand(program) {
91
+ const feedbackCmd = program
92
+ .command("feedback")
93
+ .description("Submit a bug report or feature request to HQ");
94
+ registerSubcommand(feedbackCmd, "bug");
95
+ registerSubcommand(feedbackCmd, "feature");
96
+ }
97
+ //# sourceMappingURL=feedback.js.map
98
+ //# debugId=3e8d95ee-bfb4-5ca3-8177-7bc3ca618bd4
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * HQ CLI - Module management, package management, and cloud sync for HQ
4
4
  */
5
5
 
6
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e834c407-cd5c-5dc1-af3b-7f041af651ef")}catch(e){}}();
6
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="65033669-3ba8-5aac-8a98-49827f627ff9")}catch(e){}}();
7
7
  import { Command } from "commander";
8
8
  import { initSentry, Sentry } from "./sentry.js";
9
9
  import { registerAddCommand } from "./commands/add.js";
@@ -28,12 +28,14 @@ import { registerRunCommand } from "./commands/run.js";
28
28
  import { registerGroupsCommand } from "./commands/groups.js";
29
29
  import { registerFilesCommand } from "./commands/files.js";
30
30
  import { registerMembersCommand } from "./commands/members.js";
31
+ import { registerFeedbackCommand } from "./commands/feedback.js";
32
+ import { sanitizeArgv } from "./utils/feedback-diagnostics.js";
31
33
  initSentry();
32
34
  const program = new Command();
33
35
  program
34
36
  .name("hq")
35
37
  .description("HQ management CLI — modules, packages, and cloud sync")
36
- .version("5.8.6");
38
+ .version("5.12.1");
37
39
  // Module management subcommand group
38
40
  const modulesCmd = program
39
41
  .command("modules")
@@ -86,8 +88,15 @@ registerFilesCommand(program);
86
88
  registerMembersCommand(program);
87
89
  // Onboarding (top-level — Cognito + vault-service provisioning)
88
90
  registerOnboardCommand(program);
91
+ // Feedback (subcommand group — hq feedback bug|feature)
92
+ registerFeedbackCommand(program);
89
93
  (async () => {
90
94
  try {
95
+ Sentry.addBreadcrumb({
96
+ category: "command",
97
+ message: sanitizeArgv(process.argv.slice(2)).join(" "),
98
+ level: "info",
99
+ });
91
100
  await program.parseAsync();
92
101
  }
93
102
  catch (err) {
@@ -99,4 +108,4 @@ registerOnboardCommand(program);
99
108
  }
100
109
  })();
101
110
  //# sourceMappingURL=index.js.map
102
- //# debugId=e834c407-cd5c-5dc1-af3b-7f041af651ef
111
+ //# debugId=65033669-3ba8-5aac-8a98-49827f627ff9
package/dist/sentry.js CHANGED
@@ -1,8 +1,9 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="9a31fb53-8cc1-5394-ac16-e96b82fa6caa")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="508ac88d-5683-5d1f-9857-80a8a74e03e2")}catch(e){}}();
3
3
  import * as Sentry from "@sentry/node";
4
4
  import { BUNDLED_DSN } from "./sentry-dsn.generated.js";
5
5
  import { beforeSend } from "./sentry-before-send.js";
6
+ import { beforeBreadcrumb } from "./utils/breadcrumb-buffer.js";
6
7
  export function initSentry() {
7
8
  const dsn = BUNDLED_DSN || process.env.SENTRY_DSN;
8
9
  if (!dsn)
@@ -15,8 +16,9 @@ export function initSentry() {
15
16
  tags: { repo: "hq-cli" },
16
17
  },
17
18
  beforeSend,
19
+ beforeBreadcrumb,
18
20
  });
19
21
  }
20
22
  export { Sentry };
21
23
  //# sourceMappingURL=sentry.js.map
22
- //# debugId=9a31fb53-8cc1-5394-ac16-e96b82fa6caa
24
+ //# debugId=508ac88d-5683-5d1f-9857-80a8a74e03e2
@@ -0,0 +1,4 @@
1
+ import type { Breadcrumb } from "@sentry/node";
2
+ export declare function beforeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null;
3
+ export declare function getRecentBreadcrumbs(): Breadcrumb[];
4
+ //# sourceMappingURL=breadcrumb-buffer.d.ts.map
@@ -0,0 +1,18 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="6a54e8f0-10de-59c2-82d9-22de94d9e6f2")}catch(e){}}();
3
+ const BUFFER_SIZE = 20;
4
+ const _buffer = [];
5
+ // Sentry beforeBreadcrumb hook: records every breadcrumb in a ring buffer
6
+ // and returns it unchanged so Sentry still processes it normally.
7
+ export function beforeBreadcrumb(breadcrumb) {
8
+ _buffer.push(breadcrumb);
9
+ if (_buffer.length > BUFFER_SIZE) {
10
+ _buffer.shift();
11
+ }
12
+ return breadcrumb;
13
+ }
14
+ export function getRecentBreadcrumbs() {
15
+ return [..._buffer];
16
+ }
17
+ //# sourceMappingURL=breadcrumb-buffer.js.map
18
+ //# debugId=6a54e8f0-10de-59c2-82d9-22de94d9e6f2
@@ -0,0 +1,22 @@
1
+ export interface GitContext {
2
+ branch: string | null;
3
+ head: string | null;
4
+ dirty: boolean;
5
+ remoteUrl: string | null;
6
+ }
7
+ export interface DiagnosticsBlob {
8
+ cliVersion: string;
9
+ nodeVersion: string;
10
+ os: {
11
+ platform: string;
12
+ release: string;
13
+ arch: string;
14
+ };
15
+ command: string[];
16
+ cwd: string;
17
+ git: GitContext;
18
+ recentSentryBreadcrumbs: unknown[];
19
+ }
20
+ export declare function sanitizeArgv(argv: string[]): string[];
21
+ export declare function collectDiagnostics(): DiagnosticsBlob;
22
+ //# sourceMappingURL=feedback-diagnostics.d.ts.map
@@ -0,0 +1,95 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ee39c63b-ddaa-553d-b5db-77633d323d88")}catch(e){}}();
3
+ import * as os from "os";
4
+ import { execFileSync } from "child_process";
5
+ import { getRecentBreadcrumbs } from "./breadcrumb-buffer.js";
6
+ import { CLI_VERSION } from "../cli-version.js";
7
+ const SECRET_FLAGS = new Set([
8
+ "--token",
9
+ "--secret",
10
+ "--password",
11
+ "--key",
12
+ "--api-key",
13
+ "--access-token",
14
+ "--auth-token",
15
+ ]);
16
+ export function sanitizeArgv(argv) {
17
+ const result = [];
18
+ for (let i = 0; i < argv.length; i++) {
19
+ const arg = argv[i];
20
+ if (arg.startsWith("--") && arg.includes("=")) {
21
+ const eqIdx = arg.indexOf("=");
22
+ const flag = arg.slice(0, eqIdx);
23
+ if (SECRET_FLAGS.has(flag)) {
24
+ result.push(`${flag}=***`);
25
+ continue;
26
+ }
27
+ }
28
+ result.push(arg);
29
+ if (SECRET_FLAGS.has(arg) && i + 1 < argv.length) {
30
+ result.push("***");
31
+ i++;
32
+ }
33
+ }
34
+ return result;
35
+ }
36
+ function sanitizeRemoteUrl(url) {
37
+ return url.replace(/https?:\/\/[^@]+@/, "https://***@");
38
+ }
39
+ function runGit(args) {
40
+ return execFileSync("git", args, {
41
+ encoding: "utf-8",
42
+ timeout: 2000,
43
+ stdio: ["ignore", "pipe", "ignore"],
44
+ }).trim();
45
+ }
46
+ function collectGitContext() {
47
+ let branch = null;
48
+ let head = null;
49
+ let dirty = false;
50
+ let remoteUrl = null;
51
+ try {
52
+ branch = runGit(["rev-parse", "--abbrev-ref", "HEAD"]);
53
+ }
54
+ catch {
55
+ return { branch: null, head: null, dirty: false, remoteUrl: null };
56
+ }
57
+ try {
58
+ head = runGit(["rev-parse", "--short", "HEAD"]);
59
+ }
60
+ catch {
61
+ // best-effort
62
+ }
63
+ try {
64
+ const statusOut = runGit(["status", "--porcelain"]);
65
+ dirty = statusOut.length > 0;
66
+ }
67
+ catch {
68
+ // best-effort
69
+ }
70
+ try {
71
+ const raw = runGit(["remote", "get-url", "origin"]);
72
+ remoteUrl = sanitizeRemoteUrl(raw);
73
+ }
74
+ catch {
75
+ // no origin remote
76
+ }
77
+ return { branch, head, dirty, remoteUrl };
78
+ }
79
+ export function collectDiagnostics() {
80
+ return {
81
+ cliVersion: CLI_VERSION,
82
+ nodeVersion: process.version,
83
+ os: {
84
+ platform: os.platform(),
85
+ release: os.release(),
86
+ arch: os.arch(),
87
+ },
88
+ command: sanitizeArgv(process.argv.slice(2)),
89
+ cwd: process.cwd(),
90
+ git: collectGitContext(),
91
+ recentSentryBreadcrumbs: getRecentBreadcrumbs(),
92
+ };
93
+ }
94
+ //# sourceMappingURL=feedback-diagnostics.js.map
95
+ //# debugId=ee39c63b-ddaa-553d-b5db-77633d323d88
@@ -1,6 +1,7 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="05d73a12-54b0-559c-a964-de7dff2d48eb")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e1ca2b83-1de0-5000-ab66-f573000a579b")}catch(e){}}();
3
3
  import { DEFAULT_VAULT_API_URL } from './cognito-session.js';
4
+ import { Sentry } from '../sentry.js';
4
5
  export async function vaultApiFetch(opts) {
5
6
  const url = new URL(opts.path, DEFAULT_VAULT_API_URL);
6
7
  if (opts.query) {
@@ -8,14 +9,31 @@ export async function vaultApiFetch(opts) {
8
9
  url.searchParams.set(k, v);
9
10
  }
10
11
  }
11
- return fetch(url.toString(), {
12
- method: opts.method ?? 'GET',
12
+ const method = opts.method ?? 'GET';
13
+ const safeUrl = url.search ? `${url.origin}${url.pathname}?<redacted>` : `${url.origin}${url.pathname}`;
14
+ Sentry.addBreadcrumb({
15
+ category: "http",
16
+ message: `${method} ${opts.path}`,
17
+ level: "info",
18
+ data: { url: safeUrl, method },
19
+ });
20
+ const response = await fetch(url.toString(), {
21
+ method,
13
22
  headers: {
14
23
  Authorization: `Bearer ${opts.token}`,
15
24
  'Content-Type': 'application/json',
16
25
  },
17
26
  body: opts.body ? JSON.stringify(opts.body) : undefined,
18
27
  });
28
+ if (!response.ok) {
29
+ Sentry.addBreadcrumb({
30
+ category: "http",
31
+ message: `${method} ${opts.path} → ${response.status}`,
32
+ level: "warning",
33
+ data: { url: safeUrl, status: response.status },
34
+ });
35
+ }
36
+ return response;
19
37
  }
20
38
  async function resolveCompanyUid(token, slug) {
21
39
  const res = await vaultApiFetch({
@@ -88,4 +106,4 @@ export async function getEntityUid(token, opts) {
88
106
  return getCompanyUid(token, opts.companySlug);
89
107
  }
90
108
  //# sourceMappingURL=vault-api.js.map
91
- //# debugId=05d73a12-54b0-559c-a964-de7dff2d48eb
109
+ //# debugId=e1ca2b83-1de0-5000-ab66-f573000a579b
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.11.0",
3
+ "version": "5.12.1",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -11,6 +11,7 @@
11
11
  "build": "node scripts/generate-dsn.mjs && tsc",
12
12
  "typecheck": "tsc --noEmit",
13
13
  "test": "vitest run",
14
+ "vitest": "vitest",
14
15
  "clean": "rm -rf dist"
15
16
  },
16
17
  "dependencies": {
@@ -0,0 +1 @@
1
+ export const CLI_VERSION = "5.12.1";