@indigoai-us/hq-cli 5.72.0 → 5.73.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.
@@ -73,6 +73,13 @@ export declare function listOutposts(token: string): Promise<OutpostSummary[]>;
73
73
  export declare function getOutpostStatus(token: string, outpostId?: string): Promise<Record<string, unknown>>;
74
74
  export declare function enableCodex(token: string, outpostId?: string): Promise<Record<string, unknown>>;
75
75
  export declare function regenerateLoginUrl(token: string, outpostId?: string): Promise<Record<string, unknown>>;
76
+ /**
77
+ * Hand the box the one-time Claude sign-in code the operator got from the login
78
+ * URL. hq-pro stores it as the row's pending code; the box polls for it, feeds
79
+ * it to `claude`, and flips itself `awaiting-claude-login → ready`. This is the
80
+ * terminal-native equivalent of pasting the code into the web console.
81
+ */
82
+ export declare function submitLoginCode(token: string, code: string, outpostId?: string): Promise<Record<string, unknown>>;
76
83
  export declare function destroyOutpost(token: string, outpostId?: string): Promise<Record<string, unknown>>;
77
84
  /** Result of `POST /outpost/exec` — a terminal SSM invocation on the box. */
78
85
  export interface OutpostExecResult {
@@ -116,6 +116,21 @@ export async function regenerateLoginUrl(token, outpostId) {
116
116
  query: outpostId ? { outpostId } : undefined,
117
117
  });
118
118
  }
119
+ /**
120
+ * Hand the box the one-time Claude sign-in code the operator got from the login
121
+ * URL. hq-pro stores it as the row's pending code; the box polls for it, feeds
122
+ * it to `claude`, and flips itself `awaiting-claude-login → ready`. This is the
123
+ * terminal-native equivalent of pasting the code into the web console.
124
+ */
125
+ export async function submitLoginCode(token, code, outpostId) {
126
+ return outpostRequest({
127
+ token,
128
+ path: "/outpost/login-code",
129
+ method: "POST",
130
+ body: { code },
131
+ query: outpostId ? { outpostId } : undefined,
132
+ });
133
+ }
119
134
  export async function destroyOutpost(token, outpostId) {
120
135
  return outpostRequest({
121
136
  token,
@@ -1011,8 +1026,40 @@ export function registerOutpostsCommand(program, selfDeployOverrides = {}) {
1011
1026
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1012
1027
  return;
1013
1028
  }
1014
- console.log(chalk.green("Login-URL regeneration requested. The box mints a fresh URL shortly" +
1015
- "check `hq outposts status` to pick it up."));
1029
+ console.log(chalk.green("Login-URL regeneration requested. The box mints a fresh URL shortly."));
1030
+ console.log(chalk.dim("Next: `hq outposts status" +
1031
+ (opts.id ? ` --id ${opts.id}` : "") +
1032
+ "` to read the login URL, open it and sign in, then paste the code back with " +
1033
+ "`hq outposts login-code <code>" +
1034
+ (opts.id ? ` --id ${opts.id}` : "") +
1035
+ "`."));
1036
+ }
1037
+ catch (err) {
1038
+ fail(err);
1039
+ }
1040
+ });
1041
+ outposts
1042
+ .command("login-code <code>")
1043
+ .description("Submit the Claude sign-in code for an Outpost that is awaiting login")
1044
+ .option("--id <outpostId>", "Outpost id (defaults to your primary box)")
1045
+ .option("--json", "Emit raw JSON")
1046
+ .action(async function (code, opts) {
1047
+ try {
1048
+ const trimmed = code.trim();
1049
+ if (!trimmed) {
1050
+ console.error(chalk.red("Provide the sign-in code: hq outposts login-code <code>"));
1051
+ process.exit(1);
1052
+ }
1053
+ const token = await ensureCognitoToken();
1054
+ const result = await submitLoginCode(token, trimmed, opts.id);
1055
+ if (opts.json) {
1056
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1057
+ return;
1058
+ }
1059
+ console.log(chalk.green("Code submitted — the box will finish signing in shortly."));
1060
+ console.log(chalk.dim("Track it: `hq outposts status" +
1061
+ (opts.id ? ` --id ${opts.id}` : "") +
1062
+ "` (it flips to `ready` once Claude auth completes)."));
1016
1063
  }
1017
1064
  catch (err) {
1018
1065
  fail(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.72.0",
3
+ "version": "5.73.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -171,6 +171,38 @@ describe("hq outposts codex-enable / login", () => {
171
171
  expect(String(url)).toContain("/outpost/regenerate-login-url");
172
172
  expect(init?.method).toBe("POST");
173
173
  });
174
+
175
+ it("login-code POSTs /outpost/login-code with the code in the body", async () => {
176
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, { ok: true, userId: "u1" }));
177
+ await run(["outposts", "login-code", "ABC-123"]);
178
+ const [url, init] = fetchSpy.mock.calls[0];
179
+ expect(String(url)).toContain("/outpost/login-code");
180
+ expect(init?.method).toBe("POST");
181
+ expect(JSON.parse(String(init?.body))).toEqual({ code: "ABC-123" });
182
+ });
183
+
184
+ it("login-code targets a specific box via --id (outpostId query)", async () => {
185
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, { ok: true, userId: "u1" }));
186
+ await run(["outposts", "login-code", "CODE", "--id", "3"]);
187
+ const [url] = fetchSpy.mock.calls[0];
188
+ expect(String(url)).toContain("outpostId=3");
189
+ });
190
+
191
+ it("login-code trims whitespace and refuses an empty code without calling the API", async () => {
192
+ await expect(run(["outposts", "login-code", " "])).rejects.toThrow(
193
+ "process.exit(1)",
194
+ );
195
+ expect(fetchSpy).not.toHaveBeenCalled();
196
+ });
197
+
198
+ it("login-code surfaces an awaiting-login not-found error", async () => {
199
+ fetchSpy.mockResolvedValueOnce(
200
+ jsonResponse(404, { error: true, step: "not-found", message: "no outpost" }),
201
+ );
202
+ await expect(run(["outposts", "login-code", "CODE"])).rejects.toThrow(
203
+ "process.exit(1)",
204
+ );
205
+ });
174
206
  });
