@indigoai-us/hq-cli 5.61.0 → 5.62.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.
Files changed (80) hide show
  1. package/dist/commands/agents.d.ts +109 -0
  2. package/dist/commands/agents.js +385 -0
  3. package/dist/commands/db-migrate.d.ts +6 -0
  4. package/dist/commands/db-migrate.js +42 -0
  5. package/dist/commands/db-provision.d.ts +15 -0
  6. package/dist/commands/db-provision.js +78 -0
  7. package/dist/commands/db-sql.d.ts +9 -0
  8. package/dist/commands/db-sql.js +81 -0
  9. package/dist/commands/db-status.d.ts +7 -0
  10. package/dist/commands/db-status.js +70 -0
  11. package/dist/commands/db.d.ts +9 -0
  12. package/dist/commands/db.js +23 -0
  13. package/dist/commands/integrations.d.ts +78 -0
  14. package/dist/commands/integrations.js +309 -0
  15. package/dist/commands/members.js +4 -4
  16. package/dist/commands/outposts.d.ts +60 -0
  17. package/dist/commands/outposts.js +255 -0
  18. package/dist/commands/secrets.d.ts +8 -0
  19. package/dist/commands/secrets.js +23 -8
  20. package/dist/commands/skill.d.ts +153 -0
  21. package/dist/commands/skill.js +593 -0
  22. package/dist/commands/workers.d.ts +48 -0
  23. package/dist/commands/workers.js +229 -0
  24. package/dist/lib/db/control-plane.d.ts +45 -0
  25. package/dist/lib/db/control-plane.js +81 -0
  26. package/dist/lib/db/local.d.ts +49 -0
  27. package/dist/lib/db/local.js +106 -0
  28. package/dist/lib/db/migrate.d.ts +41 -0
  29. package/dist/lib/db/migrate.js +104 -0
  30. package/dist/lib/db/paths.d.ts +56 -0
  31. package/dist/lib/db/paths.js +103 -0
  32. package/dist/lib/db/remote-engine.d.ts +58 -0
  33. package/dist/lib/db/remote-engine.js +90 -0
  34. package/dist/lib/db/remote-sql.d.ts +22 -0
  35. package/dist/lib/db/remote-sql.js +39 -0
  36. package/dist/lib/db/sql.d.ts +49 -0
  37. package/dist/lib/db/sql.js +132 -0
  38. package/dist/main.js +27 -2
  39. package/dist/utils/cognito-session.js +3 -3
  40. package/dist/utils/sandbox-runner-client.js +3 -3
  41. package/package.json +9 -1
  42. package/pnpm-workspace.yaml +2 -0
  43. package/src/commands/agents.test.ts +297 -0
  44. package/src/commands/agents.ts +561 -0
  45. package/src/commands/db-migrate.ts +55 -0
  46. package/src/commands/db-provision.ts +102 -0
  47. package/src/commands/db-sql.ts +124 -0
  48. package/src/commands/db-status.ts +100 -0
  49. package/src/commands/db.ts +26 -0
  50. package/src/commands/integrations.test.ts +284 -0
  51. package/src/commands/integrations.ts +438 -0
  52. package/src/commands/members.ts +2 -2
  53. package/src/commands/outposts.test.ts +177 -0
  54. package/src/commands/outposts.ts +338 -0
  55. package/src/commands/secrets.parse-destination.test.ts +38 -0
  56. package/src/commands/secrets.test.ts +24 -0
  57. package/src/commands/secrets.ts +30 -10
  58. package/src/commands/skill.test.ts +770 -0
  59. package/src/commands/skill.ts +796 -0
  60. package/src/commands/workers.test.ts +158 -0
  61. package/src/commands/workers.ts +298 -0
  62. package/src/lib/db/control-plane.test.ts +59 -0
  63. package/src/lib/db/control-plane.ts +113 -0
  64. package/src/lib/db/local.test.ts +81 -0
  65. package/src/lib/db/local.ts +148 -0
  66. package/src/lib/db/migrate.test.ts +133 -0
  67. package/src/lib/db/migrate.ts +137 -0
  68. package/src/lib/db/paths.test.ts +112 -0
  69. package/src/lib/db/paths.ts +128 -0
  70. package/src/lib/db/remote-engine.test.ts +44 -0
  71. package/src/lib/db/remote-engine.ts +148 -0
  72. package/src/lib/db/remote-sql.test.ts +32 -0
  73. package/src/lib/db/remote-sql.ts +62 -0
  74. package/src/lib/db/sql.test.ts +106 -0
  75. package/src/lib/db/sql.ts +192 -0
  76. package/src/main.ts +31 -0
  77. package/src/utils/cognito-session.ts +1 -1
  78. package/src/utils/sandbox-runner-client.ts +1 -1
  79. package/test/commands/db-tenant-isolation.test.ts +94 -0
  80. package/test/commands/db.test.ts +85 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * hq db sql — run SQL against the company local vault DB (US-004).
