bet-cli 0.1.4 → 0.3.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.
@@ -1,15 +1,22 @@
1
- import chalk from 'chalk';
2
- import { Command } from 'commander';
3
- import { readConfig } from '../lib/config.js';
4
- import path from 'node:path';
5
- import { listProjects } from '../lib/projects.js';
6
- import type { Project, RootConfig } from '../lib/types.js';
7
- import { emitSelection } from '../utils/output.js';
8
- import { promptSelect } from '../ui/prompt.js';
9
- import { SelectRow } from '../ui/select.js';
1
+ import chalk from "chalk";
2
+ import { Command } from "commander";
3
+ import { readConfig } from "../lib/config.js";
4
+ import path from "node:path";
5
+ import { listProjects } from "../lib/projects.js";
6
+ import type { Project, RootConfig } from "../lib/types.js";
7
+ import { emitSelection } from "../utils/output.js";
8
+ import { promptSelect } from "../ui/prompt.js";
9
+ import { SelectRow } from "../ui/select.js";
10
10
 
11
11
  const MAX_ROWS = 18;
12
- const GROUP_COLORS = ['#22d3ee', '#34d399', '#facc15', '#c084fc', '#60a5fa', '#f87171'];
12
+ const GROUP_COLORS = [
13
+ "#22d3ee",
14
+ "#34d399",
15
+ "#facc15",
16
+ "#c084fc",
17
+ "#60a5fa",
18
+ "#f87171",
19
+ ];
13
20
 
