gitdog 0.2.1 → 0.2.2

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/README.md CHANGED
@@ -17,7 +17,7 @@
17
17
  </p>
18
18
 
19
19
  <p align="center">
20
- <img src="https://raw.githubusercontent.com/hyodotdev/gitdog-cli/main/.github/assets/terminal.svg?v=0.2.1" alt="Gitdog terminal: connect a project, configure automation and review pending actions" width="800">
20
+ <img src="https://raw.githubusercontent.com/hyodotdev/gitdog-cli/main/.github/assets/terminal.svg?v=0.2.2" alt="Gitdog terminal: connect a project, configure automation and review pending actions" width="800">
21
21
  </p>
22
22
 
23
23
  Connect repositories, configure issue, discussion and PR automation, review
@@ -64,7 +64,7 @@ Use `npx gitdog@latest --help` to explore commands. Add `--json` for scripts.
64
64
 
65
65
  ```sh
66
66
  npx gitdog@latest keys create --name ci
67
- npx --yes gitdog@0.2.1 --key-file /path/to/key.json projects list --json
67
+ npx --yes gitdog@0.2.2 --key-file /path/to/key.json projects list --json
68
68
  npx gitdog@latest keys revoke KEY_ID --yes
69
69
  ```
70
70
 
package/dist/auth.d.ts CHANGED
@@ -1,13 +1,17 @@
1
1
  import { SessionStore } from "./storage.js";
2
2
  import { type Ui } from "./ui.js";
3
3
  export declare const CALLBACK_PATH = "/gitdog/callback";
4
+ export declare const LOGIN_RESULT_PATH = "/cli/login";
5
+ type LoginOutcome = "success" | "failed";
4
6
  export declare function openBrowser(url: string, signal?: AbortSignal): Promise<boolean>;
