@indigoai-us/hq-cli 5.101.5 → 5.101.7
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/dist/commands/mesh.d.ts +12 -0
- package/dist/commands/mesh.js +173 -0
- package/dist/commands/outposts.d.ts +18 -232
- package/dist/commands/outposts.js +27 -1290
- package/dist/lib/mesh/api.d.ts +88 -0
- package/dist/lib/mesh/api.js +319 -0
- package/dist/lib/mesh/cache.d.ts +10 -0
- package/dist/lib/mesh/cache.js +42 -0
- package/dist/main.js +4 -0
- package/package.json +9 -3
- package/dist/commands/outposts-heartbeat.d.ts +0 -96
- package/dist/commands/outposts-heartbeat.js +0 -188
- package/dist/outpost/session-heartbeat-publisher.d.ts +0 -76
- package/dist/outpost/session-heartbeat-publisher.js +0 -117
- package/dist/outpost/session-heartbeat.d.ts +0 -210
- package/dist/outpost/session-heartbeat.js +0 -657
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [5.101.7] — 2026-08-17
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **`hq outposts`** is now sourced from `@indigoai-us/hq-cloud` (≥ 6.15.5)
|
|
10
|
+
instead of being implemented in the CLI. The command surface and behavior are
|
|
11
|
+
unchanged; the control-plane client, box-side helpers, and command tree moved
|
|
12
|
+
into hq-cloud so they can also be consumed directly (including from a Next.js
|
|
13
|
+
app) via the new `@indigoai-us/hq-cloud/outposts` subpath exports. The CLI
|
|
14
|
+
keeps its own authenticated transport, so Sentry breadcrumbs, API-key routing,
|
|
15
|
+
and plan-gate handling are preserved.
|
|
16
|
+
|
|
17
|
+
## [5.101.6] — 2026-08-17
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **`hq mesh`** — native work-mesh verbs in the CLI (Cognito + hq-pro REST +
|
|
22
|
+
`~/.hq/work-mesh/cache`). No pack helper required. Verbs: `check`
|
|
23
|
+
(`status` / `projects`), `start`, `progress`, `blocked`, `done`, `note`,
|
|
24
|
+
`story`, `doctor` (warms directory + inbox + pair DMs). Does **not** start
|
|
25
|
+
MQTT listen and is not `hq doctor` (hook guardrails). `--apply` Board PUTs
|
|
26
|
+
from local prd.json are not in the CLI yet.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- `hq mesh doctor` now keys `directory` / `inbox` / `contacts` on the real
|
|
31
|
+
`prs_*` / `agt_*` principal. `custom:entityUid` is read from the Cognito ID
|
|
32
|
+
token first; if the JWT has only email/sub (typical human access tokens),
|
|
33
|
+
doctor falls back to `/v1/realtime/credentials` and keeps only the uid —
|
|
34
|
+
AWS IoT creds are discarded. Leftover `session.json` is removed once the
|
|
35
|
+
real principal is known.
|
|
36
|
+
|
|
3
37
|
## [5.101.5] — 2026-08-17
|
|
4
38
|
|
|
5
39
|
- No user-facing changes recorded.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hq mesh` — work-mesh verbs for agents and local sessions.
|
|
3
|
+
*
|
|
4
|
+
* Native hq-cli: Cognito + hq-pro REST + ~/.hq/work-mesh/cache. Does not
|
|
5
|
+
* require hq-pack-work-mesh on disk and does not start MQTT listen.
|
|
6
|
+
* Distinct from `hq doctor` (hook guardrails).
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import { type MeshCompany, type MeshThread } from "../lib/mesh/api.js";
|
|
10
|
+
export declare function formatCheckLines(threads: MeshThread[], company: MeshCompany, projectId?: string): string[];
|
|
11
|
+
export declare function registerMeshCommand(program: Command): void;
|
|
12
|
+
//# sourceMappingURL=mesh.d.ts.map
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hq mesh` — work-mesh verbs for agents and local sessions.
|
|
3
|
+
*
|
|
4
|
+
* Native hq-cli: Cognito + hq-pro REST + ~/.hq/work-mesh/cache. Does not
|
|
5
|
+
* require hq-pack-work-mesh on disk and does not start MQTT listen.
|
|
6
|
+
* Distinct from `hq doctor` (hook guardrails).
|
|
7
|
+
*/
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { loadCachedTokens } from "../utils/cognito-session.js";
|
|
10
|
+
import { STORY_STATUSES, appendThreadEvent, callerLabelFromToken, ensureProjectThread, eventPayload, listActiveThreads, patchStoryStatus, requireToken, resolveMeshCompany, resolveMeshPrincipalUid, warmMeshConversationCache, } from "../lib/mesh/api.js";
|
|
11
|
+
export function formatCheckLines(threads, company, projectId) {
|
|
12
|
+
if (threads.length === 0) {
|
|
13
|
+
return ["Work mesh: no active project threads found."];
|
|
14
|
+
}
|
|
15
|
+
const scope = projectId
|
|
16
|
+
? `${company.companySlug || company.companyUid}/${projectId}`
|
|
17
|
+
: company.companySlug || company.companyUid;
|
|
18
|
+
const lines = [`Work mesh: ${threads.length} active thread(s) for ${scope}`];
|
|
19
|
+
for (const thread of threads.slice(0, 8)) {
|
|
20
|
+
const owner = thread.ownerUid ? ` owner=${thread.ownerUid}` : "";
|
|
21
|
+
const summary = thread.progressSummary ||
|
|
22
|
+
thread.sourceSignalSummary ||
|
|
23
|
+
thread.blockedReason ||
|
|
24
|
+
"";
|
|
25
|
+
lines.push(`- ${thread.threadStatus ?? "unknown"} ${thread.threadId ?? "?"}${owner}${summary ? `: ${summary}` : ""}`);
|
|
26
|
+
}
|
|
27
|
+
return lines;
|
|
28
|
+
}
|
|
29
|
+
function fail(message) {
|
|
30
|
+
console.error(chalk.red(message));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
function requireProject(opts) {
|
|
34
|
+
const project = opts.project?.trim();
|
|
35
|
+
if (!project)
|
|
36
|
+
fail("`--project <slug>` is required.");
|
|
37
|
+
return project;
|
|
38
|
+
}
|
|
39
|
+
async function withCompany(opts) {
|
|
40
|
+
const token = await requireToken();
|
|
41
|
+
const company = await resolveMeshCompany(token, opts.company);
|
|
42
|
+
return { token, company };
|
|
43
|
+
}
|
|
44
|
+
async function runCheck(opts) {
|
|
45
|
+
const { token, company } = await withCompany(opts);
|
|
46
|
+
const threads = await listActiveThreads(token, company.companyUid, opts.project);
|
|
47
|
+
if (opts.json) {
|
|
48
|
+
console.log(JSON.stringify({ ok: true, action: "check", company, projectId: opts.project, threads }, null, 2));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
for (const line of formatCheckLines(threads, company, opts.project)) {
|
|
52
|
+
console.log(line);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function runEvent(verb, opts) {
|
|
56
|
+
const { token, company } = await withCompany(opts);
|
|
57
|
+
const projectId = requireProject(opts);
|
|
58
|
+
const ensured = await ensureProjectThread(token, company, projectId, {
|
|
59
|
+
threadId: opts.threadId,
|
|
60
|
+
summary: opts.summary,
|
|
61
|
+
});
|
|
62
|
+
const eventKind = verb === "start" ? "claim" : verb;
|
|
63
|
+
const event = await appendThreadEvent(token, company.companyUid, ensured.threadId, eventKind, eventPayload(eventKind, opts, callerLabelFromToken(token)));
|
|
64
|
+
if (opts.json) {
|
|
65
|
+
console.log(JSON.stringify({
|
|
66
|
+
ok: true,
|
|
67
|
+
action: verb,
|
|
68
|
+
eventKind,
|
|
69
|
+
company,
|
|
70
|
+
projectId,
|
|
71
|
+
threadId: ensured.threadId,
|
|
72
|
+
created: ensured.created,
|
|
73
|
+
...event,
|
|
74
|
+
}, null, 2));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
console.log(`Work mesh: ${verb} ${ensured.threadId}${event.eventId ? ` (${event.eventId})` : ""}`);
|
|
78
|
+
}
|
|
79
|
+
async function runStory(opts) {
|
|
80
|
+
const storyId = opts.story?.trim();
|
|
81
|
+
const status = opts.status?.trim();
|
|
82
|
+
if (!storyId || !status || !STORY_STATUSES.has(status)) {
|
|
83
|
+
fail("story requires --story <id> and --status queued|in_progress|review|done");
|
|
84
|
+
}
|
|
85
|
+
const { token, company } = await withCompany(opts);
|
|
86
|
+
const projectId = requireProject(opts);
|
|
87
|
+
const patched = await patchStoryStatus(token, company.companyUid, projectId, storyId, status);
|
|
88
|
+
if (opts.json) {
|
|
89
|
+
console.log(JSON.stringify({ ok: true, action: "story", company, ...patched, storyId }, null, 2));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
console.log(`Work mesh: story ${storyId} → ${patched.status} (v${patched.version ?? "?"})`);
|
|
93
|
+
}
|
|
94
|
+
async function runDoctor(opts) {
|
|
95
|
+
if (opts.apply) {
|
|
96
|
+
fail("`hq mesh doctor --apply` (paced Board PUTs from local prd.json) is not in the CLI yet.\n" +
|
|
97
|
+
"Omit --apply to warm the conversation cache (directory, inbox, pair DMs).");
|
|
98
|
+
}
|
|
99
|
+
const token = await requireToken();
|
|
100
|
+
const principalUid = await resolveMeshPrincipalUid(token, loadCachedTokens());
|
|
101
|
+
const warmed = await warmMeshConversationCache(token, principalUid);
|
|
102
|
+
if (opts.json) {
|
|
103
|
+
console.log(JSON.stringify({ ok: true, action: "doctor", ...warmed }, null, 2));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
console.log(`Work mesh cache warmed at ${warmed.cacheRoot}` +
|
|
107
|
+
` (directory=${warmed.directory} inbox=${warmed.inbox}` +
|
|
108
|
+
` contacts=${warmed.contacts} pair-threads=${warmed.threads})`);
|
|
109
|
+
}
|
|
110
|
+
function wrap(action) {
|
|
111
|
+
return async () => {
|
|
112
|
+
try {
|
|
113
|
+
await action();
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function collectRepeatable(value, previous = []) {
|
|
121
|
+
return previous.concat(value);
|
|
122
|
+
}
|
|
123
|
+
function addSharedFlags(cmd) {
|
|
124
|
+
return cmd
|
|
125
|
+
.option("--company <slug|uid>", "Company slug or cloud uid")
|
|
126
|
+
.option("--project <slug>", "HQ project slug / projectId")
|
|
127
|
+
.option("--thread-id <id>", "Explicit work thread id")
|
|
128
|
+
.option("--json", "Print machine-readable JSON");
|
|
129
|
+
}
|
|
130
|
+
export function registerMeshCommand(program) {
|
|
131
|
+
const mesh = program
|
|
132
|
+
.command("mesh")
|
|
133
|
+
.description("Work mesh — register project work, report progress, and warm ~/.hq/work-mesh/cache");
|
|
134
|
+
addSharedFlags(mesh
|
|
135
|
+
.command("check")
|
|
136
|
+
.alias("status")
|
|
137
|
+
.alias("projects")
|
|
138
|
+
.description("Show active work-mesh threads for a company/project")).action((opts) => wrap(() => runCheck(opts))());
|
|
139
|
+
addSharedFlags(mesh
|
|
140
|
+
.command("start")
|
|
141
|
+
.description("Ensure a project thread exists and claim/report start")
|
|
142
|
+
.option("--summary <text>", "What you are starting")).action((opts) => wrap(() => runEvent("start", opts))());
|
|
143
|
+
addSharedFlags(mesh
|
|
144
|
+
.command("progress")
|
|
145
|
+
.description("Append a progress event to the project thread")
|
|
146
|
+
.requiredOption("--summary <text>", "Progress summary")).action((opts) => wrap(() => runEvent("progress", opts))());
|
|
147
|
+
addSharedFlags(mesh
|
|
148
|
+
.command("blocked")
|
|
149
|
+
.description("Append a blocked event to the project thread")
|
|
150
|
+
.option("--reason <text>", "Why you are blocked")
|
|
151
|
+
.option("--ask <text>", "Repeatable ask", collectRepeatable)
|
|
152
|
+
.option("--summary <text>", "Optional summary")).action((opts) => wrap(() => runEvent("blocked", opts))());
|
|
153
|
+
addSharedFlags(mesh
|
|
154
|
+
.command("done")
|
|
155
|
+
.description("Mark the project thread done")
|
|
156
|
+
.option("--summary <text>", "What shipped")).action((opts) => wrap(() => runEvent("done", opts))());
|
|
157
|
+
addSharedFlags(mesh
|
|
158
|
+
.command("note")
|
|
159
|
+
.description("Append a note with no status change")
|
|
160
|
+
.option("--summary <text>", "Note text")).action((opts) => wrap(() => runEvent("note", opts))());
|
|
161
|
+
addSharedFlags(mesh
|
|
162
|
+
.command("story")
|
|
163
|
+
.description("PATCH one Board story status")
|
|
164
|
+
.requiredOption("--story <id>", "Story id, e.g. US-001")
|
|
165
|
+
.requiredOption("--status <status>", "queued|in_progress|review|done")).action((opts) => wrap(() => runStory(opts))());
|
|
166
|
+
mesh
|
|
167
|
+
.command("doctor")
|
|
168
|
+
.description("Warm ~/.hq/work-mesh/cache from hq-pro (directory, inbox, pair DMs). Not `hq doctor`.")
|
|
169
|
+
.option("--apply", "Reserved — Board PUTs from local prd are not in the CLI yet")
|
|
170
|
+
.option("--json", "Print machine-readable JSON")
|
|
171
|
+
.action((opts) => wrap(() => runDoctor(opts))());
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=mesh.js.map
|
|
@@ -1,240 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `hq outposts` —
|
|
3
|
-
*
|
|
4
|
-
* control plane on `DEFAULT_VAULT_API_URL` via the shared `vaultApiFetch`
|
|
5
|
-
* helper — the same routes the console's outpost panel calls.
|
|
2
|
+
* `hq outposts` — thin adapter over the Outpost command tree, which now lives in
|
|
3
|
+
* @indigoai-us/hq-cloud (`/outposts/cli`).
|
|
6
4
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* The wire contract, the command surface, and the box-side helpers all moved out
|
|
6
|
+
* of the CLI so they can be shared — notably so a Next.js app can drive the same
|
|
7
|
+
* `/outpost/*` control plane through `@indigoai-us/hq-cloud/outposts`. All the CLI
|
|
8
|
+
* keeps is the wiring: its own authenticated transport (`vaultApiFetch`, which
|
|
9
|
+
* carries Sentry breadcrumbs, the `hqk_` route-rewrite table, and plan-gate
|
|
10
|
+
* decoding), its token resolution, and its cached-session read.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
import { spawnSync } from "node:child_process";
|
|
25
|
-
import { type BillingErrorPayload } from "../utils/billing-gate.js";
|
|
26
|
-
/**
|
|
27
|
-
* hq-pro's per-person cap envelope on a `409` provision block. Unlike every
|
|
28
|
-
* other `/outpost/*` failure this body carries NO `message`/`error` field —
|
|
29
|
-
* only the cap facts — so it has to be decoded structurally or the reason
|
|
30
|
-
* degrades to a bare `res.statusText` ("Conflict").
|
|
31
|
-
*/
|
|
32
|
-
export interface OutpostCappedPayload {
|
|
33
|
-
limit: number;
|
|
34
|
-
outposts: OutpostSummary[];
|
|
35
|
-
}
|
|
36
|
-
/** Decode hq-pro's `{ capped, limit, outposts }` cap envelope, if present. */
|
|
37
|
-
export declare function parseCappedPayload(body: unknown): OutpostCappedPayload | undefined;
|
|
38
|
-
/** A non-2xx from the `/outpost/*` control plane. Carries status + `step`. */
|
|
39
|
-
export declare class OutpostHttpError extends Error {
|
|
40
|
-
status: number;
|
|
41
|
-
step?: string;
|
|
42
|
-
/** hq-pro's billing envelope on a `402 billing_required` provision block. */
|
|
43
|
-
billing?: BillingErrorPayload;
|
|
44
|
-
/** hq-pro's cap envelope on a `409` provision block. */
|
|
45
|
-
capped?: OutpostCappedPayload;
|
|
46
|
-
constructor(status: number, message: string, step?: string, billing?: BillingErrorPayload, capped?: OutpostCappedPayload);
|
|
47
|
-
}
|
|
48
|
-
/** Row summary from `GET /outpost/list`. */
|
|
49
|
-
export interface OutpostSummary {
|
|
50
|
-
outpostId: string;
|
|
51
|
-
state: string;
|
|
52
|
-
instanceName: string;
|
|
53
|
-
region: string;
|
|
54
|
-
agentRuntime: string;
|
|
55
|
-
platform: string;
|
|
56
|
-
createdAt: string;
|
|
57
|
-
[key: string]: unknown;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Authenticated JSON round-trip against the outpost control plane. Throws
|
|
61
|
-
* `OutpostHttpError` on any non-2xx (never swallows — hq-never-swallow-errors),
|
|
62
|
-
* decoding hq-pro's `{ error | message, step }` envelope for the reason. The
|
|
63
|
-
* `step` is preserved so callers can recognise the `destroy` route's
|
|
64
|
-
* `teardown-incomplete` 409 (which means "retry", not "failed").
|
|
65
|
-
*/
|
|
66
|
-
export declare function outpostRequest<T>(opts: {
|
|
67
|
-
token: string;
|
|
68
|
-
path: string;
|
|
69
|
-
method?: string;
|
|
70
|
-
body?: Record<string, unknown>;
|
|
71
|
-
query?: Record<string, string>;
|
|
72
|
-
}): Promise<T>;
|
|
73
|
-
/**
|
|
74
|
-
* Provision the caller's Outpost. Sends the cached Cognito refresh token so the
|
|
75
|
-
* box can authenticate AS the caller (the same body the console's
|
|
76
|
-
* `provisionMyOutpost` sends). No duplicate is ever created: a caller already at
|
|
77
|
-
* their per-person cap gets a `409` whose body lists their existing boxes —
|
|
78
|
-
* thrown here as an `OutpostHttpError` carrying `capped` (hq-pro checks the cap
|
|
79
|
-
* BEFORE activation billing, so a capped call is never charged). The refresh
|
|
80
|
-
* token is sent over HTTPS and NEVER printed.
|
|
81
|
-
*/
|
|
82
|
-
export declare function provisionOutpost(token: string, input: {
|
|
83
|
-
refreshToken: string;
|
|
84
|
-
clientIp?: string;
|
|
85
|
-
diskSizeGb?: number;
|
|
86
|
-
agentRuntime?: "claude" | "codex";
|
|
87
|
-
}): Promise<Record<string, unknown>>;
|
|
88
|
-
export declare function listOutposts(token: string): Promise<OutpostSummary[]>;
|
|
89
|
-
export declare function getOutpostStatus(token: string, outpostId?: string): Promise<Record<string, unknown>>;
|
|
90
|
-
export declare function enableCodex(token: string, outpostId?: string): Promise<Record<string, unknown>>;
|
|
91
|
-
export declare function regenerateLoginUrl(token: string, outpostId?: string): Promise<Record<string, unknown>>;
|
|
92
|
-
/**
|
|
93
|
-
* Hand the box the one-time Claude sign-in code the operator got from the login
|
|
94
|
-
* URL. hq-pro stores it as the row's pending code; the box polls for it, feeds
|
|
95
|
-
* it to `claude`, and flips itself `awaiting-claude-login → ready`. This is the
|
|
96
|
-
* terminal-native equivalent of pasting the code into the web console.
|
|
97
|
-
*/
|
|
98
|
-
export declare function submitLoginCode(token: string, code: string, outpostId?: string): Promise<Record<string, unknown>>;
|
|
99
|
-
export declare function destroyOutpost(token: string, outpostId?: string): Promise<Record<string, unknown>>;
|
|
100
|
-
/** Result of `POST /outpost/exec` — a terminal SSM invocation on the box. */
|
|
101
|
-
export interface OutpostExecResult {
|
|
102
|
-
ok: true;
|
|
103
|
-
outpostId: string;
|
|
104
|
-
instanceId: string;
|
|
105
|
-
commandId: string;
|
|
106
|
-
/** SSM invocation status (Success | Failed | Cancelled). */
|
|
107
|
-
status: string;
|
|
108
|
-
/** Remote process exit code, or null when SSM reported none. */
|
|
109
|
-
exitCode: number | null;
|
|
110
|
-
stdout: string;
|
|
111
|
-
stderr: string;
|
|
112
|
-
/** True when SSM clipped stdout/stderr at its inline output limit. */
|
|
113
|
-
truncated: boolean;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Run a one-shot shell command on the caller's Outpost via `POST /outpost/exec`
|
|
117
|
-
* (server-brokered SSM — no SSH). Throws `OutpostHttpError` on a non-2xx, whose
|
|
118
|
-
* `step` distinguishes not-ready / platform-unsupported / timeout / ssm.
|
|
119
|
-
*/
|
|
120
|
-
export declare function execOutpost(token: string, command: string, outpostId?: string): Promise<OutpostExecResult>;
|
|
121
|
-
/** Presigned input-upload details from `mode: "stage"`. */
|
|
122
|
-
export interface OutpostExecStage {
|
|
123
|
-
ok: true;
|
|
124
|
-
userId: string;
|
|
125
|
-
outpostId: string;
|
|
126
|
-
key: string;
|
|
127
|
-
putUrl: string;
|
|
128
|
-
getUrl: string;
|
|
129
|
-
expiresInSeconds: number;
|
|
130
|
-
}
|
|
131
|
-
/** Asynchronous SSM command details from `mode: "submit"`. */
|
|
132
|
-
export interface OutpostExecSubmission {
|
|
133
|
-
ok: true;
|
|
134
|
-
userId: string;
|
|
135
|
-
outpostId: string;
|
|
136
|
-
instanceId: string;
|
|
137
|
-
commandId: string;
|
|
138
|
-
outputPrefix: string;
|
|
139
|
-
/** Shell budget applied server-side (AWS-RunShellScript executionTimeout). */
|
|
140
|
-
executionTimeoutSeconds?: number;
|
|
141
|
-
}
|
|
142
|
-
/** Poll response from `mode: "result"`; streams arrive only when terminal. */
|
|
143
|
-
export interface OutpostExecAsyncResult {
|
|
144
|
-
ok: true;
|
|
145
|
-
userId: string;
|
|
146
|
-
outpostId: string;
|
|
147
|
-
status: string;
|
|
148
|
-
done: boolean;
|
|
149
|
-
exitCode?: number | null;
|
|
150
|
-
stdout?: string;
|
|
151
|
-
stderr?: string;
|
|
152
|
-
truncated?: boolean;
|
|
153
|
-
}
|
|
154
|
-
export declare function stageExecInput(token: string, outpostId?: string): Promise<OutpostExecStage>;
|
|
155
|
-
export declare function submitExec(token: string, command: string, outpostId?: string, timeoutSeconds?: number): Promise<OutpostExecSubmission>;
|
|
156
|
-
export declare function fetchExecResult(token: string, commandId: string, outpostId?: string): Promise<OutpostExecAsyncResult>;
|
|
157
|
-
/** Preserve a single command string; safely join argv when Commander split it. */
|
|
158
|
-
export declare function joinCommandParts(commandParts: string[]): string;
|
|
159
|
-
/**
|
|
160
|
-
* Prefix that best-effort `cd`s into the box's HQ checkout before running the
|
|
161
|
-
* caller's command. `exec` runs over two transports with two different default
|
|
162
|
-
* working directories — SSM runs as root with no `$HOME` (cwd `/usr/bin`) and
|
|
163
|
-
* SSH lands in the login user's home — so without this, `hq outposts exec -- pwd`
|
|
164
|
-
* printed an unhelpful, transport-dependent directory. Initialize a real root
|
|
165
|
-
* home for the SSM case before resolving the HQ folder: tools run by the caller
|
|
166
|
-
* (notably `gh`) otherwise treat the HQ checkout as their home and can create
|
|
167
|
-
* root-owned machine state inside it. The trailing `|| true` keeps the command
|
|
168
|
-
* running from the default directory when no HQ checkout is present, so exec
|
|
169
|
-
* never fails merely because the box has no HQ folder.
|
|
170
|
-
*/
|
|
171
|
-
export declare const REMOTE_HQ_DIR_PREFIX = "export HOME=\"${HOME:-/root}\"; cd \"$HOME/hq\" 2>/dev/null || cd ~ec2-user/hq 2>/dev/null || true";
|
|
172
|
-
/** Wrap `command` so it runs from the box's HQ folder (see REMOTE_HQ_DIR_PREFIX). */
|
|
173
|
-
export declare function withRemoteHqDir(command: string): string;
|
|
174
|
-
/** SSH connection details vended by `POST /outpost/ssh-access`. */
|
|
175
|
-
export interface OutpostSshAccess {
|
|
176
|
-
outpostId: string;
|
|
177
|
-
platform: "ec2" | "lightsail";
|
|
178
|
-
host: string;
|
|
179
|
-
port: number;
|
|
180
|
-
username: string;
|
|
181
|
-
/** PEM private key for the box. SENSITIVE — never printed or logged. */
|
|
182
|
-
privateKey: string;
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Fetch SSH connection info + key for the caller's Outpost and open the caller's
|
|
186
|
-
* IP on the box's SSH port. Used to reach a Lightsail box (no SSM). Throws
|
|
187
|
-
* `OutpostHttpError` on a non-2xx.
|
|
12
|
+
* `vaultApiFetch` satisfies hq-cloud's `OutpostTransport` structurally — the
|
|
13
|
+
* option bags match field-for-field — so it is passed straight through with no
|
|
14
|
+
* adapter shim.
|
|
188
15
|
*/
|
|
189
|
-
|
|
16
|
+
import type { Command } from "commander";
|
|
17
|
+
import type { ReplicaSyncDependencies } from "@indigoai-us/hq-cloud/outposts/node";
|
|
190
18
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*/
|
|
197
|
-
export declare function execViaSsh(access: OutpostSshAccess, command: string): {
|
|
198
|
-
stdout: string;
|
|
199
|
-
stderr: string;
|
|
200
|
-
exitCode: number | null;
|
|
201
|
-
error?: string;
|
|
202
|
-
};
|
|
203
|
-
/** The small local-environment surface used by `outposts self-deploy`. */
|
|
204
|
-
export interface SelfDeployDependencies {
|
|
205
|
-
spawnSync: (command: string, args: string[], options?: Parameters<typeof spawnSync>[2]) => ReturnType<typeof spawnSync>;
|
|
206
|
-
readTextFile: (file: string) => string;
|
|
207
|
-
loadCachedTokens: () => {
|
|
208
|
-
refreshToken?: string;
|
|
209
|
-
idToken?: string;
|
|
210
|
-
} | undefined;
|
|
211
|
-
getUid: () => number | undefined;
|
|
212
|
-
isStdinTty: () => boolean;
|
|
213
|
-
confirm: () => Promise<boolean>;
|
|
214
|
-
defaultHqRoot: () => string;
|
|
215
|
-
invokingUser: () => string;
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* The extra local surface `replica-sync` needs on top of the self-deploy set:
|
|
219
|
-
* two filesystem probes for the repos clone. Kept as an extension so the
|
|
220
|
-
* `self-deploy` dependency surface stays minimal.
|
|
221
|
-
*/
|
|
222
|
-
export interface ReplicaSyncDependencies extends SelfDeployDependencies {
|
|
223
|
-
pathExists: (p: string) => boolean;
|
|
224
|
-
mkdirp: (p: string) => void;
|
|
225
|
-
}
|
|
226
|
-
/** One repo entry resolved from `personal/data/repos.yaml`. */
|
|
227
|
-
export interface ReplicaRepo {
|
|
228
|
-
url: string;
|
|
229
|
-
visibility: "public" | "private";
|
|
230
|
-
name: string;
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Parse `personal/data/repos.yaml` into a clone list. Pure + exported for
|
|
234
|
-
* tests. Mirrors setup.sh's `clone_repos`: `visibility` defaults to public,
|
|
235
|
-
* `name` defaults to the URL basename. Malformed entries are skipped, never
|
|
236
|
-
* thrown.
|
|
19
|
+
* Register the `hq outposts` command group.
|
|
20
|
+
*
|
|
21
|
+
* `selfDeployOverrides` lets a test replace the box-side host-environment probes
|
|
22
|
+
* (spawnSync, /etc/os-release reads, uid checks) without a real EC2 host; it is
|
|
23
|
+
* unused in production.
|
|
237
24
|
*/
|
|
238
|
-
export declare function parseReposManifest(yamlText: string): ReplicaRepo[];
|
|
239
25
|
export declare function registerOutpostsCommand(program: Command, selfDeployOverrides?: Partial<ReplicaSyncDependencies>): void;
|
|
240
26
|
//# sourceMappingURL=outposts.d.ts.map
|