3
+ *
4
+ * Default is read-only. Prefer `hq db migrate` for schema changes.
5
+ * Never prints remote connection strings.
6
+ */
7
+
8
+ !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]="0de705fa-ba79-5f19-9797-2a68dd79f24d")}catch(e){}}();
9
+ import chalk from "chalk";
10
+ import fs from "node:fs";
11
+ import { runRemoteSql } from "../lib/db/remote-sql.js";
12
+ import { formatSqlResult, runLocalSql } from "../lib/db/sql.js";
13
+ function readSqlArg(sqlArgs, stdinFlag) {
14
+ if (stdinFlag || sqlArgs.length === 0) {
15
+ if (process.stdin.isTTY && sqlArgs.length === 0) {
16
+ throw new Error("SQL required: pass as arguments after -- or pipe via stdin");
17
+ }
18
+ return fs.readFileSync(0, "utf8");
19
+ }
20
+ return sqlArgs.join(" ");
21
+ }
22
+ export function registerDbSqlCommand(db) {
23
+ db.command("sql")
24
+ .description("Run SQL against the company vault DB (local default; --remote for secrets-injected remote; read-only by default)")
25
+ .requiredOption("--company <slug>", "Company slug (tenant scope; required)")
26
+ .option("--write", "Allow write/DDL statements (discouraged for ad-hoc DDL)", false)
27
+ .option("--remote", "Use remote tier via vault-bound credentials (not auto-replicated with local)", false)
28
+ .option("--db-path <path>", "Dangerous: open a non-canonical DB path (denied unless --allow-cross-company-path)")
29
+ .option("--allow-cross-company-path", "Dangerous: permit --db-path outside this company's canonical local DB", false)
30
+ .option("--format <fmt>", "Output format: jsonl | table", "jsonl")
31
+ .option("--home <path>", "Override home for local DB root (tests)")
32
+ .option("--stdin", "Read SQL from stdin", false)
33
+ .argument("[sql...]", "SQL statement (or use --stdin / pipe)")
34
+ .action(async (sqlParts, opts) => {
35
+ try {
36
+ const company = String(opts.company ?? "").trim();
37
+ if (!company) {
38
+ console.error(chalk.red("Error: --company is required"));
39
+ process.exitCode = 1;
40
+ return;
41
+ }
42
+ const sql = readSqlArg(sqlParts, !!opts.stdin).trim();
43
+ const format = opts.format === "table" ? "table" : "jsonl";
44
+ if (opts.remote) {
45
+ // Local and remote are not auto-replicated (v1 limitation).
46
+ const remoteResult = await runRemoteSql({
47
+ company,
48
+ sql,
49
+ getConnectionConfig: async () => {
50
+ throw new Error("no remote binding");
51
+ },
52
+ });
53
+ console.log(formatSqlResult({
54
+ columns: remoteResult.rows[0]
55
+ ? Object.keys(remoteResult.rows[0])
56
+ : [],
57
+ rows: remoteResult.rows,
58
+ changes: 0,
59
+ readonly: true,
60
+ }, format));
61
+ return;
62
+ }
63
+ const result = runLocalSql({
64
+ company,
65
+ sql,
66
+ write: !!opts.write,
67
+ dbPathOverride: opts.dbPath,
68
+ allowCrossCompanyPath: !!opts.allowCrossCompanyPath,
69
+ format,
70
+ ...(opts.home ? { home: opts.home } : {}),
71
+ });
72
+ console.log(formatSqlResult(result, format));
73
+ }
74
+ catch (error) {
75
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
76
+ process.exitCode = 1;
77
+ }
78
+ });
79
+ }
80
+ //# sourceMappingURL=db-sql.js.map
81
+ //# debugId=0de705fa-ba79-5f19-9797-2a68dd79f24d
@@ -0,0 +1,7 @@
1
+ /**
2
+ * hq db status — ensure local vault DB exists and report health.
3
+ * Optionally reports remote tier when control plane returns a binding (US-009).
4
+ */
5
+ import { Command } from "commander";
6
+ export declare function registerDbStatusCommand(db: Command): void;
7
+ //# sourceMappingURL=db-status.d.ts.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * hq db status — ensure local vault DB exists and report health.
3
+ * Optionally reports remote tier when control plane returns a binding (US-009).
4
+ */
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]="8b820218-b1e5-57a2-8acd-d8e749a0d83f")}catch(e){}}();
7
+ import chalk from "chalk";
8
+ import { ControlPlaneDbClient } from "../lib/db/control-plane.js";
9
+ import { ensureAndStatusLocalDb, formatLocalDbStatus, } from "../lib/db/local.js";
10
+ export function registerDbStatusCommand(db) {
11
+ db.command("status")
12
+ .description("Show local (and remote when bound) vault DB status; auto-creates local SQLite if missing")
13
+ .requiredOption("--company <slug>", "Company slug (tenant scope; required)")
14
+ .option("--home <path>", "Override home directory for local DB root (tests / advanced only)")
15
+ .option("--remote", "Also query control plane for remote tier status (requires auth)", false)
16
+ .option("--company-uid <uid>", "Company uid for remote status (with --remote)")
17
+ .action(async (opts) => {
18
+ try {
19
+ const company = String(opts.company ?? "").trim();
20
+ if (!company) {
21
+ console.error(chalk.red("Error: --company is required"));
22
+ process.exitCode = 1;
23
+ return;
24
+ }
25
+ const status = ensureAndStatusLocalDb(company, opts.home ? { home: opts.home } : undefined);
26
+ // Never print remote connection material.
27
+ console.log(formatLocalDbStatus(status));
28
+ if (opts.remote) {
29
+ const companyUid = opts.companyUid || `cmp_${company}`;
30
+ const baseUrl = process.env.HQ_API_BASE_URL ||
31
+ process.env.HQ_CLOUD_API_URL ||
32
+ "https://hqapi.getindigo.ai";
33
+ try {
34
+ const client = new ControlPlaneDbClient({
35
+ baseUrl,
36
+ getAccessToken: async () => {
37
+ if (process.env.HQ_DB_MOCK_CONTROL_PLANE === "1") {
38
+ return "mock-token";
39
+ }
40
+ throw new Error("auth required for remote status");
41
+ },
42
+ });
43
+ const remote = await client.status(companyUid);
44
+ if (!remote.remote) {
45
+ console.log("remote: (none)");
46
+ }
47
+ else {
48
+ console.log(`remote: ${remote.remote.status}`);
49
+ console.log(`remoteEngine: ${remote.remote.engineId}`);
50
+ console.log(`remoteRegion: ${remote.remote.region}`);
51
+ console.log(`remoteResourceArn: ${remote.remote.resourceArn}`);
52
+ }
53
+ }
54
+ catch (e) {
55
+ const msg = e instanceof Error ? e.message : "remote status failed";
56
+ console.log(`remote: error (${msg})`);
57
+ }
58
+ }
59
+ if (!status.healthy) {
60
+ process.exitCode = 1;
61
+ }
62
+ }
63
+ catch (error) {
64
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
65
+ process.exitCode = 1;
66
+ }
67
+ });
68
+ }
69
+ //# sourceMappingURL=db-status.js.map
70
+ //# debugId=8b820218-b1e5-57a2-8acd-d8e749a0d83f
@@ -0,0 +1,9 @@
1
+ /**
2
+ * hq db — vault database commands (local + remote tiers).
3
+ */
4
+ import { Command } from "commander";
5
+ /**
6
+ * Register the `hq db` command group and its subcommands.
7
+ */
8
+ export declare function registerDbCommand(program: Command): void;
9
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * hq db — vault database commands (local + remote tiers).
3
+ */
4
+
5
+ !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]="c5202241-a9a4-52bf-81b7-e95dc255d08f")}catch(e){}}();
6
+ import { registerDbMigrateCommand } from "./db-migrate.js";
7
+ import { registerDbProvisionCommand } from "./db-provision.js";
8
+ import { registerDbSqlCommand } from "./db-sql.js";
9
+ import { registerDbStatusCommand } from "./db-status.js";
10
+ /**
11
+ * Register the `hq db` command group and its subcommands.
12
+ */
13
+ export function registerDbCommand(program) {
14
+ const db = program
15
+ .command("db")
16
+ .description("Vault databases — local SQLite per company and HQ-managed remote Postgres-class DBs");
17
+ registerDbStatusCommand(db);
18
+ registerDbSqlCommand(db);
19
+ registerDbMigrateCommand(db);
20
+ registerDbProvisionCommand(db);
21
+ }
22
+ //# sourceMappingURL=db.js.map
23
+ //# debugId=c5202241-a9a4-52bf-81b7-e95dc255d08f
@@ -0,0 +1,78 @@
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
+ import { Command } from "commander";
29
+ interface AdminConnection {
30
+ id: string;
31
+ provider: string;
32
+ status: string;
33
+ writePolicy?: string;
34
+ installation?: {
35
+ displayName?: string;
36
+ domain?: string;
37
+ status?: string;
38
+ } | null;
39
+ }
40
+ interface GatewayMessage {
41
+ result?: unknown;
42
+ error?: {
43
+ code?: number;
44
+ message?: string;
45
+ };
46
+ }
47
+ export declare class IntegrationsCliError extends Error {
48
+ constructor(message: string);
49
+ }
50
+ /** "factory:linear" → "linear"; mirrors hq-pro's factoryToolPrefix. */
51
+ export declare function toolPrefixForProvider(provider: string): string;
52
+ export declare function fetchConnections(token: string, companyUid: string): Promise<AdminConnection[]>;
53
+ /**
54
+ * Resolve one connection by `--connection acct_…` or `--provider linear`
55
+ * (matches `factory:<slug>` and bare provider ids, case-insensitive). Errors
56
+ * list what IS connected so the fix is one command away.
57
+ */
58
+ export declare function selectConnection(connections: AdminConnection[], opts: {
59
+ connection?: string;
60
+ provider?: string;
61
+ }): AdminConnection;
62
+ export declare function callGateway(token: string, params: Record<string, unknown>): Promise<GatewayMessage>;
63
+ /**
64
+ * Gateway results arrive MCP-style: `{ content: [{ type: "text", text }] }`
65
+ * where `text` is the provider's JSON. Unwrap to the inner payload; fall back
66
+ * to the raw result when the shape differs.
67
+ */
68
+ export declare function unwrapGatewayResult(result: unknown): unknown;
69
+ interface QueuedOutcome {
70
+ queuedForApproval: true;
71
+ queueId: string;
72
+ connectionId: string;
73
+ expiresAt?: string;
74
+ }
75
+ export declare function queuedOutcome(payload: unknown): QueuedOutcome | null;
76
+ export declare function registerIntegrationsCommand(program: Command): void;
77
+ export {};
78
+ //# sourceMappingURL=integrations.d.ts.map
@@ -0,0 +1,309 @@
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
+ !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]="a9b3ea5a-d7b5-5786-a961-af26b18eebaa")}catch(e){}}();
30
+ import { randomUUID } from "node:crypto";
31
+ import chalk from "chalk";
32
+ import { ensureCognitoIdToken } from "../utils/cognito-session.js";
33
+ import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
34
+ export class IntegrationsCliError extends Error {
35
+ constructor(message) {
36
+ super(message);
37
+ this.name = "IntegrationsCliError";
38
+ }
39
+ }
40
+ /** "factory:linear" → "linear"; mirrors hq-pro's factoryToolPrefix. */
41
+ export function toolPrefixForProvider(provider) {
42
+ return provider
43
+ .replace(/^factory:/, "")
44
+ .trim()
45
+ .toLowerCase()
46
+ .replace(/[^a-z0-9]+/g, ".");
47
+ }
48
+ export async function fetchConnections(token, companyUid) {
49
+ const res = await vaultApiFetch({
50
+ token,
51
+ path: "/v1/integrations/admin",
52
+ query: { companyUid },
53
+ });
54
+ if (!res.ok) {
55
+ const body = (await res.json().catch(() => ({})));
56
+ throw new IntegrationsCliError(body.error ?? `Failed to list integrations (HTTP ${res.status})`);
57
+ }
58
+ const data = (await res.json());
59
+ return data.connections ?? [];
60
+ }
61
+ /**
62
+ * Resolve one connection by `--connection acct_…` or `--provider linear`
63
+ * (matches `factory:<slug>` and bare provider ids, case-insensitive). Errors
64
+ * list what IS connected so the fix is one command away.
65
+ */
66
+ export function selectConnection(connections, opts) {
67
+ const active = connections.filter((c) => c.status !== "revoked");
68
+ if (opts.connection) {
69
+ const match = connections.find((c) => c.id === opts.connection);
70
+ if (!match) {
71
+ throw new IntegrationsCliError(`No connection '${opts.connection}'. Run \`hq integrations list\` to see connected apps.`);
72
+ }
73
+ return match;
74
+ }
75
+ if (opts.provider) {
76
+ const want = opts.provider.trim().toLowerCase();
77
+ const match = active.find((c) => {
78
+ const bare = c.provider.replace(/^factory:/, "").toLowerCase();
79
+ return bare === want || c.provider.toLowerCase() === want;
80
+ });
81
+ if (!match) {
82
+ const available = active
83
+ .map((c) => c.provider.replace(/^factory:/, ""))
84
+ .join(", ");
85
+ throw new IntegrationsCliError(`No connected app matches '${opts.provider}'.` +
86
+ (available ? ` Connected: ${available}.` : " Nothing is connected yet — connect apps on the console Integrations page."));
87
+ }
88
+ return match;
89
+ }
90
+ if (active.length === 1)
91
+ return active[0];
92
+ if (active.length === 0) {
93
+ throw new IntegrationsCliError("No connected apps yet. Connect one on the console Integrations page, then retry.");
94
+ }
95
+ throw new IntegrationsCliError(`Multiple apps are connected — pick one with --provider:\n` +
96
+ active.map((c) => ` --provider ${c.provider.replace(/^factory:/, "")}`).join("\n"));
97
+ }
98
+ export async function callGateway(token, params) {
99
+ const res = await vaultApiFetch({
100
+ token,
101
+ path: "/v1/integrations/mcp",
102
+ method: "POST",
103
+ body: {
104
+ jsonrpc: "2.0",
105
+ id: `hq-cli-${randomUUID()}`,
106
+ method: "tools/call",
107
+ params,
108
+ },
109
+ });
110
+ const message = (await res.json().catch(() => null));
111
+ if (!res.ok || !message) {
112
+ throw new IntegrationsCliError(`Integration gateway request failed (HTTP ${res.status}).`);
113
+ }
114
+ if (message.error) {
115
+ throw new IntegrationsCliError(message.error.message ?? "Integration gateway returned an error.");
116
+ }
117
+ return message;
118
+ }
119
+ /**
120
+ * Gateway results arrive MCP-style: `{ content: [{ type: "text", text }] }`
121
+ * where `text` is the provider's JSON. Unwrap to the inner payload; fall back
122
+ * to the raw result when the shape differs.
123
+ */
124
+ export function unwrapGatewayResult(result) {
125
+ if (result && typeof result === "object" && Array.isArray(result.content)) {
126
+ const content = result.content;
127
+ const text = content.find((c) => c.type === "text")?.text;
128
+ if (typeof text === "string") {
129
+ try {
130
+ return JSON.parse(text);
131
+ }
132
+ catch {
133
+ return text;
134
+ }
135
+ }
136
+ }
137
+ return result;
138
+ }
139
+ export function queuedOutcome(payload) {
140
+ if (payload &&
141
+ typeof payload === "object" &&
142
+ payload.queuedForApproval === true &&
143
+ typeof payload.queueId === "string") {
144
+ return payload;
145
+ }
146
+ return null;
147
+ }
148
+ function printJson(value) {
149
+ console.log(JSON.stringify(value, null, 2));
150
+ }
151
+ export function registerIntegrationsCommand(program) {
152
+ const integrations = program
153
+ .command("integrations")
154
+ .description("Use company-connected apps (Linear, Notion, …) through HQ's governed integration gateway");
155
+ integrations
156
+ .command("list")
157
+ .description("List the company's connected apps")
158
+ .option("--company <slug>", "Company slug (defaults to your single active company)")
159
+ .option("--json", "Machine-readable output")
160
+ .action(async (opts) => {
161
+ const token = await ensureCognitoIdToken();
162
+ const companyUid = await getCompanyUid(token, opts.company);
163
+ const connections = await fetchConnections(token, companyUid);
164
+ if (opts.json) {
165
+ printJson(connections);
166
+ return;
167
+ }
168
+ if (connections.length === 0) {
169
+ console.log("No apps connected yet. Connect one on the console Integrations page.");
170
+ return;
171
+ }
172
+ for (const c of connections) {
173
+ const name = c.installation?.displayName ?? c.provider.replace(/^factory:/, "");
174
+ const flags = [
175
+ c.status,
176
+ c.writePolicy ? `writes: ${c.writePolicy}` : null,
177
+ c.installation?.status === "needs_credentials" ? "needs sign-in" : null,
178
+ ]
179
+ .filter(Boolean)
180
+ .join(" · ");
181
+ console.log(`${chalk.bold(name)} (${c.provider.replace(/^factory:/, "")}) ${chalk.dim(flags)}`);
182
+ console.log(chalk.dim(` connection: ${c.id}`));
183
+ }
184
+ });
185
+ integrations
186
+ .command("tools")
187
+ .description("List what a connected app can do")
188
+ .option("--provider <slug>", "Connected app (e.g. linear)")
189
+ .option("--connection <id>", "Connection id (acct_…)")
190
+ .option("--company <slug>", "Company slug")
191
+ .option("--json", "Machine-readable output")
192
+ .action(async (opts) => {
193
+ const token = await ensureCognitoIdToken();
194
+ const companyUid = await getCompanyUid(token, opts.company);
195
+ const connection = selectConnection(await fetchConnections(token, companyUid), opts);
196
+ const prefix = toolPrefixForProvider(connection.provider);
197
+ const message = await callGateway(token, {
198
+ companyUid,
199
+ name: `${prefix}.mcp.tools.list`,
200
+ arguments: { companyUid, connectionId: connection.id },
201
+ });
202
+ const payload = unwrapGatewayResult(message.result);
203
+ if (opts.json) {
204
+ printJson(payload);
205
+ return;
206
+ }
207
+ const tools = payload?.tools ?? [];
208
+ if (tools.length === 0) {
209
+ console.log("The app reported no tools.");
210
+ return;
211
+ }
212
+ for (const tool of tools) {
213
+ const label = tool.title && tool.title !== tool.name ? ` ${chalk.dim(tool.title)}` : "";
214
+ console.log(`${chalk.bold(tool.name)}${label}`);
215
+ }
216
+ console.log(chalk.dim(`\n${tools.length} tools. Call one with: hq integrations call <tool> --provider ${connection.provider.replace(/^factory:/, "")} --args '<json>'`));
217
+ });
218
+ integrations
219
+ .command("call <tool>")
220
+ .description("Call one of a connected app's tools (approval-gated when it changes things)")
221
+ .option("--provider <slug>", "Connected app (e.g. linear)")
222
+ .option("--connection <id>", "Connection id (acct_…)")
223
+ .option("--company <slug>", "Company slug")
224
+ .option("--args <json>", "Tool arguments as JSON", "{}")
225
+ .option("--idempotency-key <key>", "Stable key so retries execute at most once (default: generated)")
226
+ .option("--json", "Machine-readable output")
227
+ .action(async (tool, opts) => {
228
+ let parsedArgs;
229
+ try {
230
+ const raw = JSON.parse(opts.args);
231
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
232
+ throw new Error("not an object");
233
+ }
234
+ parsedArgs = raw;
235
+ }
236
+ catch {
237
+ throw new IntegrationsCliError(`--args must be a JSON object, e.g. --args '{"assignee":"me"}'`);
238
+ }
239
+ const token = await ensureCognitoIdToken();
240
+ const companyUid = await getCompanyUid(token, opts.company);
241
+ const connection = selectConnection(await fetchConnections(token, companyUid), opts);
242
+ const prefix = toolPrefixForProvider(connection.provider);
243
+ const idempotencyKey = opts.idempotencyKey ?? `hq-cli-${randomUUID()}`;
244
+ const message = await callGateway(token, {
245
+ companyUid,
246
+ name: `${prefix}.mcp.tools.call`,
247
+ arguments: {
248
+ companyUid,
249
+ connectionId: connection.id,
250
+ toolName: tool,
251
+ arguments: parsedArgs,
252
+ idempotencyKey,
253
+ },
254
+ });
255
+ const payload = unwrapGatewayResult(message.result);
256
+ const queued = queuedOutcome(payload);
257
+ if (queued) {
258
+ if (opts.json) {
259
+ printJson(payload);
260
+ return;
261
+ }
262
+ console.log(chalk.yellow("Queued for approval — this call can change the app, so a company owner decides first."));
263
+ console.log(`Approve with:\n hq integrations approve ${queued.queueId} --provider ${connection.provider.replace(/^factory:/, "")}`);
264
+ if (queued.expiresAt) {
265
+ console.log(chalk.dim(`Expires ${queued.expiresAt}`));
266
+ }
267
+ return;
268
+ }
269
+ printJson(payload);
270
+ });
271
+ for (const decision of ["approve", "reject"]) {
272
+ integrations
273
+ .command(`${decision} <queueId>`)
274
+ .description(decision === "approve"
275
+ ? "Approve a queued call — it executes exactly once (owner only)"
276
+ : "Reject a queued call (owner only)")
277
+ .option("--provider <slug>", "Connected app (e.g. linear)")
278
+ .option("--connection <id>", "Connection id (acct_…)")
279
+ .option("--company <slug>", "Company slug")
280
+ .option("--json", "Machine-readable output")
281
+ .action(async (queueId, opts) => {
282
+ const token = await ensureCognitoIdToken();
283
+ const companyUid = await getCompanyUid(token, opts.company);
284
+ const connection = selectConnection(await fetchConnections(token, companyUid), opts);
285
+ const res = await vaultApiFetch({
286
+ token,
287
+ path: `/v1/integrations/confirm/${encodeURIComponent(queueId)}/${decision}`,
288
+ method: "POST",
289
+ body: { companyUid, connectionId: connection.id },
290
+ });
291
+ const body = (await res.json().catch(() => ({})));
292
+ if (!res.ok) {
293
+ throw new IntegrationsCliError(body.error ?? `${decision} failed (HTTP ${res.status})`);
294
+ }
295
+ if (opts.json) {
296
+ printJson(body);
297
+ return;
298
+ }
299
+ console.log(decision === "approve"
300
+ ? chalk.green(`Approved — the call ran (status: ${body.status ?? "resumed"}).`)
301
+ : chalk.yellow(`Rejected (status: ${body.status ?? "rejected"}).`));
302
+ if (decision === "approve" && body.result !== undefined) {
303
+ printJson(unwrapGatewayResult(body.result));
304
+ }
305
+ });
306
+ }
307
+ }
308
+ //# sourceMappingURL=integrations.js.map
309
+ //# debugId=a9b3ea5a-d7b5-5786-a961-af26b18eebaa
@@ -1,5 +1,5 @@
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]="c844265d-9140-55ac-8fc7-c0b2a049a0af")}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]="e3c1d5a2-b7f4-5f0f-8603-000df9bcca33")}catch(e){}}();
3
3
  import chalk from "chalk";
