-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathcli.ts
More file actions
251 lines (229 loc) · 7.45 KB
/
Copy pathcli.ts
File metadata and controls
251 lines (229 loc) · 7.45 KB
1
2
3
4
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#!/usr/bin/env node
import {
endTelemetrySpan,
flushTelemetry,
isTelemetryEnabled,
startTelemetrySpan,
withTelemetrySpan,
} from "@githits/core-internal";
import { colorizeBrand, shouldUseColors } from "@githits/mcp/internal";
import { Command } from "commander";
import { version } from "../package.json";
import { handleCliError } from "./cli/errors.js";
import {
enforceCachedRequiredUpdateForInvocation,
runWithUpdateCheckFlush,
startRequiredUpdateRefreshTaskForInvocation,
startUpdateCheckTaskForInvocation,
} from "./cli/update-check.js";
import {
registerAuthStatusCommand,
registerCodeCommandGroup,
registerDocsCommandGroup,
registerDoctorCommand,
registerExampleCommand,
registerFeedbackCommand,
registerInitCommand,
registerLanguagesCommand,
registerLoginCommand,
registerLogoutCommand,
registerMcpCommand,
registerPkgCommandGroup,
registerUnifiedSearchCommands,
} from "./commands/index.js";
import { loginFlow, stderrLoginOutput } from "./commands/login.js";
import {
clearAutoLoginAuthSessionMetadata,
createContainer,
loadAutoLoginAuthSessionMetadata,
} from "./container.js";
import { FileSystemServiceImpl } from "./services/filesystem-service.js";
import { createLazyCliFetch } from "./services/proxy-fetch.js";
import { NpmRegistryUpdateCheckService } from "./services/update-check-service.js";
import { createRootCliPreAction } from "./shared/root-cli-pre-action.js";
const program = new Command();
const argv = process.argv.slice(2);
// Bridge the --no-color flag to the NO_COLOR convention that every
// shouldUseColors() call across the CLI reads.
if (argv.includes("--no-color")) {
process.env.NO_COLOR = "1";
}
const useColors = shouldUseColors();
const commandSpans = new WeakMap<
Command,
ReturnType<typeof startTelemetrySpan>
>();
const createUpdateCheckService = () =>
new NpmRegistryUpdateCheckService({
currentVersion: version,
fileSystemService: new FileSystemServiceImpl(),
fetcher: createLazyCliFetch(),
});
await enforceCachedRequiredUpdateForInvocation({
args: argv,
env: process.env,
createService: createUpdateCheckService,
stderr: process.stderr,
exit: process.exit as (code: number) => never,
});
const updateCheckTask = startUpdateCheckTaskForInvocation({
args: argv,
env: process.env,
stderrIsTTY: process.stderr.isTTY === true,
stdinIsTTY: process.stdin.isTTY === true,
stdoutIsTTY: process.stdout.isTTY === true,
createService: createUpdateCheckService,
});
const requiredUpdateRefreshTask = startRequiredUpdateRefreshTaskForInvocation({
args: argv,
env: process.env,
createService: createUpdateCheckService,
});
if (isTelemetryEnabled()) {
process.once("exit", (exitCode) => {
flushTelemetry(exitCode);
});
}
const rootCliPreAction = createRootCliPreAction({
createContainer,
loadAuthSessionMetadata: loadAutoLoginAuthSessionMetadata,
clearAuthSessionMetadata: clearAutoLoginAuthSessionMetadata,
loginFlow: (options, deps) => loginFlow(options, deps, stderrLoginOutput),
});
program
.name("githits")
.description("Grounded open-source context for AI coding agents")
.version(version)
.option("--no-color", "Disable colored output")
.configureHelp({
styleTitle: (title: string) =>
colorizeBrand(title, "primary", useColors, { bold: true }),
})
.hook("preAction", async (thisCommand, actionCommand) => {
const command = actionCommand ?? thisCommand;
commandSpans.set(
command,
startTelemetrySpan(getTelemetryCommandName(command)),
);
await rootCliPreAction(thisCommand, actionCommand);
})
.hook("postAction", (_thisCommand, actionCommand) => {
endTelemetrySpan(commandSpans.get(actionCommand));
})
.addHelpText(
"after",
`
${colorizeBrand("Getting started:", "primary", useColors, { bold: true })}
githits init Connect GitHits to your coding agents
githits login Sign in to your GitHits account
githits mcp Show MCP setup instructions
githits example "query" Find real-world implementations
Learn more at https://githits.com
Docs: https://docs.githits.com
Support: support@githits.com`,
);
// Setup command
registerInitCommand(program);
// Auth commands
registerLoginCommand(program);
registerLogoutCommand(program);
// MCP server command
registerMcpCommand(program);
// CLI commands
registerExampleCommand(program);
registerLanguagesCommand(program);
registerFeedbackCommand(program);
registerDoctorCommand(program);
const registrationArgv = stripRootRegistrationOptions(argv);
if (shouldEagerLoadSearchCommands(registrationArgv)) {
await withTelemetrySpan("cli.register.search", () =>
registerUnifiedSearchCommands(program),
);
}
if (shouldEagerLoadGatedCommandGroup(registrationArgv, "code")) {
await withTelemetrySpan("cli.register.code-group", () =>
registerCodeCommandGroup(program),
);
}
if (shouldEagerLoadGatedCommandGroup(registrationArgv, "pkg")) {
await withTelemetrySpan("cli.register.pkg-group", () =>
registerPkgCommandGroup(program),
);
}
if (shouldEagerLoadGatedCommandGroup(registrationArgv, "docs")) {
await withTelemetrySpan("cli.register.docs-group", () =>
registerDocsCommandGroup(program),
);
}
// Auth status as subcommand of `auth`
const authCommand = program
.command("auth")
.summary("Manage authentication")
.description("Manage authentication with GitHits.");
registerAuthStatusCommand(authCommand);
try {
await runWithUpdateCheckFlush(
() => withTelemetrySpan("cli.parse", () => program.parseAsync()),
updateCheckTask,
{ stderr: process.stderr, requiredUpdateRefreshTask },
);
} catch (error) {
handleCliError(error, {
stderr: process.stderr,
exit: process.exit as (code: number) => never,
});
}
/**
* Commander supports root options before subcommands, e.g.
* `githits --no-color pkg info`. Registration happens before Commander
* parses argv, so the lightweight command sniff must ignore root-only
* flags or it will misclassify `--no-color` as the requested command.
*/
function stripRootRegistrationOptions(args: string[]): string[] {
return args.filter((arg) => arg !== "--no-color");
}
/**
* Argv-sniff optimisation for command groups. Returns `true`
* when the user's invocation might need the group registered — i.e.
* they typed the group name or asked for help. Here we only decide
* whether to build the command group eagerly so registration can run.
*/
function shouldEagerLoadGatedCommandGroup(
args: string[],
groupName: string,
): boolean {
const [firstArg] = args;
return (
args.length === 0 ||
firstArg === groupName ||
(firstArg === "help" && (!args[1] || args[1] === groupName)) ||
firstArg === "--help" ||
firstArg === "-h"
);
}
function shouldEagerLoadSearchCommands(args: string[]): boolean {
const [firstArg] = args;
return (
args.length === 0 ||
firstArg === "search" ||
firstArg === "search-status" ||
firstArg === "--help" ||
firstArg === "-h" ||
(firstArg === "help" && (!args[1] || isSearchHelpTarget(args[1])))
);
}
function isSearchHelpTarget(value: string | undefined): boolean {
return value === "search" || value === "search-status";
}
function getTelemetryCommandName(command: Command): string {
const names: string[] = [];
let current: Command | null = command;
while (current) {
const name = current.name();
if (name && name !== "githits") {
names.unshift(name);
}
current = current.parent ?? null;
}
return `command.${names.join(".")}`;
}