5
- export declare function createLoginListener(timeoutMs?: number): Promise<{
7
+ export declare function createLoginListener(timeoutMs?: number, siteUrl?: string): Promise<{
6
8
  redirectTo: string;
7
9
  signal: AbortSignal;
8
10
  code: Promise<string>;
11
+ complete: (outcome: LoginOutcome) => Promise<void>;
9
12
  close: () => Promise<void>;
10
13
  }>;
11
14
  export declare function validateAuthRedirect(value: string, apiUrl: string): void;
12
15
  export declare function login(apiUrl: string, siteUrl: string, ui: Ui, noBrowser?: boolean): Promise<void>;
13
16
  export declare function logout(ui: Ui, localOnly?: boolean, store?: SessionStore): Promise<void>;
17
+ export {};
package/dist/auth.js CHANGED
@@ -3,10 +3,23 @@ import { promisify } from "node:util";
3
3
  import { createServer } from "node:http";
4
4
  import { randomBytes } from "node:crypto";
5
5
  import { httpClient, invoke } from "./client.js";
6
- import { SessionStore, validTokens, tokenExpiresSoon } from "./storage.js";
6
+ import { DEFAULT_API_URL, DEFAULT_AUTH_SITE_URL, DEFAULT_SITE_URL, SessionStore, validTokens, tokenExpiresSoon, } from "./storage.js";
7
7
  import { asRecord } from "./ui.js";
8
8
  const runFile = promisify(execFile);
9
9
  export const CALLBACK_PATH = "/gitdog/callback";
10
+ export const LOGIN_RESULT_PATH = "/cli/login";
11
+ function sendToLoginResult(response, siteUrl, outcome) {
12
+ if (response.destroyed)
13
+ return Promise.resolve();
14
+ const location = new URL(LOGIN_RESULT_PATH, siteUrl);
15
+ location.searchParams.set("status", outcome);
16
+ return new Promise((resolve) => {
17
+ response.once("close", resolve);
18
+ response
19
+ .writeHead(302, { Location: location.href, Connection: "close" })
20
+ .end();
21
+ });
22
+ }
10
23
  export async function openBrowser(url, signal) {
11
24
  signal?.throwIfAborted();
12
25
  const parsed = new URL(url);
@@ -33,7 +46,7 @@ export async function openBrowser(url, signal) {
33
46
  return false;
34
47
  }
35
48
  }
36
- export async function createLoginListener(timeoutMs = 300_000) {
49
+ export async function createLoginListener(timeoutMs = 300_000, siteUrl = DEFAULT_SITE_URL) {
37
50
  const cancellation = new AbortController();
38
51
  const state = randomBytes(32).toString("hex");
39
52
  let resolveCode;
@@ -45,6 +58,14 @@ export async function createLoginListener(timeoutMs = 300_000) {
45
58
  void code.catch(() => { });
46
59
  let expectedHost = "";
47
60
  let settled = false;
61
+ // Held open so the browser reports the real result, not just a received code.
62
+ let pendingResponse;
63
+ const complete = async (outcome) => {
64
+ const response = pendingResponse;
65
+ pendingResponse = undefined;
66
+ if (response)
67
+ await sendToLoginResult(response, siteUrl, outcome);
68
+ };
48
69
  const finish = (error, value) => {
49
70
  if (settled)
50
71
  return;
@@ -88,28 +109,19 @@ export async function createLoginListener(timeoutMs = 300_000) {
88
109
  response.writeHead(410).end("This sign-in has already completed.");
89
110
  return;
90
111
  }
91
- if (url.searchParams.has("error")) {
92
- response
93
- .writeHead(400)
94
- .end("Gitdog sign-in was not completed. Return to your terminal.");
112
+ if (url.searchParams.has("error") || !url.searchParams.has("code")) {
113
+ void sendToLoginResult(response, siteUrl, "failed");
95
114
  finish(new Error("GitHub sign-in was declined or failed."));
96
115
  return;
97
116
  }
98
117
  const value = url.searchParams.get("code");
99
- if (!url.searchParams.has("code")) {
100
- response
101
- .writeHead(400)
102
- .end("Gitdog sign-in was not completed. Return to your terminal.");
103
- finish(new Error("GitHub sign-in was declined or failed."));
104
- return;
105
- }
106
118
  if (url.searchParams.getAll("code").length !== 1 ||
107
119
  !value ||
108
120
  value.length > 4096) {
109
121
  response.writeHead(400).end("Missing or invalid sign-in code.");
110
122
  return;
111
123
  }
112
- response.end("Gitdog received your sign-in. Return to your terminal to finish. You can close this tab.");
124
+ pendingResponse = response;
113
125
  finish(undefined, value);
114
126
  });
115
127
  const timer = setTimeout(() => finish(new Error("Sign-in timed out. Run npx gitdog@latest login to try again.")), timeoutMs);
@@ -142,8 +154,10 @@ export async function createLoginListener(timeoutMs = 300_000) {
142
154
  redirectTo,
143
155
  signal: cancellation.signal,
144
156
  code,
157
+ complete,
145
158
  close: async () => {
146
159
  finish(new Error("Sign-in cancelled."));
160
+ await complete("failed");
147
161
  process.off("SIGINT", onInterrupt);
148
162
  server.closeAllConnections();
149
163
  await new Promise((resolve) => server.close(() => resolve()));
@@ -154,7 +168,10 @@ export function validateAuthRedirect(value, apiUrl) {
154
168
  const url = new URL(value);
155
169
  const api = new URL(apiUrl);
156
170
  const siteOrigin = api.origin.replace(/\.convex\.cloud$/, ".convex.site");
157
- if (![api.origin, siteOrigin].includes(url.origin) ||
171
+ const allowedOrigins = [api.origin, siteOrigin];
172
+ if (api.origin === new URL(DEFAULT_API_URL).origin)
173
+ allowedOrigins.push(new URL(DEFAULT_AUTH_SITE_URL).origin);
174
+ if (!allowedOrigins.includes(url.origin) ||
158
175
  url.pathname !== "/api/auth/signin/github" ||
159
176
  url.username ||
160
177
  url.password ||
@@ -166,7 +183,7 @@ export async function login(apiUrl, siteUrl, ui, noBrowser = false) {
166
183
  if (ui.json)
167
184
  throw new Error("Browser login needs human interaction. Omit --json; use GITDOG_TOKEN for automation.");
168
185
  ui.banner();
169
- const listener = await createLoginListener();
186
+ const listener = await createLoginListener(undefined, siteUrl);
170
187
  const client = httpClient(apiUrl, 30_000, listener.signal);
171
188
  try {
172
189
  ui.info("1 / 2 Sign in with your GitHub account");
@@ -207,6 +224,7 @@ export async function login(apiUrl, siteUrl, ui, noBrowser = false) {
207
224
  const store = new SessionStore();
208
225
  await store.locked(() => store.save({ ...tokens, apiUrl, siteUrl }, listener.signal), undefined, listener.signal);
209
226
  listener.signal.throwIfAborted();
227
+ await listener.complete("success");
210
228
  ui.output(`✓ Signed in as ${String(user.name ?? "GitHub user")}.`);
211
229
  if (user.requiresCurrentLegalAttestation)
212
230
  ui.info(`Complete account consent at ${siteUrl}/legal/consent before running AI workflows.`);
package/dist/storage.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export declare const DEFAULT_API_URL = "https://calm-hippopotamus-56.convex.cloud";
2
2
  export declare const DEFAULT_SITE_URL = "https://gitdog.dev";
3
+ export declare const DEFAULT_AUTH_SITE_URL = "https://auth.gitdog.dev";
3
4
  export type Tokens = {
4
5
  token: string;
5
6
  refreshToken: string;
package/dist/storage.js CHANGED
@@ -5,6 +5,7 @@ import { randomUUID } from "node:crypto";
5
5
  import { asRecord } from "./ui.js";
6
6
  export const DEFAULT_API_URL = "https://calm-hippopotamus-56.convex.cloud";
7
7
  export const DEFAULT_SITE_URL = "https://gitdog.dev";
8
+ export const DEFAULT_AUTH_SITE_URL = "https://auth.gitdog.dev";
8
9
  export function configDirectory() {
9
10
  return (process.env.GITDOG_CONFIG_DIR ||
10
11
  join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "gitdog"));
package/dist/ui.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.2.1";
1
+ export declare const VERSION = "0.2.2";
2
2
  export declare const DOG: string;
3
3
  export declare function redactSecrets(value: string): string;
4
4
  export declare function clean(value: string): string;
package/dist/ui.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createInterface } from "node:readline/promises";
2
2
  import { stripVTControlCharacters } from "node:util";
3
- export const VERSION = "0.2.1";
3
+ export const VERSION = "0.2.2";
4
4
  export const DOG = String.raw `
5
5
  ▗██▄▄ ▄▄██▖
6
6
  ██▀▜██▙▄ ▙▄ ▄▟██▛▀██
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitdog",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Manage Gitdog projects, GitHub automation and API keys from your terminal",
5
5
  "type": "module",
6
6
  "bin": {