4
4
  import { ensureCognitoToken } from "../utils/cognito-session.js";
5
5
  import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
@@ -427,8 +427,8 @@ export function registerMembersCommand(program) {
427
427
  console.log();
428
428
  console.log(chalk.bold("To complete the invite, do ONE of:"));
429
429
  console.log(` 1. Manually notify ${inviteeEmail ?? "the invitee"}: ask them to sign into HQ`);
430
- console.log(` at https://hq.getindigo.ai with that email address.`);
431
- console.log(` 2. Or use the hq-console UI at https://hq.getindigo.ai to issue the`);
430
+ console.log(` at https://hq.computer with that email address.`);
431
+ console.log(` 2. Or use the hq-console UI at https://hq.computer to issue the`);
432
432
  console.log(` invite instead — the UI path triggers an automated email via Resend.`);
433
433
  if (result.emailSkipped !== true && result.emailSent === undefined) {
434
434
  console.log();
@@ -564,4 +564,4 @@ export function registerMembersCommand(program) {
564
564
  });
565
565
  }
566
566
  //# sourceMappingURL=members.js.map
567
- //# debugId=c844265d-9140-55ac-8fc7-c0b2a049a0af
567
+ //# debugId=e3c1d5a2-b7f4-5f0f-8603-000df9bcca33
@@ -0,0 +1,60 @@
1
+ /**
2
+ * `hq outposts` — manage your personal HQ Outposts (EC2 boxes) from the
3
+ * terminal instead of the web console. Targets the hq-pro `/outpost/*`
4
+ * control plane on `DEFAULT_VAULT_API_URL` via the shared `vaultApiFetch`
5
+ * helper — the same routes the console's outpost panel calls.
6
+ *
7
+ * Outposts are PERSONAL / caller-scoped: hq-pro keys every `/outpost/*` route
8
+ * on the caller's Cognito sub, so there is no `--company`. `--id <outpostId>`
9
+ * selects a specific box (passed as the `outpostId` query param); when omitted
10
+ * hq-pro targets the caller's primary slot.
11
+ *
12
+ * Subcommands:
13
+ * hq outposts list — every Outpost you own (row summaries)
14
+ * hq outposts status [--id] — live detail for one box
15
+ * hq outposts codex-enable [--id] — enable / retry Codex on the box
16
+ * hq outposts login [--id] — request a fresh login URL
17
+ * hq outposts destroy [--id] --yes — tear the box down (destructive; flag-guarded)
18
+ *
19
+ * NOTE: hq-pro exposes no rename or settings-mutation route for Outposts (the
20
+ * web console can't rename them either), so this CLI wraps only the lifecycle
21
+ * and status routes that exist. Renaming an Outpost is not a backend capability.
22
+ */
23
+ import { Command } from "commander";
24
+ /** A non-2xx from the `/outpost/*` control plane. Carries status + `step`. */
25
+ export declare class OutpostHttpError extends Error {
26
+ status: number;
27
+ step?: string;
28
+ constructor(status: number, message: string, step?: string);
29
+ }
30
+ /** Row summary from `GET /outpost/list`. */
31
+ export interface OutpostSummary {
32
+ outpostId: string;
33
+ state: string;
34
+ instanceName: string;
35
+ region: string;
36
+ agentRuntime: string;
37
+ platform: string;
38
+ createdAt: string;
39
+ [key: string]: unknown;
40
+ }
41
+ /**
42
+ * Authenticated JSON round-trip against the outpost control plane. Throws
43
+ * `OutpostHttpError` on any non-2xx (never swallows — hq-never-swallow-errors),
44
+ * decoding hq-pro's `{ error | message, step }` envelope for the reason. The
45
+ * `step` is preserved so callers can recognise the `destroy` route's
46
+ * `teardown-incomplete` 409 (which means "retry", not "failed").
47
+ */
48
+ export declare function outpostRequest<T>(opts: {
49
+ token: string;
50
+ path: string;
51
+ method?: string;
52
+ query?: Record<string, string>;
53
+ }): Promise<T>;
54
+ export declare function listOutposts(token: string): Promise<OutpostSummary[]>;
55
+ export declare function getOutpostStatus(token: string, outpostId?: string): Promise<Record<string, unknown>>;
56
+ export declare function enableCodex(token: string, outpostId?: string): Promise<Record<string, unknown>>;
57
+ export declare function regenerateLoginUrl(token: string, outpostId?: string): Promise<Record<string, unknown>>;
58
+ export declare function destroyOutpost(token: string, outpostId?: string): Promise<Record<string, unknown>>;
59
+ export declare function registerOutpostsCommand(program: Command): void;
60
+ //# sourceMappingURL=outposts.d.ts.map