175
207
 
176
208
  describe("hq outposts destroy", () => {
@@ -182,6 +182,26 @@ export async function regenerateLoginUrl(
182
182
  });
183
183
  }
184
184
 
185
+ /**
186
+ * Hand the box the one-time Claude sign-in code the operator got from the login
187
+ * URL. hq-pro stores it as the row's pending code; the box polls for it, feeds
188
+ * it to `claude`, and flips itself `awaiting-claude-login → ready`. This is the
189
+ * terminal-native equivalent of pasting the code into the web console.
190
+ */
191
+ export async function submitLoginCode(
192
+ token: string,
193
+ code: string,
194
+ outpostId?: string,
195
+ ): Promise<Record<string, unknown>> {
196
+ return outpostRequest({
197
+ token,
198
+ path: "/outpost/login-code",
199
+ method: "POST",
200
+ body: { code },
201
+ query: outpostId ? { outpostId } : undefined,
202
+ });
203
+ }
204
+
185
205
  export async function destroyOutpost(
186
206
  token: string,
187
207
  outpostId?: string,
@@ -1450,8 +1470,58 @@ export function registerOutpostsCommand(
1450
1470
  }
1451
1471
  console.log(
1452
1472
  chalk.green(
1453
- "Login-URL regeneration requested. The box mints a fresh URL shortly" +
1454
- "check `hq outposts status` to pick it up.",
1473
+ "Login-URL regeneration requested. The box mints a fresh URL shortly.",
1474
+ ),
1475
+ );
1476
+ console.log(
1477
+ chalk.dim(
1478
+ "Next: `hq outposts status" +
1479
+ (opts.id ? ` --id ${opts.id}` : "") +
1480
+ "` to read the login URL, open it and sign in, then paste the code back with " +
1481
+ "`hq outposts login-code <code>" +
1482
+ (opts.id ? ` --id ${opts.id}` : "") +
1483
+ "`.",
1484
+ ),
1485
+ );
1486
+ } catch (err) {
1487
+ fail(err);
1488
+ }
1489
+ });
1490
+
1491
+ outposts
1492
+ .command("login-code <code>")
1493
+ .description(
1494
+ "Submit the Claude sign-in code for an Outpost that is awaiting login",
1495
+ )
1496
+ .option("--id <outpostId>", "Outpost id (defaults to your primary box)")
1497
+ .option("--json", "Emit raw JSON")
1498
+ .action(async function (
1499
+ this: Command,
1500
+ code: string,
1501
+ opts: { id?: string; json?: boolean },
1502
+ ) {
1503
+ try {
1504
+ const trimmed = code.trim();
1505
+ if (!trimmed) {
1506
+ console.error(chalk.red("Provide the sign-in code: hq outposts login-code <code>"));
1507
+ process.exit(1);
1508
+ }
1509
+ const token = await ensureCognitoToken();
1510
+ const result = await submitLoginCode(token, trimmed, opts.id);
1511
+ if (opts.json) {
1512
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1513
+ return;
1514
+ }
1515
+ console.log(
1516
+ chalk.green(
1517
+ "Code submitted — the box will finish signing in shortly.",
1518
+ ),
1519
+ );
1520
+ console.log(
1521
+ chalk.dim(
1522
+ "Track it: `hq outposts status" +
1523
+ (opts.id ? ` --id ${opts.id}` : "") +
1524
+ "` (it flips to `ready` once Claude auth completes).",
1455
1525
  ),
1456
1526
  );
1457
1527
  } catch (err) {