@indigoai-us/hq-cli 5.77.13 → 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 +34 -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 +51 -2
- package/dist/commands/pkg-install.js +43 -0
- package/dist/commands/run.js +8 -0
- package/dist/commands/secrets.js +125 -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 -2247
- package/src/commands/pack-update-cache.test.ts +0 -149
- 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 -71
- package/src/commands/pkg-install.ts +0 -215
- 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 -184
- 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 -2037
- package/src/commands/secrets.ts +0 -2240
- 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,507 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `hq integrations` subcommand group — use company-connected apps (the
|
|
3
|
-
* integration factory's governed gateway) from any HQ-authenticated session.
|
|
4
|
-
*
|
|
5
|
-
* The console connects apps (e.g. Linear via OAuth sign-in); cloud agents get
|
|
6
|
-
* their tools natively through hq-pro's integration MCP gateway
|
|
7
|
-
* (POST /v1/integrations/mcp). This command gives LOCAL sessions (Claude
|
|
8
|
-
* Code / Codex chats, scripts) the same first-class path — no hand-crafted
|
|
9
|
-
* authenticated curl calls.
|
|
10
|
-
*
|
|
11
|
-
* Subcommands:
|
|
12
|
-
* hq integrations list Connected apps for the company.
|
|
13
|
-
* hq integrations tools --provider <p> What the connected app can do.
|
|
14
|
-
* hq integrations call <tool> --provider <p> --args '<json>'
|
|
15
|
-
* Invoke one of the app's tools.
|
|
16
|
-
* hq integrations approve|reject <queueId> --provider <p>
|
|
17
|
-
* Decide a queued (approval-gated)
|
|
18
|
-
* call; approve executes it.
|
|
19
|
-
*
|
|
20
|
-
* Governance: reads flow freely; calls that can change the app are subject to
|
|
21
|
-
* the connection's write policy (default: a person approves first). A queued
|
|
22
|
-
* outcome is surfaced clearly with the exact approve command to run.
|
|
23
|
-
*
|
|
24
|
-
* Auth: Cognito ID token via the shared session cache (`hq auth refresh`
|
|
25
|
-
* semantics); company resolves like every other command — `--company <slug>`
|
|
26
|
-
* or the caller's single active membership. Never hardcoded.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import { randomUUID } from "node:crypto";
|
|
30
|
-
import { Command } from "commander";
|
|
31
|
-
import chalk from "chalk";
|
|
32
|
-
import { ensureCognitoIdToken } from "../utils/cognito-session.js";
|
|
33
|
-
import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
|
|
34
|
-
import { AuthError } from "../utils/auth-error.js";
|
|
35
|
-
|
|
36
|
-
interface AdminConnection {
|
|
37
|
-
id: string;
|
|
38
|
-
provider: string;
|
|
39
|
-
status: string;
|
|
40
|
-
writePolicy?: string;
|
|
41
|
-
installation?: {
|
|
42
|
-
displayName?: string;
|
|
43
|
-
domain?: string;
|
|
44
|
-
status?: string;
|
|
45
|
-
} | null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
interface GatewayMessage {
|
|
49
|
-
result?: unknown;
|
|
50
|
-
error?: { code?: number; message?: string };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export class IntegrationsCliError extends Error {
|
|
54
|
-
/**
|
|
55
|
-
* True when the error is the caller's request/state/permission (a client 4xx
|
|
56
|
-
* or a local input/usage error) rather than an hq-cli defect. Expected errors
|
|
57
|
-
* are printed to the user but skipped for Sentry capture (HQ-CLI-6). Defaults
|
|
58
|
-
* to false so an unclassified error still reaches Sentry.
|
|
59
|
-
*/
|
|
60
|
-
readonly expected: boolean;
|
|
61
|
-
|
|
62
|
-
constructor(message: string, opts: { expected?: boolean } = {}) {
|
|
63
|
-
super(message);
|
|
64
|
-
this.name = "IntegrationsCliError";
|
|
65
|
-
this.expected = opts.expected ?? false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* A client 4xx is the caller's request/state/permission (bad params, stale
|
|
71
|
-
* queueId, a non-owner approving) — expected and user-facing, not a bug. A 5xx
|
|
72
|
-
* (or a 2xx protocol violation) is a genuine server/unknown fault worth a Sentry
|
|
73
|
-
* crash report.
|
|
74
|
-
*/
|
|
75
|
-
function isClientError(status: number): boolean {
|
|
76
|
-
return status >= 400 && status < 500;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// The integration gateway answers `POST /v1/integrations/mcp` JSON-RPC-style: a
|
|
80
|
-
// transport failure is a non-2xx HTTP status, but a GOVERNED refusal arrives as
|
|
81
|
-
// HTTP 200 carrying a JSON-RPC `error` object (mirrors hq-pro's
|
|
82
|
-
// integration-mcp/server.ts error mapping). These caller-side codes are the
|
|
83
|
-
// JSON-RPC analog of a client 4xx — the caller's request/state/permission,
|
|
84
|
-
// expected and actionable, not an hq-cli defect — so they are printed to the
|
|
85
|
-
// user and skipped for Sentry capture (HQ-CLI-B):
|
|
86
|
-
// -32003 UNAUTHORIZED — connection-level access denial, e.g. the
|
|
87
|
-
// "You do not have access to this integration. Ask its
|
|
88
|
-
// owner to share it with you." (IntegrationAccessDenied)
|
|
89
|
-
// that flooded Sentry, plus ConnectionNotFound / a
|
|
90
|
-
// read-only share rejecting a write.
|
|
91
|
-
// -32602 INVALID_PARAMS — an unknown tool or unsupported provider for the
|
|
92
|
-
// connection (a bad request the caller can correct).
|
|
93
|
-
// Everything else stays unexpected so a genuine fault still reaches Sentry:
|
|
94
|
-
// PROVIDER_ERROR (-32050, an upstream provider fault), INTERNAL_ERROR (-32603),
|
|
95
|
-
// CONFLICT (-32009, which the gateway also raises for a confirm queue being
|
|
96
|
-
// unavailable or an owner notification failing — real backend faults worth a
|
|
97
|
-
// report), METHOD_NOT_FOUND / PARSE_ERROR, and any absent or unrecognized code.
|
|
98
|
-
const EXPECTED_GATEWAY_ERROR_CODES = new Set<number>([-32003, -32602]);
|
|
99
|
-
|
|
100
|
-
function isExpectedGatewayError(code: number | undefined): boolean {
|
|
101
|
-
return code != null && EXPECTED_GATEWAY_ERROR_CODES.has(code);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// A 401 from ANY integration-gateway vault call means the caller's HQ session
|
|
105
|
-
// is expired or missing — an expected auth state fixed by `hq login`, not an
|
|
106
|
-
// hq-cli defect. Raise the same typed AuthError the vault company-resolution
|
|
107
|
-
// paths use (HQ-CLI-8) so the top-level handler prints one actionable message
|
|
108
|
-
// and skips Sentry, instead of surfacing the opaque, unactionable
|
|
109
|
-
// "Integration gateway request failed (HTTP 401)" that shipped as a fatal from
|
|
110
|
-
// `callGateway` (HQ-CLI-9). Non-401 statuses keep their existing behavior:
|
|
111
|
-
// other 4xx stay expected client errors, 5xx still report.
|
|
112
|
-
function raiseIfUnauthorized(res: Response): void {
|
|
113
|
-
if (res.status === 401) throw new AuthError();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** "factory:linear" → "linear"; mirrors hq-pro's factoryToolPrefix. */
|
|
117
|
-
export function toolPrefixForProvider(provider: string): string {
|
|
118
|
-
return provider
|
|
119
|
-
.replace(/^factory:/, "")
|
|
120
|
-
.trim()
|
|
121
|
-
.toLowerCase()
|
|
122
|
-
.replace(/[^a-z0-9]+/g, ".");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export async function fetchConnections(
|
|
126
|
-
token: string,
|
|
127
|
-
companyUid: string,
|
|
128
|
-
): Promise<AdminConnection[]> {
|
|
129
|
-
const res = await vaultApiFetch({
|
|
130
|
-
token,
|
|
131
|
-
path: "/v1/integrations/admin",
|
|
132
|
-
query: { companyUid },
|
|
133
|
-
});
|
|
134
|
-
if (!res.ok) {
|
|
135
|
-
raiseIfUnauthorized(res);
|
|
136
|
-
const body = (await res.json().catch(() => ({}))) as { error?: string };
|
|
137
|
-
throw new IntegrationsCliError(
|
|
138
|
-
body.error ?? `Failed to list integrations (HTTP ${res.status})`,
|
|
139
|
-
{ expected: isClientError(res.status) },
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
const data = (await res.json()) as { connections?: AdminConnection[] };
|
|
143
|
-
return data.connections ?? [];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Resolve one connection by `--connection acct_…` or `--provider linear`
|
|
148
|
-
* (matches `factory:<slug>` and bare provider ids, case-insensitive). Errors
|
|
149
|
-
* list what IS connected so the fix is one command away.
|
|
150
|
-
*/
|
|
151
|
-
export function selectConnection(
|
|
152
|
-
connections: AdminConnection[],
|
|
153
|
-
opts: { connection?: string; provider?: string },
|
|
154
|
-
): AdminConnection {
|
|
155
|
-
const active = connections.filter((c) => c.status !== "revoked");
|
|
156
|
-
if (opts.connection) {
|
|
157
|
-
const match = connections.find((c) => c.id === opts.connection);
|
|
158
|
-
if (!match) {
|
|
159
|
-
throw new IntegrationsCliError(
|
|
160
|
-
`No connection '${opts.connection}'. Run \`hq integrations list\` to see connected apps.`,
|
|
161
|
-
{ expected: true },
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
return match;
|
|
165
|
-
}
|
|
166
|
-
if (opts.provider) {
|
|
167
|
-
const want = opts.provider.trim().toLowerCase();
|
|
168
|
-
const match = active.find((c) => {
|
|
169
|
-
const bare = c.provider.replace(/^factory:/, "").toLowerCase();
|
|
170
|
-
return bare === want || c.provider.toLowerCase() === want;
|
|
171
|
-
});
|
|
172
|
-
if (!match) {
|
|
173
|
-
const available = active
|
|
174
|
-
.map((c) => c.provider.replace(/^factory:/, ""))
|
|
175
|
-
.join(", ");
|
|
176
|
-
throw new IntegrationsCliError(
|
|
177
|
-
`No connected app matches '${opts.provider}'.` +
|
|
178
|
-
(available ? ` Connected: ${available}.` : " Nothing is connected yet — connect apps on the console Integrations page."),
|
|
179
|
-
{ expected: true },
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
return match;
|
|
183
|
-
}
|
|
184
|
-
if (active.length === 1) return active[0];
|
|
185
|
-
if (active.length === 0) {
|
|
186
|
-
throw new IntegrationsCliError(
|
|
187
|
-
"No connected apps yet. Connect one on the console Integrations page, then retry.",
|
|
188
|
-
{ expected: true },
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
throw new IntegrationsCliError(
|
|
192
|
-
`Multiple apps are connected — pick one with --provider:\n` +
|
|
193
|
-
active.map((c) => ` --provider ${c.provider.replace(/^factory:/, "")}`).join("\n"),
|
|
194
|
-
{ expected: true },
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export async function callGateway(
|
|
199
|
-
token: string,
|
|
200
|
-
params: Record<string, unknown>,
|
|
201
|
-
): Promise<GatewayMessage> {
|
|
202
|
-
const res = await vaultApiFetch({
|
|
203
|
-
token,
|
|
204
|
-
path: "/v1/integrations/mcp",
|
|
205
|
-
method: "POST",
|
|
206
|
-
body: {
|
|
207
|
-
jsonrpc: "2.0",
|
|
208
|
-
id: `hq-cli-${randomUUID()}`,
|
|
209
|
-
method: "tools/call",
|
|
210
|
-
params,
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
const message = (await res.json().catch(() => null)) as GatewayMessage | null;
|
|
214
|
-
if (!res.ok || !message) {
|
|
215
|
-
raiseIfUnauthorized(res);
|
|
216
|
-
throw new IntegrationsCliError(
|
|
217
|
-
`Integration gateway request failed (HTTP ${res.status}).`,
|
|
218
|
-
{ expected: isClientError(res.status) },
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
if (message.error) {
|
|
222
|
-
throw new IntegrationsCliError(
|
|
223
|
-
message.error.message ?? "Integration gateway returned an error.",
|
|
224
|
-
{ expected: isExpectedGatewayError(message.error.code) },
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
return message;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Gateway results arrive MCP-style: `{ content: [{ type: "text", text }] }`
|
|
232
|
-
* where `text` is the provider's JSON. Unwrap to the inner payload; fall back
|
|
233
|
-
* to the raw result when the shape differs.
|
|
234
|
-
*/
|
|
235
|
-
export function unwrapGatewayResult(result: unknown): unknown {
|
|
236
|
-
if (result && typeof result === "object" && Array.isArray((result as { content?: unknown }).content)) {
|
|
237
|
-
const content = (result as { content: Array<{ type?: string; text?: string }> }).content;
|
|
238
|
-
const text = content.find((c) => c.type === "text")?.text;
|
|
239
|
-
if (typeof text === "string") {
|
|
240
|
-
try {
|
|
241
|
-
return JSON.parse(text);
|
|
242
|
-
} catch {
|
|
243
|
-
return text;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return result;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
interface QueuedOutcome {
|
|
251
|
-
queuedForApproval: true;
|
|
252
|
-
queueId: string;
|
|
253
|
-
connectionId: string;
|
|
254
|
-
expiresAt?: string;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export function queuedOutcome(payload: unknown): QueuedOutcome | null {
|
|
258
|
-
if (
|
|
259
|
-
payload &&
|
|
260
|
-
typeof payload === "object" &&
|
|
261
|
-
(payload as { queuedForApproval?: unknown }).queuedForApproval === true &&
|
|
262
|
-
typeof (payload as { queueId?: unknown }).queueId === "string"
|
|
263
|
-
) {
|
|
264
|
-
return payload as unknown as QueuedOutcome;
|
|
265
|
-
}
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function printJson(value: unknown): void {
|
|
270
|
-
console.log(JSON.stringify(value, null, 2));
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
export function registerIntegrationsCommand(program: Command): void {
|
|
274
|
-
const integrations = program
|
|
275
|
-
.command("integrations")
|
|
276
|
-
.description(
|
|
277
|
-
"Use company-connected apps (Linear, Notion, …) through HQ's governed integration gateway",
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
integrations
|
|
281
|
-
.command("list")
|
|
282
|
-
.description("List the company's connected apps")
|
|
283
|
-
.option("--company <slug>", "Company slug (defaults to your single active company)")
|
|
284
|
-
.option("--json", "Machine-readable output")
|
|
285
|
-
.action(async (opts: { company?: string; json?: boolean }) => {
|
|
286
|
-
const token = await ensureCognitoIdToken();
|
|
287
|
-
const companyUid = await getCompanyUid(token, opts.company);
|
|
288
|
-
const connections = await fetchConnections(token, companyUid);
|
|
289
|
-
if (opts.json) {
|
|
290
|
-
printJson(connections);
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
if (connections.length === 0) {
|
|
294
|
-
console.log(
|
|
295
|
-
"No apps connected yet. Connect one on the console Integrations page.",
|
|
296
|
-
);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
for (const c of connections) {
|
|
300
|
-
const name =
|
|
301
|
-
c.installation?.displayName ?? c.provider.replace(/^factory:/, "");
|
|
302
|
-
const flags = [
|
|
303
|
-
c.status,
|
|
304
|
-
c.writePolicy ? `writes: ${c.writePolicy}` : null,
|
|
305
|
-
c.installation?.status === "needs_credentials" ? "needs sign-in" : null,
|
|
306
|
-
]
|
|
307
|
-
.filter(Boolean)
|
|
308
|
-
.join(" · ");
|
|
309
|
-
console.log(
|
|
310
|
-
`${chalk.bold(name)} (${c.provider.replace(/^factory:/, "")}) ${chalk.dim(flags)}`,
|
|
311
|
-
);
|
|
312
|
-
console.log(chalk.dim(` connection: ${c.id}`));
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
integrations
|
|
317
|
-
.command("tools")
|
|
318
|
-
.description("List what a connected app can do")
|
|
319
|
-
.option("--provider <slug>", "Connected app (e.g. linear)")
|
|
320
|
-
.option("--connection <id>", "Connection id (acct_…)")
|
|
321
|
-
.option("--company <slug>", "Company slug")
|
|
322
|
-
.option("--json", "Machine-readable output")
|
|
323
|
-
.action(
|
|
324
|
-
async (opts: {
|
|
325
|
-
provider?: string;
|
|
326
|
-
connection?: string;
|
|
327
|
-
company?: string;
|
|
328
|
-
json?: boolean;
|
|
329
|
-
}) => {
|
|
330
|
-
const token = await ensureCognitoIdToken();
|
|
331
|
-
const companyUid = await getCompanyUid(token, opts.company);
|
|
332
|
-
const connection = selectConnection(
|
|
333
|
-
await fetchConnections(token, companyUid),
|
|
334
|
-
opts,
|
|
335
|
-
);
|
|
336
|
-
const prefix = toolPrefixForProvider(connection.provider);
|
|
337
|
-
const message = await callGateway(token, {
|
|
338
|
-
companyUid,
|
|
339
|
-
name: `${prefix}.mcp.tools.list`,
|
|
340
|
-
arguments: { companyUid, connectionId: connection.id },
|
|
341
|
-
});
|
|
342
|
-
const payload = unwrapGatewayResult(message.result) as {
|
|
343
|
-
tools?: Array<{ name: string; title?: string; description?: string }>;
|
|
344
|
-
};
|
|
345
|
-
if (opts.json) {
|
|
346
|
-
printJson(payload);
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
const tools = payload?.tools ?? [];
|
|
350
|
-
if (tools.length === 0) {
|
|
351
|
-
console.log("The app reported no tools.");
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
for (const tool of tools) {
|
|
355
|
-
const label = tool.title && tool.title !== tool.name ? ` ${chalk.dim(tool.title)}` : "";
|
|
356
|
-
console.log(`${chalk.bold(tool.name)}${label}`);
|
|
357
|
-
}
|
|
358
|
-
console.log(
|
|
359
|
-
chalk.dim(
|
|
360
|
-
`\n${tools.length} tools. Call one with: hq integrations call <tool> --provider ${connection.provider.replace(/^factory:/, "")} --args '<json>'`,
|
|
361
|
-
),
|
|
362
|
-
);
|
|
363
|
-
},
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
integrations
|
|
367
|
-
.command("call <tool>")
|
|
368
|
-
.description("Call one of a connected app's tools (approval-gated when it changes things)")
|
|
369
|
-
.option("--provider <slug>", "Connected app (e.g. linear)")
|
|
370
|
-
.option("--connection <id>", "Connection id (acct_…)")
|
|
371
|
-
.option("--company <slug>", "Company slug")
|
|
372
|
-
.option("--args <json>", "Tool arguments as JSON", "{}")
|
|
373
|
-
.option(
|
|
374
|
-
"--idempotency-key <key>",
|
|
375
|
-
"Stable key so retries execute at most once (default: generated)",
|
|
376
|
-
)
|
|
377
|
-
.option("--json", "Machine-readable output")
|
|
378
|
-
.action(
|
|
379
|
-
async (
|
|
380
|
-
tool: string,
|
|
381
|
-
opts: {
|
|
382
|
-
provider?: string;
|
|
383
|
-
connection?: string;
|
|
384
|
-
company?: string;
|
|
385
|
-
args: string;
|
|
386
|
-
idempotencyKey?: string;
|
|
387
|
-
json?: boolean;
|
|
388
|
-
},
|
|
389
|
-
) => {
|
|
390
|
-
let parsedArgs: Record<string, unknown>;
|
|
391
|
-
try {
|
|
392
|
-
const raw: unknown = JSON.parse(opts.args);
|
|
393
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
394
|
-
throw new Error("not an object");
|
|
395
|
-
}
|
|
396
|
-
parsedArgs = raw as Record<string, unknown>;
|
|
397
|
-
} catch {
|
|
398
|
-
throw new IntegrationsCliError(
|
|
399
|
-
`--args must be a JSON object, e.g. --args '{"assignee":"me"}'`,
|
|
400
|
-
{ expected: true },
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
const token = await ensureCognitoIdToken();
|
|
404
|
-
const companyUid = await getCompanyUid(token, opts.company);
|
|
405
|
-
const connection = selectConnection(
|
|
406
|
-
await fetchConnections(token, companyUid),
|
|
407
|
-
opts,
|
|
408
|
-
);
|
|
409
|
-
const prefix = toolPrefixForProvider(connection.provider);
|
|
410
|
-
const idempotencyKey = opts.idempotencyKey ?? `hq-cli-${randomUUID()}`;
|
|
411
|
-
const message = await callGateway(token, {
|
|
412
|
-
companyUid,
|
|
413
|
-
name: `${prefix}.mcp.tools.call`,
|
|
414
|
-
arguments: {
|
|
415
|
-
companyUid,
|
|
416
|
-
connectionId: connection.id,
|
|
417
|
-
toolName: tool,
|
|
418
|
-
arguments: parsedArgs,
|
|
419
|
-
idempotencyKey,
|
|
420
|
-
},
|
|
421
|
-
});
|
|
422
|
-
const payload = unwrapGatewayResult(message.result);
|
|
423
|
-
const queued = queuedOutcome(payload);
|
|
424
|
-
if (queued) {
|
|
425
|
-
if (opts.json) {
|
|
426
|
-
printJson(payload);
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
console.log(
|
|
430
|
-
chalk.yellow(
|
|
431
|
-
"Queued for approval — this call can change the app, so a company owner decides first.",
|
|
432
|
-
),
|
|
433
|
-
);
|
|
434
|
-
console.log(
|
|
435
|
-
`Approve with:\n hq integrations approve ${queued.queueId} --provider ${connection.provider.replace(/^factory:/, "")}`,
|
|
436
|
-
);
|
|
437
|
-
if (queued.expiresAt) {
|
|
438
|
-
console.log(chalk.dim(`Expires ${queued.expiresAt}`));
|
|
439
|
-
}
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
printJson(payload);
|
|
443
|
-
},
|
|
444
|
-
);
|
|
445
|
-
|
|
446
|
-
for (const decision of ["approve", "reject"] as const) {
|
|
447
|
-
integrations
|
|
448
|
-
.command(`${decision} <queueId>`)
|
|
449
|
-
.description(
|
|
450
|
-
decision === "approve"
|
|
451
|
-
? "Approve a queued call — it executes exactly once (owner only)"
|
|
452
|
-
: "Reject a queued call (owner only)",
|
|
453
|
-
)
|
|
454
|
-
.option("--provider <slug>", "Connected app (e.g. linear)")
|
|
455
|
-
.option("--connection <id>", "Connection id (acct_…)")
|
|
456
|
-
.option("--company <slug>", "Company slug")
|
|
457
|
-
.option("--json", "Machine-readable output")
|
|
458
|
-
.action(
|
|
459
|
-
async (
|
|
460
|
-
queueId: string,
|
|
461
|
-
opts: {
|
|
462
|
-
provider?: string;
|
|
463
|
-
connection?: string;
|
|
464
|
-
company?: string;
|
|
465
|
-
json?: boolean;
|
|
466
|
-
},
|
|
467
|
-
) => {
|
|
468
|
-
const token = await ensureCognitoIdToken();
|
|
469
|
-
const companyUid = await getCompanyUid(token, opts.company);
|
|
470
|
-
const connection = selectConnection(
|
|
471
|
-
await fetchConnections(token, companyUid),
|
|
472
|
-
opts,
|
|
473
|
-
);
|
|
474
|
-
const res = await vaultApiFetch({
|
|
475
|
-
token,
|
|
476
|
-
path: `/v1/integrations/confirm/${encodeURIComponent(queueId)}/${decision}`,
|
|
477
|
-
method: "POST",
|
|
478
|
-
body: { companyUid, connectionId: connection.id },
|
|
479
|
-
});
|
|
480
|
-
const body = (await res.json().catch(() => ({}))) as {
|
|
481
|
-
status?: string;
|
|
482
|
-
result?: unknown;
|
|
483
|
-
error?: string;
|
|
484
|
-
};
|
|
485
|
-
if (!res.ok) {
|
|
486
|
-
raiseIfUnauthorized(res);
|
|
487
|
-
throw new IntegrationsCliError(
|
|
488
|
-
body.error ?? `${decision} failed (HTTP ${res.status})`,
|
|
489
|
-
{ expected: isClientError(res.status) },
|
|
490
|
-
);
|
|
491
|
-
}
|
|
492
|
-
if (opts.json) {
|
|
493
|
-
printJson(body);
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
console.log(
|
|
497
|
-
decision === "approve"
|
|
498
|
-
? chalk.green(`Approved — the call ran (status: ${body.status ?? "resumed"}).`)
|
|
499
|
-
: chalk.yellow(`Rejected (status: ${body.status ?? "rejected"}).`),
|
|
500
|
-
);
|
|
501
|
-
if (decision === "approve" && body.result !== undefined) {
|
|
502
|
-
printJson(unwrapGatewayResult(body.result));
|
|
503
|
-
}
|
|
504
|
-
},
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
}
|
package/src/commands/list.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* hq modules list command (US-005)
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import { Command } from 'commander';
|
|
8
|
-
import { findHqRoot, readManifest, readLock, getModulesDir } from '../utils/manifest.js';
|
|
9
|
-
import { isRepo, getCurrentCommit, isBehindRemote } from '../utils/git.js';
|
|
10
|
-
|
|
11
|
-
export function registerListCommand(program: Command): void {
|
|
12
|
-
program
|
|
13
|
-
.command('list')
|
|
14
|
-
.alias('ls')
|
|
15
|
-
.description('List all modules and their status')
|
|
16
|
-
.action(async () => {
|
|
17
|
-
try {
|
|
18
|
-
const hqRoot = findHqRoot();
|
|
19
|
-
const manifest = readManifest(hqRoot);
|
|
20
|
-
|
|
21
|
-
if (!manifest || manifest.modules.length === 0) {
|
|
22
|
-
console.log('No modules in manifest. Use "hq modules add" to add modules.');
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const modulesDir = getModulesDir(hqRoot);
|
|
27
|
-
const lock = readLock(hqRoot);
|
|
28
|
-
|
|
29
|
-
console.log('Modules:\n');
|
|
30
|
-
|
|
31
|
-
for (const module of manifest.modules) {
|
|
32
|
-
console.log(` ${module.name}`);
|
|
33
|
-
|
|
34
|
-
if (module.strategy === 'package') {
|
|
35
|
-
// hq-pack content pack — wired by scripts/scan-packages.sh
|
|
36
|
-
console.log(` Strategy: package`);
|
|
37
|
-
console.log(` Source: ${module.source}`);
|
|
38
|
-
if (module.version) {
|
|
39
|
-
console.log(` Version: ${module.version}`);
|
|
40
|
-
}
|
|
41
|
-
if (module.installed_at) {
|
|
42
|
-
console.log(` Path: ${module.installed_at}`);
|
|
43
|
-
}
|
|
44
|
-
if (module.resolved_sha) {
|
|
45
|
-
console.log(` SHA: ${module.resolved_sha}`);
|
|
46
|
-
}
|
|
47
|
-
console.log();
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// legacy git-repo module
|
|
52
|
-
const moduleDir = path.join(modulesDir, module.name);
|
|
53
|
-
const installed = await isRepo(moduleDir);
|
|
54
|
-
|
|
55
|
-
console.log(` Repo: ${module.repo}`);
|
|
56
|
-
console.log(` Branch: ${module.branch || 'main'}`);
|
|
57
|
-
console.log(` Strategy: ${module.strategy}`);
|
|
58
|
-
console.log(` Paths: ${module.paths.map(p => `${p.src} -> ${p.dest}`).join(', ')}`);
|
|
59
|
-
|
|
60
|
-
if (installed) {
|
|
61
|
-
const commit = await getCurrentCommit(moduleDir);
|
|
62
|
-
const shortCommit = commit.slice(0, 7);
|
|
63
|
-
const lockedCommit = lock?.locked[module.name];
|
|
64
|
-
const isLocked = lockedCommit === commit;
|
|
65
|
-
|
|
66
|
-
console.log(` Status: ✓ installed @ ${shortCommit}${isLocked ? ' (locked)' : ''}`);
|
|
67
|
-
|
|
68
|
-
// Check if behind upstream
|
|
69
|
-
const { behind, commits } = await isBehindRemote(moduleDir);
|
|
70
|
-
if (behind) {
|
|
71
|
-
console.log(` Updates: ${commits} commit(s) behind remote`);
|
|
72
|
-
}
|
|
73
|
-
} else {
|
|
74
|
-
console.log(` Status: ✗ not installed`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
console.log();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error('Error:', error instanceof Error ? error.message : error);
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
}
|
package/src/commands/login.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* hq login — opens browser for Cognito auth.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Command } from 'commander';
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
import { browserLogin, loadCachedTokens, isExpiring } from '@indigoai-us/hq-cloud';
|
|
8
|
-
import { cognitoConfigForLoginProvider } from '../utils/login-provider.js';
|
|
9
|
-
|
|
10
|
-
function peekIdToken(idToken: string): { email?: string; sub?: string } {
|
|
11
|
-
try {
|
|
12
|
-
const payload = idToken.split('.')[1];
|
|
13
|
-
if (!payload) return {};
|
|
14
|
-
const pad = payload.length % 4 === 0 ? '' : '='.repeat(4 - (payload.length % 4));
|
|
15
|
-
const normalized = payload.replace(/-/g, '+').replace(/_/g, '/') + pad;
|
|
16
|
-
const decoded = JSON.parse(Buffer.from(normalized, 'base64').toString('utf-8'));
|
|
17
|
-
return { email: decoded.email, sub: decoded.sub };
|
|
18
|
-
} catch {
|
|
19
|
-
return {};
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function registerLoginCommand(program: Command): void {
|
|
24
|
-
program
|
|
25
|
-
.command('login')
|
|
26
|
-
.description('Authenticate with HQ via Cognito')
|
|
27
|
-
.option(
|
|
28
|
-
'--provider <provider>',
|
|
29
|
-
'OAuth provider to use: google, microsoft, or picker',
|
|
30
|
-
)
|
|
31
|
-
.action(async (options: { provider?: string }) => {
|
|
32
|
-
try {
|
|
33
|
-
const existing = loadCachedTokens();
|
|
34
|
-
if (existing && !isExpiring(existing, 120)) {
|
|
35
|
-
const who = peekIdToken(existing.idToken).email ?? 'cached session';
|
|
36
|
-
console.log(chalk.green(`Already logged in as ${who}. Run \`hq logout\` to switch accounts.`));
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
console.log('Opening browser for authentication...');
|
|
41
|
-
const tokens = await browserLogin(cognitoConfigForLoginProvider(options.provider));
|
|
42
|
-
const who = peekIdToken(tokens.idToken).email ?? 'HQ';
|
|
43
|
-
console.log(chalk.green(`Logged in as ${who}`));
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error(
|
|
46
|
-
chalk.red('Login failed:'),
|
|
47
|
-
error instanceof Error ? error.message : 'Unknown error'
|
|
48
|
-
);
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
package/src/commands/logout.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* hq logout — clears cached Cognito session
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Command } from 'commander';
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
import { clearCachedTokens, loadCachedTokens } from '@indigoai-us/hq-cloud';
|
|
8
|
-
|
|
9
|
-
export function registerLogoutCommand(program: Command): void {
|
|
10
|
-
program
|
|
11
|
-
.command('logout')
|
|
12
|
-
.description('Log out and clear cached credentials')
|
|
13
|
-
.action(async () => {
|
|
14
|
-
try {
|
|
15
|
-
const existing = loadCachedTokens();
|
|
16
|
-
if (!existing) {
|
|
17
|
-
console.log(chalk.yellow('No cached HQ session'));
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
clearCachedTokens();
|
|
21
|
-
console.log(chalk.green('Logged out successfully'));
|
|
22
|
-
} catch (error) {
|
|
23
|
-
console.error(
|
|
24
|
-
chalk.red('Logout failed:'),
|
|
25
|
-
error instanceof Error ? error.message : 'Unknown error'
|
|
26
|
-
);
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
}
|