gitdog 0.0.0-stage → 0.1.1

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/dist/ui.js ADDED
@@ -0,0 +1,181 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stripVTControlCharacters } from "node:util";
3
+ export const VERSION = "0.1.1";
4
+ export const DOG = String.raw `
5
+ ▗██▄▄ ▄▄██▖
6
+ ██▀▜██▙▄ ▙▄ ▄▟██▛▀██
7
+ ▐█▌ ▀███▄▐██▄ ▄███▀ ▜█▌
8
+ ▐█▌ ███████▙ ▝▀▀▀ ▐█▌
9
+ ██▌ ▄█████████▌ ▐█▌
10
+ ▐█▌ ▄████████████ ▐█▌
11
+ ▐█▙▟████████████▌ ▜█▘
12
+ ▀▝▀▘ ▀▀███▛
13
+
14
+ ▄█▙▖ ▄█▙▖
15
+ ▜██▌ ▄▄ ▜██▘
16
+ ▄███▛▜███▄
17
+ ▗███▌ ▐███▖
18
+ █████▄ ▄█████
19
+ ▗▄ ▝▜██▛▀ ▀▜██▛▘ ▄▖
20
+ ▐██▄▖ ▗▄██▌
21
+ ▟█████▙▄▖ ▗▄▄█████▙
22
+ ▐█▛███████████▄▄▄▄▄▄███████████▜█▌
23
+ ▀ ▝██████████████████████████▘ ▀
24
+ ▝▜██████████████████████▛▘
25
+ ▝▜██████████████████▀▘
26
+ ▀▀▜█▜██████▛█▛▀▘
27
+ ▀▀▀▀
28
+ `;
29
+ export function redactSecrets(value) {
30
+ return value
31
+ .replace(/\beyJ[\w-]+\.[\w-]+\.[\w-]+\b/g, "[redacted token]")
32
+ .replace(/\bgdk_[a-f0-9]{64}\b/g, "[redacted API key]");
33
+ }
34
+ // C1 controls and bidirectional overrides can drive or spoof terminals.
35
+ const UNSAFE_UNICODE = /[\u0080-\u009f\u202a-\u202e\u2066-\u2069]/g;
36
+ export function clean(value) {
37
+ return redactSecrets(stripVTControlCharacters(value)
38
+ .replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "")
39
+ .replace(UNSAFE_UNICODE, ""));
40
+ }
41
+ /** Escape characters JSON.stringify leaves raw; the JSON value is unchanged. */
42
+ export function safeJson(value) {
43
+ return redactSecrets(JSON.stringify(value ?? null, null, 2)).replace(UNSAFE_UNICODE, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
44
+ }
45
+ export class Ui {
46
+ json;
47
+ constructor(json = false) {
48
+ this.json = json;
49
+ }
50
+ color(text, code = 36) {
51
+ return process.stdout.isTTY &&
52
+ !process.env.NO_COLOR &&
53
+ process.env.TERM !== "dumb"
54
+ ? `\x1b[${code}m${text}\x1b[0m`
55
+ : text;
56
+ }
57
+ banner() {
58
+ if (this.json || process.env.GITDOG_NO_BANNER === "1")
59
+ return;
60
+ process.stdout.write(this.color(DOG, 1) + this.color(` G I T D O G / ${VERSION}\n`, 1));
61
+ process.stdout.write(" Your repositories. A very good assistant.\n\n");
62
+ }
63
+ info(message) {
64
+ if (!this.json)
65
+ process.stderr.write(`${clean(message)}\n`);
66
+ }
67
+ output(value) {
68
+ if (this.json) {
69
+ process.stdout.write(`${safeJson(value)}\n`);
70
+ return;
71
+ }
72
+ if (typeof value === "string") {
73
+ process.stdout.write(`${clean(value)}\n`);
74
+ return;
75
+ }
76
+ const record = asRecord(value);
77
+ const rows = Array.isArray(value)
78
+ ? value
79
+ : Array.isArray(record?.items)
80
+ ? record.items
81
+ : undefined;
82
+ if (rows) {
83
+ if (rows.length === 0) {
84
+ process.stdout.write("No results.\n");
85
+ }
86
+ else {
87
+ const preferred = [
88
+ "path",
89
+ "kind",
90
+ "githubRepo",
91
+ "fullName",
92
+ "githubNumber",
93
+ "githubTitle",
94
+ "name",
95
+ "prefix",
96
+ "scopes",
97
+ "expiresAt",
98
+ "revokedAt",
99
+ "type",
100
+ "githubUsername",
101
+ "title",
102
+ "message",
103
+ "status",
104
+ "indexStatus",
105
+ "role",
106
+ "userRole",
107
+ "_id",
108
+ ];
109
+ const keys = preferred.filter((key) => rows.some((row) => asRecord(row)?.[key] !== undefined));
110
+ if (keys.length > 0)
111
+ this.table(rows.map((row) => asRecord(row) ?? {}), keys);
112
+ else
113
+ process.stdout.write(`${clean(JSON.stringify(value, null, 2))}\n`);
114
+ }
115
+ if (typeof record?.total === "number")
116
+ this.info(`Showing ${rows.length} of ${record.total}. Use --offset and --limit for more.`);
117
+ if (record?.nextCursor !== undefined && record.nextCursor !== null) {
118
+ this.info(`Next page: --cursor ${JSON.stringify(record.nextCursor)}`);
119
+ }
120
+ if (record?.hasMore === true && record.nextCursor == null)
121
+ this.info("More results are available.");
122
+ return;
123
+ }
124
+ process.stdout.write(`${clean(JSON.stringify(value, null, 2) ?? "null")}\n`);
125
+ }
126
+ table(rows, keys) {
127
+ const cell = (value) => clean(String(value ?? "—"))
128
+ .replace(/\s+/g, " ")
129
+ .slice(0, 90);
130
+ const widths = keys.map((key) => Math.max(key.length, ...rows.map((row) => cell(row[key]).length)));
131
+ const line = (row) => keys
132
+ .map((key, i) => cell(row[key]).padEnd(widths[i]))
133
+ .join(" ")
134
+ .trimEnd();
135
+ process.stdout.write(this.color(line(Object.fromEntries(keys.map((key) => [key, key]))), 1) +
136
+ "\n");
137
+ rows.forEach((row) => process.stdout.write(`${line(row)}\n`));
138
+ }
139
+ async ask(question, fallback) {
140
+ if (this.json || !process.stdin.isTTY || !process.stdout.isTTY) {
141
+ throw new Error("This command needs input. Supply explicit arguments for non-interactive use.");
142
+ }
143
+ const input = createInterface({
144
+ input: process.stdin,
145
+ output: process.stdout,
146
+ });
147
+ try {
148
+ const answer = (await input.question(`${clean(question)}${fallback ? ` (${clean(fallback)})` : ""}: `)).trim();
149
+ if (!answer && fallback)
150
+ return fallback;
151
+ if (!answer)
152
+ throw new Error("A value is required.");
153
+ return answer;
154
+ }
155
+ finally {
156
+ input.close();
157
+ }
158
+ }
159
+ async confirm(message, yes = false) {
160
+ if (yes)
161
+ return;
162
+ const answer = await this.ask(`${message} [y/N]`, "n");
163
+ if (!/^y(es)?$/i.test(answer))
164
+ throw new Error("Cancelled.");
165
+ }
166
+ }
167
+ export function asRecord(value) {
168
+ return typeof value === "object" && value !== null && !Array.isArray(value)
169
+ ? value
170
+ : undefined;
171
+ }
172
+ export function integer(value, minimum = 1, maximum = Number.MAX_SAFE_INTEGER) {
173
+ const result = Number(value);
174
+ if (!/^\d+$/.test(value) ||
175
+ !Number.isSafeInteger(result) ||
176
+ result < minimum ||
177
+ result > maximum) {
178
+ throw new Error(`Expected an integer between ${minimum} and ${maximum}.`);
179
+ }
180
+ return result;
181
+ }
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "gitdog",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Manage Gitdog projects, GitHub automation and API keys from your terminal",
5
+ "type": "module",
6
+ "bin": {
7
+ "gitdog": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "engines": {
14
+ "node": ">=22.14.0"
15
+ },
16
+ "packageManager": "bun@1.3.13",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/hyodotdev/gitdog-cli.git"
20
+ },
21
+ "homepage": "https://gitdog.dev",
22
+ "bugs": {
23
+ "url": "https://github.com/hyodotdev/gitdog-cli/issues"
24
+ },
25
+ "license": "UNLICENSED",
26
+ "keywords": [
27
+ "gitdog",
28
+ "github",
29
+ "cli",
30
+ "automation",
31
+ "ai"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public",
35
+ "registry": "https://registry.npmjs.org"
36
+ },
37
+ "scripts": {
38
+ "build": "tsc -p tsconfig.json",
39
+ "typecheck": "tsc --noEmit",
40
+ "lint": "prettier --check .",
41
+ "format": "prettier --write .",
42
+ "test": "bun run build && node --test test/*.test.mjs",
43
+ "test:dev": "bun run build && node scripts/test-dev.mjs",
44
+ "dev": "bun src/cli.ts",
45
+ "prepack": "bun run build"
46
+ },
47
+ "dependencies": {
48
+ "commander": "^15.0.0",
49
+ "convex": "^1.43.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^22.14.0",
53
+ "prettier": "^3.9.0",
54
+ "typescript": "~6.0.3"
55
+ }
56
+ }