14
21
  function groupColor(index: number): string {
15
22
  return GROUP_COLORS[index % GROUP_COLORS.length];
@@ -28,7 +35,10 @@ function orderedGroupsByConfigRoots(
28
35
  projects: Project[],
29
36
  roots: RootConfig[],
30
37
  ): { rootName: string; items: Project[] }[] {
31
- const groupsByRoot = new Map<string, { rootName: string; items: Project[] }>();
38
+ const groupsByRoot = new Map<
39
+ string,
40
+ { rootName: string; items: Project[] }
41
+ >();
32
42
  for (const project of projects) {
33
43
  const key = project.root;
34
44
  if (!groupsByRoot.has(key)) {
@@ -60,58 +70,76 @@ function orderedGroupsByConfigRoots(
60
70
 
61
71
  export function registerList(program: Command): void {
62
72
  program
63
- .command('list')
64
- .description('List projects')
65
- .option('--plain', 'Print a non-interactive list')
66
- .option('--json', 'Print JSON output')
67
- .option('--print', 'Print selected path only')
68
- .action(async (options: { plain?: boolean; json?: boolean; print?: boolean }) => {
69
- const config = await readConfig();
70
- const projects = listProjects(config);
73
+ .command("list")
74
+ .alias("ls")
75
+ .description("List projects")
76
+ .option("--plain", "Print a non-interactive list")
77
+ .option("--json", "Print JSON output")
78
+ .option("--print", "Print selected path only")
79
+ .action(
80
+ async (options: { plain?: boolean; json?: boolean; print?: boolean }) => {
81
+ const config = await readConfig();
82
+ const projects = listProjects(config);
71
83
 
72
- if (options.json) {
73
- process.stdout.write(JSON.stringify(projects, null, 2));
74
- process.stdout.write('\n');
75
- return;
76
- }
84
+ if (options.json) {
85
+ process.stdout.write(JSON.stringify(projects, null, 2));
86
+ process.stdout.write("\n");
87
+ return;
88
+ }
77
89
 
78
- if (!process.stdin.isTTY || options.plain) {
79
- if (projects.length === 0) {
80
- process.stdout.write('No projects indexed. Run bet update.\n');
90
+ if (!process.stdin.isTTY || options.plain) {
91
+ if (projects.length === 0) {
92
+ process.stdout.write("No projects indexed. Run bet update.\n");
93
+ return;
94
+ }
95
+ const orderedGroups = orderedGroupsByConfigRoots(
96
+ projects,
97
+ config.roots,
98
+ );
99
+ let groupIndex = 0;
100
+ for (const { rootName, items } of orderedGroups) {
101
+ const color = groupColor(groupIndex++);
102
+ const label = chalk.hex(color).bold(`[${rootName}]`);
103
+ process.stdout.write(`${label}\n`);
104
+ for (const project of items) {
105
+ const rel = relativePath(project.path, project.root);
106
+ process.stdout.write(
107
+ ` ${chalk.reset(formatLabel(project.slug, project.rootName, rel))}\n`,
108
+ );
109
+ }
110
+ }
81
111
  return;
82
112
  }
83
- const orderedGroups = orderedGroupsByConfigRoots(projects, config.roots);
113
+
114
+ const orderedGroups = orderedGroupsByConfigRoots(
115
+ projects,
116
+ config.roots,
117
+ );
118
+ const rows: SelectRow<Project>[] = [];
84
119
  let groupIndex = 0;
85
120
  for (const { rootName, items } of orderedGroups) {
86
- const color = groupColor(groupIndex++);
87
- const label = chalk.hex(color).bold(`[${rootName}]`);
88
- process.stdout.write(`${label}\n`);
121
+ rows.push({
122
+ type: "group",
123
+ label: rootName,
124
+ color: groupColor(groupIndex++),
125
+ });
89
126
  for (const project of items) {
90
127
  const rel = relativePath(project.path, project.root);
91
- process.stdout.write(` ${chalk.reset(formatLabel(project.slug, project.rootName, rel))}\n`);
128
+ rows.push({
129
+ type: "item",
130
+ label: formatLabel(project.slug, project.rootName, rel),
131
+ value: project,
132
+ });
92
133
  }
93
134
  }
94
- return;
95
- }
96
-
97
- const orderedGroups = orderedGroupsByConfigRoots(projects, config.roots);
98
- const rows: SelectRow<Project>[] = [];
99
- let groupIndex = 0;
100
- for (const { rootName, items } of orderedGroups) {
101
- rows.push({ type: 'group', label: rootName, color: groupColor(groupIndex++) });
102
- for (const project of items) {
103
- const rel = relativePath(project.path, project.root);
104
- rows.push({
105
- type: 'item',
106
- label: formatLabel(project.slug, project.rootName, rel),
107
- value: project,
108
- });
109
- }
110
- }
111
135
 
112
- const selected = await promptSelect(rows, { title: 'Projects', maxRows: MAX_ROWS });
113
- if (!selected) return;
136
+ const selected = await promptSelect(rows, {
137
+ title: "Projects",
138
+ maxRows: MAX_ROWS,
139
+ });
140
+ if (!selected) return;
114
141
 
115
- emitSelection(selected.value, { printOnly: options.print });
116
- });
142
+ emitSelection(selected.value, { printOnly: options.print });
143
+ },
144
+ );
117
145
  }
@@ -3,7 +3,12 @@ import readline from "node:readline";
3
3
  import { Command } from "commander";
4
4
  import { readConfig, resolveRoots, writeConfig } from "../lib/config.js";
5
5
  import { normalizeAbsolute } from "../utils/paths.js";
6
- import { installUpdateCron, uninstallUpdateCron, parseCronSchedule, formatScheduleLabel } from "../lib/cron.js";
6
+ import {
7
+ installUpdateCron,
8
+ uninstallUpdateCron,
9
+ parseCronSchedule,
10
+ formatScheduleLabel,
11
+ } from "../lib/cron.js";
7
12
  import { scanRoots } from "../lib/scan.js";
8
13
  import { computeMetadata } from "../lib/metadata.js";
9
14
  import { getEffectiveIgnores, isPathIgnored } from "../lib/ignore.js";
@@ -30,16 +35,16 @@ export function willOverrideRoots(
30
35
  providedRootConfigs: RootConfig[] | undefined,
31
36
  configRoots: RootConfig[],
32
37
  ): boolean {
33
- return !!(
34
- providedRootConfigs !== undefined &&
35
- configRoots.length > 0
36
- );
38
+ return !!(providedRootConfigs !== undefined && configRoots.length > 0);
37
39
  }
38
40
 
39
41
  const DEFAULT_SLUG_PARENT_FOLDERS = ["src", "app"];
40
42
 
41
43
  export { DEFAULT_SLUG_PARENT_FOLDERS };
42
- export function projectSlug(pathName: string, slugParentFolders: string[]): string {
44
+ export function projectSlug(
45
+ pathName: string,
46
+ slugParentFolders: string[],
47
+ ): string {
43
48
  const folderName = path.basename(pathName);
44
49
  if (slugParentFolders.includes(folderName)) {
45
50
  return path.basename(path.dirname(pathName));
@@ -47,8 +52,14 @@ export function projectSlug(pathName: string, slugParentFolders: string[]): stri
47
52
  return folderName;
48
53
  }
49
54
 
50
- async function promptYesNo(question: string, defaultNo = true): Promise<boolean> {
51
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
55
+ async function promptYesNo(
56
+ question: string,
57
+ defaultNo = true,
58
+ ): Promise<boolean> {
59
+ const rl = readline.createInterface({
60
+ input: process.stdin,
61
+ output: process.stderr,
62
+ });
52
63
  return new Promise((resolve) => {
53
64
  const defaultHint = defaultNo ? "y/N" : "Y/n";
54
65
  rl.question(question + " [" + defaultHint + "] ", (answer) => {
@@ -69,165 +80,209 @@ export function registerUpdate(program: Command): void {
69
80
  .description("Scan roots and update the project index")
70
81
  .option("--roots <paths>", "Comma-separated list of roots to scan")
71
82
  .option("--force", "Allow overriding configured roots when not in TTY")
72
- .option("--cron [frequency]", "Run update on a schedule: Nm/Nh/Nd e.g. 5m, 1h, 2d (default 1h), or 0/false to disable")
73
- .action(async (options: { roots?: string; force?: boolean; cron?: boolean | string }) => {
74
- try {
75
- const config = await readConfig();
76
- const providedPaths = parseRoots(options.roots);
77
- const providedRootConfigs = providedPaths
78
- ? pathsToRootConfigs(providedPaths)
79
- : undefined;
80
- const configRoots = config.roots.length > 0 ? config.roots : undefined;
81
- const rootsToUse = providedRootConfigs ?? configRoots;
82
-
83
- if (!rootsToUse || rootsToUse.length === 0) {
84
- log.error("update failed: no roots specified");
85
- process.stderr.write(
86
- "Error: No roots specified. Please provide roots using --roots option.\n" +
87
- "Example: bet update --roots /path/to/your/code\n",
88
- );
89
- process.exitCode = 1;
90
- return;
91
- }
83
+ .option(
84
+ "--cron [frequency]",
85
+ "Run update on a schedule: Nm/Nh/Nd e.g. 5m, 1h, 2d (default 1h), or 0/false to disable",
86
+ )
87
+ .action(
88
+ async (options: {
89
+ roots?: string;
90
+ force?: boolean;
91
+ cron?: boolean | string;
92
+ }) => {
93
+ try {
94
+ const config = await readConfig();
95
+ const providedPaths = parseRoots(options.roots);
96
+ const providedRootConfigs = providedPaths
97
+ ? pathsToRootConfigs(providedPaths)
98
+ : undefined;
99
+ const configRoots =
100
+ config.roots.length > 0 ? config.roots : undefined;
101
+ const rootsToUse = providedRootConfigs ?? configRoots;
92
102
 
93
- const willOverride = willOverrideRoots(providedRootConfigs, config.roots);
94
-
95
- if (willOverride) {
96
- log.warn("--roots overrides configured roots", "configured:", configRoots!.map((r) => r.path).join(", "), "provided:", providedRootConfigs!.map((r) => r.path).join(", "));
97
- process.stderr.write(
98
- "Warning: --roots will override your configured roots.\n" +
99
- " Configured: " +
100
- configRoots!.map((r) => r.path).join(", ") +
101
- "\n Provided: " +
102
- providedRootConfigs!.map((r) => r.path).join(", ") +
103
- "\n",
104
- );
105
- if (!process.stdin.isTTY) {
106
- if (!options.force) {
107
- log.error("update failed: refusing to override roots without confirmation (use --force when not in TTY)");
103
+ if (!rootsToUse || rootsToUse.length === 0) {
104
+ log.error("update failed: no roots specified");
108
105
  process.stderr.write(
109
- "Error: Refusing to override without confirmation. Run interactively or use --force.\n",
106
+ "Error: No roots specified. Please provide roots using --roots option.\n" +
107
+ "Example: bet update --roots /path/to/your/code\n",
110
108
  );
111
109
  process.exitCode = 1;
112
110
  return;
113
111
  }
114
- } else {
115
- const confirmed = await promptYesNo("Continue?", true);
116
- if (!confirmed) {
117
- log.info("update aborted by user");
118
- process.stderr.write("Aborted.\n");
119
- return;
112
+
113
+ const willOverride = willOverrideRoots(
114
+ providedRootConfigs,
115
+ config.roots,
116
+ );
117
+
118
+ if (willOverride) {
119
+ log.warn(
120
+ "--roots overrides configured roots",
121
+ "configured:",
122
+ configRoots!.map((r) => r.path).join(", "),
123
+ "provided:",
124
+ providedRootConfigs!.map((r) => r.path).join(", "),
125
+ );
126
+ process.stderr.write(
127
+ "Warning: --roots will override your configured roots.\n" +
128
+ " Configured: " +
129
+ configRoots!.map((r) => r.path).join(", ") +
130
+ "\n Provided: " +
131
+ providedRootConfigs!.map((r) => r.path).join(", ") +
132
+ "\n",
133
+ );
134
+ if (!process.stdin.isTTY) {
135
+ if (!options.force) {
136
+ log.error(
137
+ "update failed: refusing to override roots without confirmation (use --force when not in TTY)",
138
+ );
139
+ process.stderr.write(
140
+ "Error: Refusing to override without confirmation. Run interactively or use --force.\n",
141
+ );
142
+ process.exitCode = 1;
143
+ return;
144
+ }
145
+ } else {
146
+ const confirmed = await promptYesNo("Continue?", true);
147
+ if (!confirmed) {
148
+ log.info("update aborted by user");
149
+ process.stderr.write("Aborted.\n");
150
+ return;
151
+ }
152
+ }
120
153
  }
121
- }
122
- }
123
154
 
124
- const rootsResolved = resolveRoots(rootsToUse);
125
- const rootPaths = rootsResolved.map((r) => r.path);
126
- log.info("update started", "roots=" + rootPaths.join(", "));
127
- log.debug("scanning roots", rootPaths.length, "root(s)");
128
- const ignores = getEffectiveIgnores(config);
129
- const candidates = await scanRoots(rootPaths, ignores);
130
- const ignoredPaths = config.ignoredPaths ?? [];
131
- const filteredCandidates = candidates.filter(
132
- (c) => !isPathIgnored(c.path, ignoredPaths),
133
- );
134
- log.debug("found", filteredCandidates.length, "candidate(s) after ignoring paths");
135
- const projects: Record<string, Project> = {};
136
-
137
- for (const candidate of filteredCandidates) {
138
- const hasGit = await isInsideGitRepo(candidate.path);
139
- const auto = await computeMetadata(candidate.path, hasGit, ignores);
140
- const slug = projectSlug(candidate.path, config.slugParentFolders ?? DEFAULT_SLUG_PARENT_FOLDERS);
141
- const existing = config.projects[candidate.path];
142
- const rootConfig = rootsResolved.find((r) => r.path === candidate.root);
143
- const rootName = rootConfig?.name ?? path.basename(candidate.root);
144
-
145
- const project: Project = {
146
- id: candidate.path,
147
- slug,
148
- name: slug,
149
- path: candidate.path,
150
- root: candidate.root,
151
- rootName,
152
- hasGit,
153
- hasReadme: candidate.hasReadme,
154
- auto,
155
- user: existing?.user,
156
- };
157
-
158
- projects[candidate.path] = project;
159
- }
155
+ const rootsResolved = resolveRoots(rootsToUse);
156
+ const rootPaths = rootsResolved.map((r) => r.path);
157
+ log.info("update started", "roots=" + rootPaths.join(", "));
158
+ log.debug("scanning roots", rootPaths.length, "root(s)");
159
+ const ignores = getEffectiveIgnores(config);
160
+ const candidates = await scanRoots(rootPaths, ignores);
161
+ const ignoredPaths = config.ignoredPaths ?? [];
162
+ const filteredCandidates = candidates.filter(
163
+ (c) => !isPathIgnored(c.path, ignoredPaths),
164
+ );
165
+ log.debug(
166
+ "found",
167
+ filteredCandidates.length,
168
+ "candidate(s) after ignoring paths",
169
+ );
170
+ const projects: Record<string, Project> = {};
171
+
172
+ for (const candidate of filteredCandidates) {
173
+ const hasGit = await isInsideGitRepo(candidate.path);
174
+ const auto = await computeMetadata(candidate.path, hasGit, ignores);
175
+ const slug = projectSlug(
176
+ candidate.path,
177
+ config.slugParentFolders ?? DEFAULT_SLUG_PARENT_FOLDERS,
178
+ );
179
+ const existing = config.projects[candidate.path];
180
+ const rootConfig = rootsResolved.find(
181
+ (r) => r.path === candidate.root,
182
+ );
183
+ const rootName = rootConfig?.name ?? path.basename(candidate.root);
184
+
185
+ const project: Project = {
186
+ id: candidate.path,
187
+ slug,
188
+ name: slug,
189
+ path: candidate.path,
190
+ root: candidate.root,
191
+ rootName,
192
+ hasGit,
193
+ hasReadme: candidate.hasReadme,
194
+ auto,
195
+ user: existing?.user,
196
+ };
197
+
198
+ projects[candidate.path] = project;
199
+ }
200
+
201
+ const nextConfig: Config = {
202
+ version: config.version ?? 1,
203
+ roots: rootsResolved,
204
+ projects,
205
+ updatedAt: new Date().toISOString(),
206
+ ...(config.ignores !== undefined && { ignores: config.ignores }),
207
+ ...(config.ignoredPaths !== undefined && {
208
+ ignoredPaths: config.ignoredPaths,
209
+ }),
210
+ ...(config.slugParentFolders !== undefined && {
211
+ slugParentFolders: config.slugParentFolders,
212
+ }),
213
+ };
214
+
215
+ await writeConfig(nextConfig);
216
+
217
+ const projectCount = Object.keys(projects).length;
218
+ const rootCount = rootsResolved.length;
219
+ log.info(
220
+ "update completed",
221
+ "projects=" + projectCount,
222
+ "roots=" + rootCount,
223
+ );
160
224
 
161
- const nextConfig: Config = {
162
- version: config.version ?? 1,
163
- roots: rootsResolved,
164
- projects,
165
- ...(config.ignores !== undefined && { ignores: config.ignores }),
166
- ...(config.ignoredPaths !== undefined && { ignoredPaths: config.ignoredPaths }),
167
- ...(config.slugParentFolders !== undefined && { slugParentFolders: config.slugParentFolders }),
168
- };
169
-
170
- await writeConfig(nextConfig);
171
-
172
- const projectCount = Object.keys(projects).length;
173
- const rootCount = rootsResolved.length;
174
- log.info("update completed", "projects=" + projectCount, "roots=" + rootCount);
175
-
176
- process.stdout.write(
177
- "Indexed " +
178
- projectCount +
179
- " projects from " +
180
- rootCount +
181
- " root(s).\n",
182
- );
183
-
184
- if (options.cron !== undefined && options.cron !== false) {
185
- const entryScriptPath = path.isAbsolute(process.argv[1] ?? "")
186
- ? process.argv[1]
187
- : path.resolve(process.cwd(), process.argv[1] ?? "dist/index.js");
188
- const cronOpt = options.cron;
189
- if (cronOpt === true) {
190
- const { wrapperPath, logPath } = await installUpdateCron({
191
- nodePath: process.execPath,
192
- entryScriptPath,
193
- schedule: "1h",
194
- });
195
- process.stdout.write("Installed cron for bet update (every hour).\n");
196
225
  process.stdout.write(
197
- ` Wrapper script: ${wrapperPath}\n Log file: ${logPath}\n To view or edit crontab: crontab -l / crontab -e\n`,
226
+ "Indexed " +
227
+ projectCount +
228
+ " projects from " +
229
+ rootCount +
230
+ " root(s).\n",
198
231
  );
199
- } else if (typeof cronOpt === "string") {
200
- const normalized = cronOpt.trim().toLowerCase();
201
- if (normalized === "0" || normalized === "false") {
202
- await uninstallUpdateCron();
203
- process.stdout.write("Removed cron for bet update.\n");
204
- } else {
205
- try {
206
- const parsed = parseCronSchedule(cronOpt);
232
+
233
+ if (options.cron !== undefined && options.cron !== false) {
234
+ const entryScriptPath = path.isAbsolute(process.argv[1] ?? "")
235
+ ? process.argv[1]
236
+ : path.resolve(process.cwd(), process.argv[1] ?? "dist/index.js");
237
+ const cronOpt = options.cron;
238
+ if (cronOpt === true) {
207
239
  const { wrapperPath, logPath } = await installUpdateCron({
208
240
  nodePath: process.execPath,
209
241
  entryScriptPath,
210
- schedule: cronOpt,
242
+ schedule: "1h",
211
243
  });
212
- const label = formatScheduleLabel(parsed);
213
- process.stdout.write(`Installed cron for bet update (${label}).\n`);
244
+ process.stdout.write(
245
+ "Installed cron for bet update (every hour).\n",
246
+ );
214
247
  process.stdout.write(
215
248
  ` Wrapper script: ${wrapperPath}\n Log file: ${logPath}\n To view or edit crontab: crontab -l / crontab -e\n`,
216
249
  );
217
- } catch (err) {
218
- const message = err instanceof Error ? err.message : String(err);
219
- log.error(err instanceof Error ? err : new Error(message));
220
- process.stderr.write(`Error: ${message}\n`);
221
- process.exitCode = 1;
250
+ } else if (typeof cronOpt === "string") {
251
+ const normalized = cronOpt.trim().toLowerCase();
252
+ if (normalized === "0" || normalized === "false") {
253
+ await uninstallUpdateCron();
254
+ process.stdout.write("Removed cron for bet update.\n");
255
+ } else {
256
+ try {
257
+ const parsed = parseCronSchedule(cronOpt);
258
+ const { wrapperPath, logPath } = await installUpdateCron({
259
+ nodePath: process.execPath,
260
+ entryScriptPath,
261
+ schedule: cronOpt,
262
+ });
263
+ const label = formatScheduleLabel(parsed);
264
+ process.stdout.write(
265
+ `Installed cron for bet update (${label}).\n`,
266
+ );
267
+ process.stdout.write(
268
+ ` Wrapper script: ${wrapperPath}\n Log file: ${logPath}\n To view or edit crontab: crontab -l / crontab -e\n`,
269
+ );
270
+ } catch (err) {
271
+ const message =
272
+ err instanceof Error ? err.message : String(err);
273
+ log.error(err instanceof Error ? err : new Error(message));
274
+ process.stderr.write(`Error: ${message}\n`);
275
+ process.exitCode = 1;
276
+ }
277
+ }
222
278
  }
223
279
  }
280
+ } catch (err) {
281
+ const message = err instanceof Error ? err.message : String(err);
282
+ log.error(err instanceof Error ? err : new Error(message));
283
+ process.stderr.write(`Error: ${message}\n`);
284
+ process.exitCode = 1;
224
285
  }
225
- }
226
- } catch (err) {
227
- const message = err instanceof Error ? err.message : String(err);
228
- log.error(err instanceof Error ? err : new Error(message));
229
- process.stderr.write(`Error: ${message}\n`);
230
- process.exitCode = 1;
231
- }
232
- });
286
+ },
287
+ );
233
288
  }
package/src/index.ts CHANGED
@@ -1,35 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from "commander";
3
- import { GroupedHelp } from "./lib/help.js";
4
- import { registerUpdate } from "./commands/update.js";
5
- import { registerList } from "./commands/list.js";
6
- import { registerSearch } from "./commands/search.js";
7
- import { registerInfo } from "./commands/info.js";
8
- import { registerGo } from "./commands/go.js";
9
- import { registerPath } from "./commands/path.js";
10
- import { registerShell } from "./commands/shell.js";
11
- import { registerCompletion } from "./commands/completion.js";
12
- import { registerIgnore } from "./commands/ignore.js";
13
-
14
- const program = new Command();
15
-
16
- program.createHelp = function createHelp(this: Command) {
17
- return Object.assign(new GroupedHelp(), this.configureHelp());
18
- };
19
-
20
- program
21
- .name("bet")
22
- .description("Explore and jump between local projects.")
23
- .version("0.1.4");
24
-
25
- registerUpdate(program);
26
- registerList(program);
27
- registerSearch(program);
28
- registerInfo(program);
29
- registerGo(program);
30
- registerPath(program);
31
- registerShell(program);
32
- registerCompletion(program);
33
- registerIgnore(program);
34
-
35
- program.parseAsync(process.argv);
2
+ /**
3
+ * Set FORCE_COLOR before any module (including Ink/chalk) is loaded so that
4
+ * list/search/info render with colors. ESM hoists static imports, so we must
5
+ * use a dynamic import here.
6
+ */
7
+ process.env.FORCE_COLOR = "1";
8
+
9
+ await import("./main.js");