@indigoai-us/hq-cli 5.77.14 → 5.78.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 +23 -0
- package/assets/scaffold/core/scripts/archive-old-threads.sh +86 -0
- package/assets/scaffold/core/scripts/backfill-company-skill-mirrors.sh +73 -0
- package/assets/scaffold/core/scripts/backfill-workspace-mirror.sh +51 -0
- package/assets/scaffold/core/scripts/codex-skill-bridge.sh +550 -0
- package/assets/scaffold/core/scripts/detect-stale-core-policy-mirror.sh +185 -0
- package/assets/scaffold/core/scripts/hq-status-summary.sh +187 -0
- package/assets/scaffold/core/scripts/ontology-readme-drift.sh +264 -0
- package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +93 -0
- package/assets/scaffold/core/scripts/rebuild-all-indexes.sh +69 -0
- package/assets/scaffold/core/scripts/rebuild-companies-index.sh +206 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +127 -0
- package/assets/scaffold/core/scripts/rebuild-orchestrator-index.sh +86 -0
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +105 -0
- package/assets/scaffold/core/scripts/rebuild-public-knowledge-index.sh +83 -0
- package/assets/scaffold/core/scripts/rebuild-reports-index.sh +114 -0
- package/assets/scaffold/core/scripts/rebuild-social-drafts-index.sh +135 -0
- package/assets/scaffold/core/scripts/rebuild-threads-index.sh +82 -0
- package/assets/scaffold/core/scripts/rebuild-workers-index.sh +97 -0
- package/assets/scaffold/core/scripts/resize-screenshot.sh +47 -0
- package/assets/scaffold/core/scripts/token-usage-report.sh +254 -0
- package/assets/scaffold/core/scripts/worktree.sh +136 -0
- package/dist/commands/agents.js +28 -12
- package/dist/commands/api-keys.js +100 -20
- package/dist/commands/core.d.ts +90 -0
- package/dist/commands/core.js +279 -0
- package/dist/commands/files-browse.d.ts +21 -0
- package/dist/commands/files-browse.js +145 -24
- package/dist/commands/files.js +33 -7
- package/dist/commands/members.js +16 -5
- package/dist/commands/pack-install.d.ts +14 -0
- package/dist/commands/pack-install.js +42 -2
- package/dist/commands/pkg-install.js +39 -2
- package/dist/commands/run.js +6 -2
- package/dist/commands/secrets.js +120 -65
- package/dist/main.js +6 -0
- package/dist/utils/api-key-command-gate.d.ts +45 -0
- package/dist/utils/api-key-command-gate.js +51 -0
- package/dist/utils/hq-roots.d.ts +91 -0
- package/dist/utils/hq-roots.js +212 -0
- package/dist/utils/resolve-vault-credential.d.ts +63 -3
- package/dist/utils/resolve-vault-credential.js +152 -6
- package/dist/utils/run-bundled-script.d.ts +66 -0
- package/dist/utils/run-bundled-script.js +80 -0
- package/dist/utils/vault-api.d.ts +21 -0
- package/dist/utils/vault-api.js +74 -3
- package/package.json +7 -1
- package/.claude/policies/hq-cli-interactive-reads-use-sts-vend.md +0 -49
- package/.github/workflows/ci.yml +0 -46
- package/.github/workflows/publish.yml +0 -36
- package/.github/workflows/scripts/smoke-test-pkg.sh +0 -97
- package/e2e/cli.test.ts +0 -128
- package/eslint.config.js +0 -37
- package/pnpm-workspace.yaml +0 -4
- package/scripts/chmod-bins.mjs +0 -49
- package/scripts/generate-dsn.mjs +0 -26
- package/scripts/generate-scan-packages-table.mjs +0 -113
- package/scripts/smoke-sources-signals.sh +0 -103
- package/src/bin/hq-auth-refresh.ts +0 -43
- package/src/cli-version.ts +0 -13
- package/src/commands/__fixtures__/make-tar.ts +0 -126
- package/src/commands/_patterns.ts +0 -10
- package/src/commands/add.ts +0 -75
- package/src/commands/agents.test.ts +0 -527
- package/src/commands/agents.ts +0 -923
- package/src/commands/api-keys.test.ts +0 -291
- package/src/commands/api-keys.ts +0 -382
- package/src/commands/artifact-verify.test.ts +0 -177
- package/src/commands/auth.test.ts +0 -82
- package/src/commands/auth.ts +0 -213
- package/src/commands/billing.test.ts +0 -158
- package/src/commands/billing.ts +0 -146
- package/src/commands/channels.test.ts +0 -74
- package/src/commands/channels.ts +0 -91
- package/src/commands/cloud-demote.test.ts +0 -401
- package/src/commands/cloud-demote.ts +0 -278
- package/src/commands/cloud-provision.test.ts +0 -1117
- package/src/commands/cloud-provision.ts +0 -942
- package/src/commands/cloud.pull-all.test.ts +0 -711
- package/src/commands/cloud.pull-per-company.test.ts +0 -188
- package/src/commands/cloud.push-all.test.ts +0 -485
- package/src/commands/cloud.scope-excluded-warning.test.ts +0 -22
- package/src/commands/cloud.selectors.test.ts +0 -208
- package/src/commands/cloud.status.test.ts +0 -160
- package/src/commands/cloud.test.ts +0 -41
- package/src/commands/cloud.ts +0 -2130
- package/src/commands/company.test.ts +0 -202
- package/src/commands/company.ts +0 -148
- package/src/commands/creators.test.ts +0 -60
- package/src/commands/creators.ts +0 -117
- package/src/commands/crm.test.ts +0 -179
- package/src/commands/crm.ts +0 -236
- package/src/commands/db-migrate.ts +0 -55
- package/src/commands/db-provision.ts +0 -114
- package/src/commands/db-sql.ts +0 -124
- package/src/commands/db-status.ts +0 -108
- package/src/commands/db.ts +0 -26
- package/src/commands/dm.test.ts +0 -748
- package/src/commands/dm.ts +0 -1226
- package/src/commands/feedback.test.ts +0 -413
- package/src/commands/feedback.ts +0 -169
- package/src/commands/files-browse.test.ts +0 -1249
- package/src/commands/files-browse.ts +0 -1506
- package/src/commands/files-delete.test.ts +0 -484
- package/src/commands/files-recovery.test.ts +0 -370
- package/src/commands/files.test.ts +0 -722
- package/src/commands/files.ts +0 -1446
- package/src/commands/group-grants.test.ts +0 -330
- package/src/commands/group-grants.ts +0 -455
- package/src/commands/groups.test.ts +0 -44
- package/src/commands/groups.ts +0 -368
- package/src/commands/integrations.test.ts +0 -633
- package/src/commands/integrations.ts +0 -507
- package/src/commands/list.ts +0 -85
- package/src/commands/login.ts +0 -52
- package/src/commands/logout.ts +0 -30
- package/src/commands/marketplace-install.test.ts +0 -543
- package/src/commands/marketplace-security.test.ts +0 -646
- package/src/commands/mcp-registration.test.ts +0 -2787
- package/src/commands/mcp-registration.ts +0 -2612
- package/src/commands/mcp-status.test.ts +0 -483
- package/src/commands/mcp-status.ts +0 -575
- package/src/commands/mcp-status.us011.test.ts +0 -243
- package/src/commands/meetings.test.ts +0 -522
- package/src/commands/meetings.ts +0 -789
- package/src/commands/members.test.ts +0 -1474
- package/src/commands/members.ts +0 -1181
- package/src/commands/onboard-identity-guard.test.ts +0 -99
- package/src/commands/onboard-identity-guard.ts +0 -103
- package/src/commands/onboard-join.test.ts +0 -96
- package/src/commands/onboard-join.ts +0 -101
- package/src/commands/onboard-warning.test.ts +0 -26
- package/src/commands/onboard-warning.ts +0 -12
- package/src/commands/onboard.ts +0 -267
- package/src/commands/outposts-heartbeat.test.ts +0 -299
- package/src/commands/outposts-heartbeat.ts +0 -310
- package/src/commands/outposts-replica-sync.test.ts +0 -272
- package/src/commands/outposts-self-deploy.test.ts +0 -243
- package/src/commands/outposts.test.ts +0 -1039
- package/src/commands/outposts.ts +0 -1935
- package/src/commands/pack-install-secret-authorization.test.ts +0 -115
- package/src/commands/pack-install.test.ts +0 -1469
- package/src/commands/pack-install.ts +0 -2256
- package/src/commands/pack-update-cache.test.ts +0 -149
- package/src/commands/packs-update-api-key.test.ts +0 -105
- package/src/commands/packs.test.ts +0 -88
- package/src/commands/packs.ts +0 -622
- package/src/commands/people.test.ts +0 -807
- package/src/commands/people.ts +0 -422
- package/src/commands/pkg-install.dispatch.test.ts +0 -103
- package/src/commands/pkg-install.ts +0 -221
- package/src/commands/pkg-list.test.ts +0 -79
- package/src/commands/pkg-list.ts +0 -13
- package/src/commands/pkg-remove.ts +0 -71
- package/src/commands/pkg-update.ts +0 -189
- package/src/commands/publish.test.ts +0 -650
- package/src/commands/publish.ts +0 -580
- package/src/commands/reindex.test.ts +0 -259
- package/src/commands/reindex.ts +0 -274
- package/src/commands/rescue.test.ts +0 -39
- package/src/commands/rescue.ts +0 -210
- package/src/commands/run.env-local.test.ts +0 -88
- package/src/commands/run.test.ts +0 -45
- package/src/commands/run.ts +0 -190
- package/src/commands/safe-extract.test.ts +0 -459
- package/src/commands/safe-extract.ts +0 -444
- package/src/commands/secrets-scope.test.ts +0 -56
- package/src/commands/secrets-scope.ts +0 -32
- package/src/commands/secrets.parse-destination.test.ts +0 -38
- package/src/commands/secrets.test.ts +0 -2050
- package/src/commands/secrets.ts +0 -2249
- package/src/commands/signals.ts +0 -351
- package/src/commands/skill.test.ts +0 -770
- package/src/commands/skill.ts +0 -796
- package/src/commands/sources.ts +0 -362
- package/src/commands/sync-mode.test.ts +0 -366
- package/src/commands/sync-mode.ts +0 -387
- package/src/commands/sync-narrow.test.ts +0 -633
- package/src/commands/sync-narrow.ts +0 -555
- package/src/commands/sync.ts +0 -165
- package/src/commands/team-sync.ts +0 -628
- package/src/commands/update.ts +0 -71
- package/src/commands/whoami.ts +0 -83
- package/src/commands/workers.test.ts +0 -158
- package/src/commands/workers.ts +0 -301
- package/src/index.test.ts +0 -32
- package/src/index.ts +0 -20
- package/src/lib/db/control-plane.test.ts +0 -59
- package/src/lib/db/control-plane.ts +0 -113
- package/src/lib/db/local.test.ts +0 -81
- package/src/lib/db/local.ts +0 -148
- package/src/lib/db/migrate.test.ts +0 -133
- package/src/lib/db/migrate.ts +0 -137
- package/src/lib/db/paths.test.ts +0 -112
- package/src/lib/db/paths.ts +0 -128
- package/src/lib/db/remote-engine.test.ts +0 -44
- package/src/lib/db/remote-engine.ts +0 -148
- package/src/lib/db/remote-sql.test.ts +0 -32
- package/src/lib/db/remote-sql.ts +0 -62
- package/src/lib/db/sql.test.ts +0 -106
- package/src/lib/db/sql.ts +0 -192
- package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
- package/src/lib/local-tree-diff.test.ts +0 -301
- package/src/lib/local-tree-diff.ts +0 -362
- package/src/lib/narrow-hint-banner.test.ts +0 -235
- package/src/lib/narrow-hint-banner.ts +0 -212
- package/src/lib/plan-limit-nag.test.ts +0 -317
- package/src/lib/plan-limit-nag.ts +0 -264
- package/src/main.ts +0 -371
- package/src/node-preflight.test.ts +0 -60
- package/src/node-preflight.ts +0 -67
- package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
- package/src/outpost/session-heartbeat-guard.test.ts +0 -105
- package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
- package/src/outpost/session-heartbeat-publisher.ts +0 -186
- package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
- package/src/outpost/session-heartbeat.test.ts +0 -459
- package/src/outpost/session-heartbeat.ts +0 -877
- package/src/packaging.test.ts +0 -109
- package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
- package/src/run/discover-schemas.test.ts +0 -153
- package/src/run/discover-schemas.ts +0 -79
- package/src/run/hq-plugin.test.ts +0 -371
- package/src/run/hq-plugin.ts +0 -277
- package/src/run/varlock-shape.test.ts +0 -57
- package/src/schemas/hq-package.schema.json +0 -155
- package/src/sentry-before-send.test.ts +0 -359
- package/src/sentry-before-send.ts +0 -221
- package/src/sentry-epipe.test.ts +0 -37
- package/src/sentry-release.test.ts +0 -54
- package/src/sentry.test.ts +0 -24
- package/src/sentry.ts +0 -54
- package/src/strategies/link.ts +0 -62
- package/src/strategies/merge.ts +0 -142
- package/src/types.ts +0 -146
- package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
- package/src/utils/auth-error.test.ts +0 -40
- package/src/utils/auth-error.ts +0 -42
- package/src/utils/billing-gate.test.ts +0 -95
- package/src/utils/billing-gate.ts +0 -228
- package/src/utils/breadcrumb-buffer.ts +0 -18
- package/src/utils/cli-telemetry.test.ts +0 -153
- package/src/utils/cli-telemetry.ts +0 -61
- package/src/utils/cognito-session.machine.test.ts +0 -109
- package/src/utils/cognito-session.refresh.test.ts +0 -84
- package/src/utils/cognito-session.test.ts +0 -328
- package/src/utils/cognito-session.ts +0 -448
- package/src/utils/company-selection-error.test.ts +0 -42
- package/src/utils/company-selection-error.ts +0 -45
- package/src/utils/contribution-table.ts +0 -83
- package/src/utils/environmental-error.test.ts +0 -45
- package/src/utils/environmental-error.ts +0 -39
- package/src/utils/epipe.test.ts +0 -28
- package/src/utils/epipe.ts +0 -29
- package/src/utils/expected-cli-error.test.ts +0 -28
- package/src/utils/expected-cli-error.ts +0 -39
- package/src/utils/feedback-diagnostics.test.ts +0 -183
- package/src/utils/feedback-diagnostics.ts +0 -123
- package/src/utils/feedback-screenshots.test.ts +0 -134
- package/src/utils/feedback-screenshots.ts +0 -124
- package/src/utils/feedback-versions.test.ts +0 -98
- package/src/utils/feedback-versions.ts +0 -68
- package/src/utils/git.ts +0 -74
- package/src/utils/integrity.ts +0 -59
- package/src/utils/intercepted-process-exit.test.ts +0 -37
- package/src/utils/intercepted-process-exit.ts +0 -36
- package/src/utils/login-provider.test.ts +0 -42
- package/src/utils/login-provider.ts +0 -30
- package/src/utils/manifest.test.ts +0 -167
- package/src/utils/manifest.ts +0 -131
- package/src/utils/pack-contributions.test.ts +0 -603
- package/src/utils/pack-contributions.ts +0 -450
- package/src/utils/people.ts +0 -215
- package/src/utils/registry-client.ts +0 -204
- package/src/utils/registry.ts +0 -90
- package/src/utils/resolve-vault-credential.test.ts +0 -69
- package/src/utils/resolve-vault-credential.ts +0 -60
- package/src/utils/sandbox-runner-client.test.ts +0 -279
- package/src/utils/sandbox-runner-client.ts +0 -260
- package/src/utils/secrets-cache.ts +0 -183
- package/src/utils/sentry-identity.ts +0 -45
- package/src/utils/settle-with-timeout.test.ts +0 -21
- package/src/utils/settle-with-timeout.ts +0 -22
- package/src/utils/user-yaml-error.test.ts +0 -24
- package/src/utils/user-yaml-error.ts +0 -30
- package/src/utils/vault-api.test.ts +0 -536
- package/src/utils/vault-api.ts +0 -382
- package/src/utils/version-check.test.ts +0 -176
- package/src/utils/version-check.ts +0 -155
- package/src/utils/version-gate.test.ts +0 -1024
- package/src/utils/version-gate.ts +0 -702
- package/test/commands/db-tenant-isolation.test.ts +0 -94
- package/test/commands/db.test.ts +0 -85
- package/test/commands/signals.test.ts +0 -207
- package/test/commands/sources.test.ts +0 -231
- package/test/e2e/smoke-install-mcp.sh +0 -113
- package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
- package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
- package/test/fixtures/signals/action_item/sample.md +0 -16
- package/test/fixtures/signals/summary/sample.md +0 -12
- package/test/fixtures/sources/meetings/sample.md +0 -25
- package/test/helpers/cli-runner.ts +0 -150
- package/test/helpers/s3-list-mock.ts +0 -79
- package/test/helpers/vault-service-mock.ts +0 -295
- package/test/sources-signals/smoke.test.ts +0 -228
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -11
- package/vitest.e2e.config.ts +0 -7
|
@@ -1,413 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { Readable } from "node:stream";
|
|
3
|
-
import { Command } from "commander";
|
|
4
|
-
|
|
5
|
-
vi.mock("../utils/cognito-session.js", () => ({
|
|
6
|
-
ensureCognitoToken: vi.fn(),
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
vi.mock("../utils/vault-api.js", () => ({
|
|
10
|
-
vaultApiFetch: vi.fn(),
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
vi.mock("../utils/feedback-diagnostics.js", () => ({
|
|
14
|
-
collectDiagnostics: vi.fn(() => ({
|
|
15
|
-
cliVersion: "5.11.0",
|
|
16
|
-
nodeVersion: process.version,
|
|
17
|
-
os: { platform: "linux", release: "5.15.0", arch: "x64" },
|
|
18
|
-
command: ["feedback", "bug"],
|
|
19
|
-
cwd: "/home/user",
|
|
20
|
-
git: { branch: "main", head: "abc1234", dirty: false, remoteUrl: null },
|
|
21
|
-
recentSentryBreadcrumbs: [],
|
|
22
|
-
})),
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
vi.mock("node:fs", async (importOriginal) => {
|
|
26
|
-
const actual = await importOriginal<typeof import("node:fs")>();
|
|
27
|
-
return {
|
|
28
|
-
...actual,
|
|
29
|
-
promises: {
|
|
30
|
-
...actual.promises,
|
|
31
|
-
readFile: vi.fn(),
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
37
|
-
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
38
|
-
import { collectDiagnostics } from "../utils/feedback-diagnostics.js";
|
|
39
|
-
import * as fs from "node:fs";
|
|
40
|
-
import {
|
|
41
|
-
submitFeedback,
|
|
42
|
-
readBodyFile,
|
|
43
|
-
registerFeedbackCommand,
|
|
44
|
-
BODY_MAX_BYTES,
|
|
45
|
-
} from "./feedback.js";
|
|
46
|
-
|
|
47
|
-
function jsonResponse(status: number, body: unknown): Response {
|
|
48
|
-
return new Response(JSON.stringify(body), {
|
|
49
|
-
status,
|
|
50
|
-
headers: { "Content-Type": "application/json" },
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const mockEnsureCognitoToken = vi.mocked(ensureCognitoToken);
|
|
55
|
-
const mockVaultApiFetch = vi.mocked(vaultApiFetch);
|
|
56
|
-
const mockCollectDiagnostics = vi.mocked(collectDiagnostics);
|
|
57
|
-
const mockReadFile = vi.mocked(fs.promises.readFile);
|
|
58
|
-
|
|
59
|
-
beforeEach(() => {
|
|
60
|
-
vi.clearAllMocks();
|
|
61
|
-
mockCollectDiagnostics.mockReturnValue({
|
|
62
|
-
cliVersion: "5.11.0",
|
|
63
|
-
nodeVersion: process.version,
|
|
64
|
-
os: { platform: "linux", release: "5.15.0", arch: "x64" },
|
|
65
|
-
command: ["feedback", "bug"],
|
|
66
|
-
cwd: "/home/user",
|
|
67
|
-
git: { branch: "main", head: "abc1234", dirty: false, remoteUrl: null },
|
|
68
|
-
recentSentryBreadcrumbs: [],
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
// submitFeedback
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
|
|
76
|
-
describe("submitFeedback", () => {
|
|
77
|
-
it("returns the id from a successful bug submission", async () => {
|
|
78
|
-
mockVaultApiFetch.mockResolvedValueOnce(
|
|
79
|
-
jsonResponse(200, { id: "feedback_abc123" }),
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const result = await submitFeedback({
|
|
83
|
-
type: "bug",
|
|
84
|
-
title: "Something broke",
|
|
85
|
-
body: "Details here",
|
|
86
|
-
token: "test-token",
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
expect(result.id).toBe("feedback_abc123");
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("sends type=feature for feature requests", async () => {
|
|
93
|
-
mockVaultApiFetch.mockResolvedValueOnce(
|
|
94
|
-
jsonResponse(200, { id: "feedback_feature1" }),
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
await submitFeedback({
|
|
98
|
-
type: "feature",
|
|
99
|
-
title: "Add dark mode",
|
|
100
|
-
body: "Would be great",
|
|
101
|
-
token: "test-token",
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const call = mockVaultApiFetch.mock.calls[0][0];
|
|
105
|
-
expect(call.body).toMatchObject({ type: "feature" });
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("sends POST to /v1/feedback with the correct title and body", async () => {
|
|
109
|
-
mockVaultApiFetch.mockResolvedValueOnce(
|
|
110
|
-
jsonResponse(200, { id: "feedback_xyz" }),
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
await submitFeedback({
|
|
114
|
-
type: "bug",
|
|
115
|
-
title: "My title",
|
|
116
|
-
body: "My body text",
|
|
117
|
-
token: "tok",
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const call = mockVaultApiFetch.mock.calls[0][0];
|
|
121
|
-
expect(call.path).toBe("/v1/feedback");
|
|
122
|
-
expect(call.method).toBe("POST");
|
|
123
|
-
expect(call.body).toMatchObject({ title: "My title", body: "My body text" });
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("includes company when provided", async () => {
|
|
127
|
-
mockVaultApiFetch.mockResolvedValueOnce(
|
|
128
|
-
jsonResponse(200, { id: "feedback_co1" }),
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
await submitFeedback({
|
|
132
|
-
type: "bug",
|
|
133
|
-
title: "Bug with company",
|
|
134
|
-
body: "Details",
|
|
135
|
-
company: "acme",
|
|
136
|
-
token: "tok",
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
const call = mockVaultApiFetch.mock.calls[0][0];
|
|
140
|
-
expect((call.body as Record<string, unknown>).company).toBe("acme");
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("omits company key when not provided", async () => {
|
|
144
|
-
mockVaultApiFetch.mockResolvedValueOnce(
|
|
145
|
-
jsonResponse(200, { id: "feedback_noco" }),
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
await submitFeedback({
|
|
149
|
-
type: "bug",
|
|
150
|
-
title: "No company",
|
|
151
|
-
body: "Details",
|
|
152
|
-
token: "tok",
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
const call = mockVaultApiFetch.mock.calls[0][0];
|
|
156
|
-
expect((call.body as Record<string, unknown>)).not.toHaveProperty("company");
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("includes screenshot keys when provided, omits the key otherwise", async () => {
|
|
160
|
-
mockVaultApiFetch.mockResolvedValueOnce(jsonResponse(200, { id: "feedback_s1" }));
|
|
161
|
-
await submitFeedback({
|
|
162
|
-
type: "bug",
|
|
163
|
-
title: "With shots",
|
|
164
|
-
body: "Details",
|
|
165
|
-
token: "tok",
|
|
166
|
-
screenshots: ["feedback-screenshots/prs_a/sub/0.png"],
|
|
167
|
-
});
|
|
168
|
-
expect((mockVaultApiFetch.mock.calls[0][0].body as Record<string, unknown>).screenshots).toEqual([
|
|
169
|
-
"feedback-screenshots/prs_a/sub/0.png",
|
|
170
|
-
]);
|
|
171
|
-
|
|
172
|
-
mockVaultApiFetch.mockResolvedValueOnce(jsonResponse(200, { id: "feedback_s2" }));
|
|
173
|
-
await submitFeedback({ type: "bug", title: "No shots", body: "Details", token: "tok", screenshots: [] });
|
|
174
|
-
expect(mockVaultApiFetch.mock.calls[1][0].body).not.toHaveProperty("screenshots");
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("attaches diagnostics from collectDiagnostics to the request body", async () => {
|
|
178
|
-
mockVaultApiFetch.mockResolvedValueOnce(
|
|
179
|
-
jsonResponse(200, { id: "feedback_diag" }),
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
await submitFeedback({
|
|
183
|
-
type: "bug",
|
|
184
|
-
title: "With diag",
|
|
185
|
-
body: "Body",
|
|
186
|
-
token: "tok",
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
expect(mockCollectDiagnostics).toHaveBeenCalledOnce();
|
|
190
|
-
const call = mockVaultApiFetch.mock.calls[0][0];
|
|
191
|
-
expect((call.body as Record<string, unknown>).diagnostics).toBeDefined();
|
|
192
|
-
expect(
|
|
193
|
-
((call.body as Record<string, unknown>).diagnostics as Record<string, unknown>).cliVersion,
|
|
194
|
-
).toBe("5.11.0");
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it("throws with the error from the response body on non-2xx", async () => {
|
|
198
|
-
mockVaultApiFetch.mockResolvedValueOnce(
|
|
199
|
-
jsonResponse(400, { error: "title too long" }),
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
await expect(
|
|
203
|
-
submitFeedback({
|
|
204
|
-
type: "bug",
|
|
205
|
-
title: "Bad",
|
|
206
|
-
body: "Body",
|
|
207
|
-
token: "tok",
|
|
208
|
-
}),
|
|
209
|
-
).rejects.toThrow("title too long");
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("throws with statusText when response has no parseable error body", async () => {
|
|
213
|
-
mockVaultApiFetch.mockResolvedValueOnce(
|
|
214
|
-
new Response("internal error", { status: 500, statusText: "Internal Server Error" }),
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
await expect(
|
|
218
|
-
submitFeedback({
|
|
219
|
-
type: "bug",
|
|
220
|
-
title: "Server fail",
|
|
221
|
-
body: "Body",
|
|
222
|
-
token: "tok",
|
|
223
|
-
}),
|
|
224
|
-
).rejects.toThrow(/Internal Server Error/);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("throws before fetching when body exceeds 64 KiB", async () => {
|
|
228
|
-
const largeBody = "x".repeat(BODY_MAX_BYTES + 1);
|
|
229
|
-
|
|
230
|
-
await expect(
|
|
231
|
-
submitFeedback({
|
|
232
|
-
type: "bug",
|
|
233
|
-
title: "Big body",
|
|
234
|
-
body: largeBody,
|
|
235
|
-
token: "tok",
|
|
236
|
-
}),
|
|
237
|
-
).rejects.toThrow(/64 KiB limit/);
|
|
238
|
-
|
|
239
|
-
expect(mockVaultApiFetch).not.toHaveBeenCalled();
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("accepts a body exactly at the 64 KiB limit", async () => {
|
|
243
|
-
const exactBody = "x".repeat(BODY_MAX_BYTES);
|
|
244
|
-
mockVaultApiFetch.mockResolvedValueOnce(
|
|
245
|
-
jsonResponse(200, { id: "feedback_exact" }),
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
const result = await submitFeedback({
|
|
249
|
-
type: "bug",
|
|
250
|
-
title: "Exact size",
|
|
251
|
-
body: exactBody,
|
|
252
|
-
token: "tok",
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
expect(result.id).toBe("feedback_exact");
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it("throws before fetching when body is empty or whitespace-only", async () => {
|
|
259
|
-
await expect(
|
|
260
|
-
submitFeedback({
|
|
261
|
-
type: "bug",
|
|
262
|
-
title: "Empty",
|
|
263
|
-
body: " \n\t ",
|
|
264
|
-
token: "tok",
|
|
265
|
-
}),
|
|
266
|
-
).rejects.toThrow(/must not be empty/);
|
|
267
|
-
|
|
268
|
-
expect(mockVaultApiFetch).not.toHaveBeenCalled();
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// HQ-AB: an empty/whitespace title (e.g. `--title ""`, or a title the /hq-bug
|
|
272
|
-
// skill derived to nothing) used to slip past Commander's required-flag check
|
|
273
|
-
// and 400 server-side, flooding Sentry with a context-free warning. The local
|
|
274
|
-
// guard now rejects it before any network call.
|
|
275
|
-
it("throws before fetching when title is empty or whitespace-only", async () => {
|
|
276
|
-
await expect(
|
|
277
|
-
submitFeedback({
|
|
278
|
-
type: "bug",
|
|
279
|
-
title: " \n\t ",
|
|
280
|
-
body: "Real body",
|
|
281
|
-
token: "tok",
|
|
282
|
-
}),
|
|
283
|
-
).rejects.toThrow(/title must not be empty/);
|
|
284
|
-
|
|
285
|
-
await expect(
|
|
286
|
-
submitFeedback({
|
|
287
|
-
type: "feature",
|
|
288
|
-
title: "",
|
|
289
|
-
body: "Real body",
|
|
290
|
-
token: "tok",
|
|
291
|
-
}),
|
|
292
|
-
).rejects.toThrow(/title must not be empty/);
|
|
293
|
-
|
|
294
|
-
expect(mockVaultApiFetch).not.toHaveBeenCalled();
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
// ---------------------------------------------------------------------------
|
|
299
|
-
// readBodyFile
|
|
300
|
-
// ---------------------------------------------------------------------------
|
|
301
|
-
|
|
302
|
-
describe("readBodyFile", () => {
|
|
303
|
-
it("reads content from a file path", async () => {
|
|
304
|
-
mockReadFile.mockResolvedValueOnce("file body content" as never);
|
|
305
|
-
|
|
306
|
-
const content = await readBodyFile("/tmp/body.md");
|
|
307
|
-
|
|
308
|
-
expect(mockReadFile).toHaveBeenCalledWith("/tmp/body.md", "utf-8");
|
|
309
|
-
expect(content).toBe("file body content");
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
it('reads content from stdin when path is "-"', async () => {
|
|
313
|
-
const mockStdin = new Readable({
|
|
314
|
-
read() {
|
|
315
|
-
this.push("stdin content");
|
|
316
|
-
this.push(null);
|
|
317
|
-
},
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
const content = await readBodyFile("-", mockStdin as NodeJS.ReadableStream);
|
|
321
|
-
|
|
322
|
-
expect(content).toBe("stdin content");
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
// ---------------------------------------------------------------------------
|
|
327
|
-
// registerFeedbackCommand — action integration
|
|
328
|
-
// ---------------------------------------------------------------------------
|
|
329
|
-
|
|
330
|
-
describe("registerFeedbackCommand — action integration", () => {
|
|
331
|
-
function makeProgram(): Command {
|
|
332
|
-
const p = new Command();
|
|
333
|
-
p.exitOverride();
|
|
334
|
-
registerFeedbackCommand(p);
|
|
335
|
-
return p;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
beforeEach(() => {
|
|
339
|
-
mockEnsureCognitoToken.mockResolvedValue("test-token");
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it("success: prints Submitted: <id> and does not call process.exit", async () => {
|
|
343
|
-
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
344
|
-
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
|
|
345
|
-
mockReadFile.mockResolvedValueOnce("Valid bug report body." as never);
|
|
346
|
-
mockVaultApiFetch.mockResolvedValueOnce(jsonResponse(200, { id: "feedback_abc123" }));
|
|
347
|
-
|
|
348
|
-
await makeProgram().parseAsync([
|
|
349
|
-
"node", "hq", "feedback", "bug",
|
|
350
|
-
"--title", "Test bug",
|
|
351
|
-
"--body-file", "/tmp/body.md",
|
|
352
|
-
]);
|
|
353
|
-
|
|
354
|
-
expect(logSpy).toHaveBeenCalledWith("Submitted: feedback_abc123");
|
|
355
|
-
expect(exitSpy).not.toHaveBeenCalled();
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
it("error: API error routes to chalk error prefix and process.exit(1)", async () => {
|
|
359
|
-
const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
360
|
-
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
|
|
361
|
-
mockReadFile.mockResolvedValueOnce("Some body text." as never);
|
|
362
|
-
mockVaultApiFetch.mockResolvedValueOnce(jsonResponse(400, { error: "title too long" }));
|
|
363
|
-
|
|
364
|
-
await makeProgram().parseAsync([
|
|
365
|
-
"node", "hq", "feedback", "bug",
|
|
366
|
-
"--title", "Bad",
|
|
367
|
-
"--body-file", "/tmp/body.md",
|
|
368
|
-
]);
|
|
369
|
-
|
|
370
|
-
expect(errSpy).toHaveBeenCalledWith(
|
|
371
|
-
expect.stringContaining("Error"),
|
|
372
|
-
expect.stringContaining("title too long"),
|
|
373
|
-
);
|
|
374
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
it("auth error: ensureCognitoToken throws → chalk error prefix and process.exit(1)", async () => {
|
|
378
|
-
const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
379
|
-
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
|
|
380
|
-
mockEnsureCognitoToken.mockRejectedValueOnce(new Error("not authenticated"));
|
|
381
|
-
|
|
382
|
-
await makeProgram().parseAsync([
|
|
383
|
-
"node", "hq", "feedback", "feature",
|
|
384
|
-
"--title", "Nice feat",
|
|
385
|
-
"--body-file", "/tmp/body.md",
|
|
386
|
-
]);
|
|
387
|
-
|
|
388
|
-
expect(errSpy).toHaveBeenCalledWith(
|
|
389
|
-
expect.stringContaining("Error"),
|
|
390
|
-
"not authenticated",
|
|
391
|
-
);
|
|
392
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
it("oversize body: submitFeedback throws 64 KiB error → process.exit(1)", async () => {
|
|
396
|
-
const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
397
|
-
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
|
|
398
|
-
const largeBody = "x".repeat(BODY_MAX_BYTES + 1);
|
|
399
|
-
mockReadFile.mockResolvedValueOnce(largeBody as never);
|
|
400
|
-
|
|
401
|
-
await makeProgram().parseAsync([
|
|
402
|
-
"node", "hq", "feedback", "bug",
|
|
403
|
-
"--title", "Big",
|
|
404
|
-
"--body-file", "/tmp/big.md",
|
|
405
|
-
]);
|
|
406
|
-
|
|
407
|
-
expect(errSpy).toHaveBeenCalledWith(
|
|
408
|
-
expect.stringContaining("Error"),
|
|
409
|
-
expect.stringContaining("64 KiB limit"),
|
|
410
|
-
);
|
|
411
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
412
|
-
});
|
|
413
|
-
});
|
package/src/commands/feedback.ts
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import { Command } from "commander";
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
5
|
-
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
6
|
-
import { collectDiagnostics } from "../utils/feedback-diagnostics.js";
|
|
7
|
-
import { MAX_SCREENSHOTS, uploadScreenshots } from "../utils/feedback-screenshots.js";
|
|
8
|
-
|
|
9
|
-
export const BODY_MAX_BYTES = 64 * 1024;
|
|
10
|
-
|
|
11
|
-
export interface FeedbackResult {
|
|
12
|
-
id: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface FeedbackSubmitOptions {
|
|
16
|
-
type: "bug" | "feature";
|
|
17
|
-
title: string;
|
|
18
|
-
body: string;
|
|
19
|
-
company?: string;
|
|
20
|
-
token: string;
|
|
21
|
-
/** S3 object keys of already-uploaded screenshots (see uploadScreenshots). */
|
|
22
|
-
screenshots?: string[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function readBodyFile(
|
|
26
|
-
bodyFile: string,
|
|
27
|
-
stdin?: NodeJS.ReadableStream,
|
|
28
|
-
): Promise<string> {
|
|
29
|
-
if (bodyFile === "-") {
|
|
30
|
-
const stream = stdin ?? process.stdin;
|
|
31
|
-
if ((stream as NodeJS.ReadStream).isTTY) {
|
|
32
|
-
throw new Error(
|
|
33
|
-
"--body-file - requires piped stdin (got interactive terminal).",
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
return new Promise((resolve, reject) => {
|
|
37
|
-
let data = "";
|
|
38
|
-
stream.setEncoding("utf8");
|
|
39
|
-
stream.on("data", (chunk: string) => {
|
|
40
|
-
data += chunk;
|
|
41
|
-
});
|
|
42
|
-
stream.on("end", () => resolve(data));
|
|
43
|
-
stream.on("error", reject);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
return fs.promises.readFile(bodyFile, "utf-8");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function submitFeedback(
|
|
50
|
-
opts: FeedbackSubmitOptions,
|
|
51
|
-
): Promise<FeedbackResult> {
|
|
52
|
-
// Validate the title locally, symmetric with the body check below. Commander's
|
|
53
|
-
// `requiredOption("--title")` only requires the flag to be PRESENT — an empty
|
|
54
|
-
// or whitespace-only value (`--title ""`, or a title the /hq-bug skill derived
|
|
55
|
-
// to nothing) passes the flag check, then the server rejects it with a 400
|
|
56
|
-
// "title (non-empty string) is required" that floods Sentry as a context-free
|
|
57
|
-
// warning (HQ-AB). Catch it here so the caller gets a clear, actionable error
|
|
58
|
-
// and the bad request never reaches the server.
|
|
59
|
-
if (opts.title.trim().length === 0) {
|
|
60
|
-
throw new Error(
|
|
61
|
-
"title must not be empty. Provide a short, non-whitespace title via --title.",
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (opts.body.trim().length === 0) {
|
|
66
|
-
throw new Error(
|
|
67
|
-
"body must not be empty. Provide at least one non-whitespace character.",
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const bodyBytes = Buffer.byteLength(opts.body, "utf8");
|
|
72
|
-
if (bodyBytes > BODY_MAX_BYTES) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
`Body exceeds 64 KiB limit (${bodyBytes} bytes). Reduce the body size before submitting.`,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const diagnostics = collectDiagnostics();
|
|
79
|
-
|
|
80
|
-
const requestBody: Record<string, unknown> = {
|
|
81
|
-
type: opts.type,
|
|
82
|
-
title: opts.title,
|
|
83
|
-
body: opts.body,
|
|
84
|
-
diagnostics,
|
|
85
|
-
};
|
|
86
|
-
if (opts.company) {
|
|
87
|
-
requestBody.company = opts.company;
|
|
88
|
-
}
|
|
89
|
-
if (opts.screenshots && opts.screenshots.length > 0) {
|
|
90
|
-
requestBody.screenshots = opts.screenshots;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const res = await vaultApiFetch({
|
|
94
|
-
token: opts.token,
|
|
95
|
-
path: "/v1/feedback",
|
|
96
|
-
method: "POST",
|
|
97
|
-
body: requestBody,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
if (!res.ok) {
|
|
101
|
-
const data = await res.json().catch(() => ({}));
|
|
102
|
-
const errMsg =
|
|
103
|
-
data &&
|
|
104
|
-
typeof data === "object" &&
|
|
105
|
-
!Array.isArray(data) &&
|
|
106
|
-
typeof (data as { error?: unknown }).error === "string"
|
|
107
|
-
? (data as { error: string }).error
|
|
108
|
-
: res.statusText;
|
|
109
|
-
throw new Error(`Failed to submit feedback: ${errMsg}`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const data = (await res.json()) as { id: string };
|
|
113
|
-
return { id: data.id };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function registerSubcommand(feedbackCmd: Command, type: "bug" | "feature"): void {
|
|
117
|
-
feedbackCmd
|
|
118
|
-
.command(type)
|
|
119
|
-
.description(type === "bug" ? "Report a bug" : "Request a feature")
|
|
120
|
-
.requiredOption("--title <text>", "Short title for the report")
|
|
121
|
-
.requiredOption(
|
|
122
|
-
"--body-file <path>",
|
|
123
|
-
"Path to a markdown file with the body; use - to read from stdin",
|
|
124
|
-
)
|
|
125
|
-
.option("--company <slug>", "Company slug to associate with the report")
|
|
126
|
-
.option(
|
|
127
|
-
"--screenshot <path>",
|
|
128
|
-
`Attach a screenshot (repeatable, up to ${MAX_SCREENSHOTS}; .png/.jpg/.jpeg/.webp/.gif)`,
|
|
129
|
-
(value: string, prev: string[]) => [...prev, value],
|
|
130
|
-
[] as string[],
|
|
131
|
-
)
|
|
132
|
-
.action(
|
|
133
|
-
async (opts: { title: string; bodyFile: string; company?: string; screenshot: string[] }) => {
|
|
134
|
-
try {
|
|
135
|
-
const token = await ensureCognitoToken({ interactive: false });
|
|
136
|
-
const body = await readBodyFile(opts.bodyFile);
|
|
137
|
-
// Validate + upload screenshots (direct-to-S3 via presigned PUT)
|
|
138
|
-
// before submitting, so the row references uploaded objects.
|
|
139
|
-
const screenshots = await uploadScreenshots({
|
|
140
|
-
paths: opts.screenshot ?? [],
|
|
141
|
-
token,
|
|
142
|
-
});
|
|
143
|
-
const result = await submitFeedback({
|
|
144
|
-
type,
|
|
145
|
-
title: opts.title,
|
|
146
|
-
body,
|
|
147
|
-
company: opts.company,
|
|
148
|
-
token,
|
|
149
|
-
screenshots,
|
|
150
|
-
});
|
|
151
|
-
console.log(`Submitted: ${result.id}`);
|
|
152
|
-
} catch (err) {
|
|
153
|
-
console.error(
|
|
154
|
-
chalk.red("Error:"),
|
|
155
|
-
err instanceof Error ? err.message : String(err),
|
|
156
|
-
);
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function registerFeedbackCommand(program: Command): void {
|
|
163
|
-
const feedbackCmd = program
|
|
164
|
-
.command("feedback")
|
|
165
|
-
.description("Submit a bug report or feature request to HQ");
|
|
166
|
-
|
|
167
|
-
registerSubcommand(feedbackCmd, "bug");
|
|
168
|
-
registerSubcommand(feedbackCmd, "feature");
|
|
169
|
-
}
|