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.
package/dist/main.js ADDED
@@ -0,0 +1,50 @@
1
+ import { Command } from "commander";
2
+ import { GroupedHelp } from "./lib/help.js";
3
+ import { registerUpdate } from "./commands/update.js";
4
+ import { registerList } from "./commands/list.js";
5
+ import { registerSearch } from "./commands/search.js";
6
+ import { registerInfo } from "./commands/info.js";
7
+ import { registerGo } from "./commands/go.js";
8
+ import { registerPath } from "./commands/path.js";
9
+ import { registerShell } from "./commands/shell.js";
10
+ import { registerCompletion } from "./commands/completion.js";
11
+ import { registerIgnore } from "./commands/ignore.js";
12
+ import { registerEdit } from "./commands/edit.js";
13
+ const ASCII_HEADER = `
14
+ ░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░
15
+ ░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░
16
+ ░░░░░░░░░░░░░ ░░░░░░ ░░░░░░
17
+ ░░░░░░░░░░░░░░░░░ ░░░░░░ ░░░░░░
18
+ ░░░░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░░░
19
+ ░░░░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░░░
20
+ ░░░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░░░
21
+ ░░░░░░░░░░░░░░░░ ░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
22
+ ░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
23
+ ░░░░░░░░░░░░░░ ░░░░░░ ░░░░░░
24
+ ░░░░░░░░░ ░░░░░░ ░░░░░░
25
+ ░░░░░ ░░░░░░ ░░░░░░
26
+ ░░░░░ ░░░░░░ ░░░░░░
27
+ ░░░░░ ░░░░░░ ░░░░░░
28
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
29
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
30
+ `;
31
+ const program = new Command();
32
+ program.createHelp = function createHelp() {
33
+ return Object.assign(new GroupedHelp(), this.configureHelp());
34
+ };
35
+ program
36
+ .name("bet")
37
+ .description("Explore and jump between local projects.")
38
+ .version("0.2.0");
39
+ registerUpdate(program);
40
+ registerList(program);
41
+ registerSearch(program);
42
+ registerInfo(program);
43
+ registerGo(program);
44
+ registerEdit(program);
45
+ registerPath(program);
46
+ registerShell(program);
47
+ registerCompletion(program);
48
+ registerIgnore(program);
49
+ program.addHelpText("before", ASCII_HEADER);
50
+ program.parseAsync(process.argv);
@@ -1,10 +1,9 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Text } from 'ink';
3
- import { marked } from 'marked';
4
- import TerminalRenderer from 'marked-terminal';
5
- const renderer = new TerminalRenderer();
6
- export function Markdown({ content }) {
7
- marked.setOptions({ renderer });
8
- const output = marked.parse(content).trim();
9
- return _jsx(Text, { children: output });
1
+ import { Text } from "ink";
2
+ import { marked } from "marked";
3
+ import { markedTerminal } from "marked-terminal";
4
+ import React from "react";
5
+ export default function Markdown({ children, ...options }) {
6
+ marked.use(markedTerminal(options));
7
+ const parsedMarkdown = marked.parse(children, { async: false });
8
+ return React.createElement(Text, null, parsedMarkdown.trim());
10
9
  }
package/dist/ui/search.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from 'react';
3
- import chalk from 'chalk';
4
3
  import { Box, Text, useInput } from 'ink';
5
4
  const DEFAULT_MAX_ROWS = 18;
6
5
  export function SearchSelect({ title, allItems, filter, onSelect, onCancel, maxRows = DEFAULT_MAX_ROWS, initialQuery = '', showCount = true, }) {
@@ -37,7 +36,7 @@ export function SearchSelect({ title, allItems, filter, onSelect, onCancel, maxR
37
36
  }
38
37
  });
39
38
  if (items.length === 0) {
40
- return (_jsxs(Box, { flexDirection: "column", children: [title && _jsx(Text, { children: chalk.bold(title) }), _jsx(Text, { children: `Search: ${query}` }), _jsx(Text, { children: "No results." })] }));
39
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", padding: 1, children: [title ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: title }) })) : null, _jsxs(Box, { marginBottom: 1, flexDirection: "row", children: [_jsx(Text, { bold: true, color: "yellow", children: "Search: " }), _jsx(Text, { color: "cyan", children: query || '…' })] }), _jsx(Text, { color: "yellow", children: "No results." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Press Esc to exit." }) })] }));
41
40
  }
42
41
  const selectedRowIndex = Math.min(cursor, items.length - 1);
43
42
  const totalRows = items.length;
@@ -45,9 +44,9 @@ export function SearchSelect({ title, allItems, filter, onSelect, onCancel, maxR
45
44
  const windowStart = Math.min(Math.max(0, selectedRowIndex - Math.floor(effectiveMaxRows / 2)), Math.max(0, totalRows - effectiveMaxRows));
46
45
  const windowEnd = Math.min(totalRows, windowStart + effectiveMaxRows);
47
46
  const windowed = items.slice(windowStart, windowEnd);
48
- return (_jsxs(Box, { flexDirection: "column", children: [title && _jsx(Text, { children: chalk.bold(title) }), _jsx(Text, { children: `Search: ${query}` }), showCount && _jsx(Text, { children: chalk.dim(`${items.length} result(s)`) }), windowed.map((row, idx) => {
49
- const absoluteIndex = windowStart + idx;
50
- const selected = absoluteIndex === selectedRowIndex;
51
- return (_jsxs(Box, { children: [_jsxs(Text, { children: [selected ? chalk.cyan.bold('› ') : ' ', selected ? chalk.cyan.bold(row.label) : row.label] }), row.hint ? _jsx(Text, { children: chalk.dim(` ${row.hint}`) }) : null] }, `item-${absoluteIndex}`));
52
- }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: chalk.dim('Type to filter. Use ↑/↓ or j/k. Enter to select. Esc to cancel.') }) })] }));
47
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", padding: 1, children: [title ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: title }) })) : null, _jsxs(Box, { marginBottom: 1, flexDirection: "row", children: [_jsx(Text, { bold: true, color: "yellow", children: "Search: " }), _jsx(Text, { color: "green", children: query || '…' }), showCount ? (_jsxs(Text, { color: "cyan", children: [" \u00B7 ", items.length, " result", items.length !== 1 ? 's' : ''] })) : null] }), _jsx(Box, { flexDirection: "column", children: windowed.map((row, idx) => {
48
+ const absoluteIndex = windowStart + idx;
49
+ const selected = absoluteIndex === selectedRowIndex;
50
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: selected ? 'green' : undefined, bold: selected, children: [selected ? '› ' : ' ', row.label] }), row.hint ? _jsxs(Text, { color: "gray", children: [" ", row.hint] }) : null] }, `item-${absoluteIndex}`));
51
+ }) }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { color: "yellow", children: "Type to filter" }), _jsx(Text, { color: "gray", children: " \u00B7 " }), _jsx(Text, { color: "yellow", children: "\u2191/\u2193 or j/k" }), _jsx(Text, { color: "gray", children: " \u00B7 " }), _jsx(Text, { color: "green", children: "Enter" }), _jsx(Text, { color: "gray", children: " to select \u00B7 " }), _jsx(Text, { color: "red", children: "Esc" }), _jsx(Text, { color: "gray", children: " to cancel" })] })] }));
53
52
  }
package/dist/ui/select.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from 'react';
3
- import chalk from 'chalk';
4
3
  import { Box, Text, useInput } from 'ink';
5
4
  const DEFAULT_MAX_ROWS = 18;
6
5
  export function SelectList({ title, items, onSelect, onCancel, maxRows = DEFAULT_MAX_ROWS, }) {
@@ -31,7 +30,7 @@ export function SelectList({ title, items, onSelect, onCancel, maxRows = DEFAULT
31
30
  }
32
31
  });
33
32
  if (items.length === 0) {
34
- return (_jsxs(Box, { flexDirection: "column", children: [title && _jsx(Text, { children: chalk.bold(title) }), _jsx(Text, { children: "No results." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: chalk.dim('Press Esc to exit.') }) })] }));
33
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", padding: 1, children: [title ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: title }) })) : null, _jsx(Text, { color: "yellow", children: "No results." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Press Esc to exit." }) })] }));
35
34
  }
36
35
  const selectedRowIndex = selectableIndices[cursor] ?? 0;
37
36
  const totalRows = items.length;
@@ -39,13 +38,12 @@ export function SelectList({ title, items, onSelect, onCancel, maxRows = DEFAULT
39
38
  const windowStart = Math.min(Math.max(0, selectedRowIndex - Math.floor(effectiveMaxRows / 2)), Math.max(0, totalRows - effectiveMaxRows));
40
39
  const windowEnd = Math.min(totalRows, windowStart + effectiveMaxRows);
41
40
  const windowed = items.slice(windowStart, windowEnd);
42
- return (_jsxs(Box, { flexDirection: "column", children: [title && _jsx(Text, { children: chalk.bold(title) }), windowed.map((row, idx) => {
43
- const absoluteIndex = windowStart + idx;
44
- const selected = row.type === 'item' && absoluteIndex === selectedRowIndex;
45
- if (row.type === 'group') {
46
- const colored = row.color ? chalk.hex(row.color)(`[${row.label}]`) : `[${row.label}]`;
47
- return (_jsx(Box, { marginTop: idx === 0 ? 0 : 1, children: _jsx(Text, { children: chalk.bold(colored) }) }, `group-${absoluteIndex}`));
48
- }
49
- return (_jsxs(Box, { children: [_jsxs(Text, { children: [selected ? chalk.cyan.bold('› ') : ' ', selected ? chalk.cyan.bold(row.label) : row.label] }), row.hint ? _jsx(Text, { children: chalk.dim(` ${row.hint}`) }) : null] }, `item-${absoluteIndex}`));
50
- }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: chalk.dim('Use ↑/↓ or j/k. Enter to select. Esc to cancel.') }) })] }));
41
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", padding: 1, children: [title ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: title }) })) : null, _jsx(Box, { flexDirection: "column", children: windowed.map((row, idx) => {
42
+ const absoluteIndex = windowStart + idx;
43
+ const selected = row.type === 'item' && absoluteIndex === selectedRowIndex;
44
+ if (row.type === 'group') {
45
+ return (_jsx(Box, { marginTop: idx === 0 ? 0 : 1, children: _jsxs(Text, { bold: true, color: row.color ?? 'cyan', children: ["[", row.label, "]"] }) }, `group-${absoluteIndex}`));
46
+ }
47
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: selected ? 'green' : undefined, bold: selected, children: [selected ? '› ' : ' ', row.label] }), row.hint ? (_jsxs(Text, { color: "gray", children: [" ", row.hint] })) : null] }, `item-${absoluteIndex}`));
48
+ }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "yellow", children: "\u2191/\u2193 or j/k" }), _jsx(Text, { color: "gray", children: " \u00B7 " }), _jsx(Text, { color: "green", children: "Enter" }), _jsx(Text, { color: "gray", children: " to select \u00B7 " }), _jsx(Text, { color: "red", children: "Esc" }), _jsx(Text, { color: "gray", children: " to cancel" })] })] }));
51
49
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bet-cli",
3
3
  "description": "Explore and jump between local projects.",
4
- "version": "0.1.4",
4
+ "version": "0.3.0",
5
5
  "author": "Chris Mckenzie",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -23,6 +23,7 @@
23
23
  "node": ">=20"
24
24
  },
25
25
  "dependencies": {
26
+ "@types/marked-terminal": "^6.1.1",
26
27
  "@types/object-hash": "^3.0.6",
27
28
  "chalk": "^5.3.0",
28
29
  "commander": "^12.0.0",
@@ -31,6 +32,8 @@
31
32
  "ink": "^6.6.0",
32
33
  "ink-markdown": "^1.0.4",
33
34
  "ink-table": "^3.1.0",
35
+ "marked": "^17.0.3",
36
+ "marked-terminal": "^7.3.0",
34
37
  "object-hash": "^3.0.0",
35
38
  "react": "^19.2.4"
36
39
  },
@@ -0,0 +1,56 @@
1
+ import { Command } from "commander";
2
+ import { readConfig } from "../lib/config.js";
3
+ import { openProjectInEditor } from "../lib/editor.js";
4
+ import { findBySlug, listProjects, projectLabel } from "../lib/projects.js";
5
+ import { promptSelect } from "../ui/prompt.js";
6
+ import { SelectEntry } from "../ui/select.js";
7
+
8
+ export function registerEdit(program: Command): void {
9
+ program
10
+ .command("edit <slug>")
11
+ .description("Open a project in your editor")
12
+ .action(async (slug: string) => {
13
+ try {
14
+ const config = await readConfig();
15
+ const projects = listProjects(config);
16
+ const matches = findBySlug(projects, slug);
17
+
18
+ if (matches.length === 0) {
19
+ process.stderr.write(`No project found for slug "${slug}".\n`);
20
+ process.exitCode = 1;
21
+ return;
22
+ }
23
+
24
+ let project = matches[0];
25
+ if (matches.length > 1) {
26
+ if (!process.stdin.isTTY) {
27
+ process.stderr.write(`Slug "${slug}" is ambiguous. Matches:\n`);
28
+ for (const item of matches) {
29
+ process.stderr.write(` ${projectLabel(item)} ${item.path}\n`);
30
+ }
31
+ process.exitCode = 1;
32
+ return;
33
+ }
34
+
35
+ const items: SelectEntry<(typeof matches)[number]>[] = matches.map(
36
+ (item) => ({
37
+ label: projectLabel(item),
38
+ hint: item.path,
39
+ value: item,
40
+ type: "item",
41
+ }),
42
+ );
43
+
44
+ const selected = await promptSelect(items, { title: `Select ${slug}` });
45
+ if (!selected) return;
46
+ project = selected.value;
47
+ }
48
+
49
+ await openProjectInEditor(project.path, config.editor);
50
+ } catch (error) {
51
+ const message = error instanceof Error ? error.message : String(error);
52
+ process.stderr.write(`Error: ${message}\n`);
53
+ process.exitCode = 1;
54
+ }
55
+ });
56
+ }
@@ -38,7 +38,7 @@ export function registerIgnore(program: Command): void {
38
38
  const rootPaths = config.roots.map((r) => r.path);
39
39
  if (!isPathUnderAnyRoot(normalized, rootPaths)) {
40
40
  process.stderr.write(
41
- `Error: Path must be under a configured root.\n Path: ${normalized}\n Roots: ${rootPaths.join(", ")}\n`,
41
+ `Error: Path must be under a configured root. Use an absolute path (e.g. /path/to/project), not a project slug.\n Path: ${normalized}\n Roots: ${rootPaths.join(", ")}\n`,
42
42
  );
43
43
  process.exitCode = 1;
44
44
  return;
@@ -1,168 +1,257 @@
1
1
  import chalk from "chalk";
2
2
  import { Command } from "commander";
3
- import { readConfig } from "../lib/config.js";
3
+ import React from "react";
4
4
  import { render, Box, Text } from "ink";
5
+ import { readConfig } from "../lib/config.js";
5
6
  import { findBySlug, listProjects, projectLabel } from "../lib/projects.js";
6
7
  import { getDirtyStatus, isInsideGitRepo } from "../lib/git.js";
7
8
  import { formatDate } from "../utils/format.js";
8
9
  import { promptSelect } from "../ui/prompt.js";
9
10
  import { SelectEntry } from "../ui/select.js";
10
11
  import { readReadmeContent } from "../lib/readme.js";
11
- import Table from "../ui/table.js";
12
+ import Markdown from "../ui/markdown.js";
13
+
14
+ type MetaRowProps = {
15
+ label: string;
16
+ value: string;
17
+ valueColor?:
18
+ | "green"
19
+ | "red"
20
+ | "yellow"
21
+ | "blue"
22
+ | "cyan"
23
+ | "magenta"
24
+ | "gray";
25
+ };
12
26
 
13
- const data: { [key: string]: string }[] = [];
27
+ const MetaRow: React.FC<MetaRowProps> = ({ label, value, valueColor }) => (
28
+ <Box>
29
+ <Text bold color="gray">{`${label}: `}</Text>
30
+ <Text color={valueColor} bold={!!valueColor}>
31
+ {value}
32
+ </Text>
33
+ </Box>
34
+ );
14
35
 
15
36
  export function registerInfo(program: Command): void {
16
37
  program
17
38
  .command("info <slug>")
18
39
  .description("Show project details")
19
40
  .option("--json", "Print JSON output")
20
- .action(async (slug: string, options: { json?: boolean }) => {
21
- const config = await readConfig();
22
- const projects = listProjects(config);
23
- const matches = findBySlug(projects, slug);
24
-
25
- if (matches.length === 0) {
26
- process.stderr.write(`No project found for slug "${slug}".\n`);
27
- process.exitCode = 1;
28
- return;
29
- }
30
-
31
- let project = matches[0];
32
-
33
- if (matches.length > 1) {
34
- if (!process.stdin.isTTY) {
35
- process.stderr.write(`Slug "${slug}" is ambiguous. Matches:\n`);
36
- for (const item of matches) {
37
- process.stderr.write(` ${projectLabel(item)} ${item.path}\n`);
38
- }
41
+ .option("--full", "Show full README content")
42
+ .action(
43
+ async (slug: string, options: { json?: boolean; full?: boolean }) => {
44
+ const config = await readConfig();
45
+ const projects = listProjects(config);
46
+ const matches = findBySlug(projects, slug);
47
+
48
+ if (matches.length === 0) {
49
+ process.stderr.write(`No project found for slug "${slug}".\n`);
39
50
  process.exitCode = 1;
40
51
  return;
41
52
  }
42
53
 
43
- const items: SelectEntry<(typeof matches)[number]>[] = matches.map(
44
- (item) => ({
45
- label: projectLabel(item),
46
- hint: item.path,
47
- value: item,
48
- type: "item",
49
- }),
50
- );
54
+ let project = matches[0];
55
+
56
+ if (matches.length > 1) {
57
+ if (!process.stdin.isTTY) {
58
+ process.stderr.write(`Slug "${slug}" is ambiguous. Matches:\n`);
59
+ for (const item of matches) {
60
+ process.stderr.write(` ${projectLabel(item)} ${item.path}\n`);
61
+ }
62
+ process.exitCode = 1;
63
+ return;
64
+ }
65
+
66
+ const items: SelectEntry<(typeof matches)[number]>[] = matches.map(
67
+ (item) => ({
68
+ label: projectLabel(item),
69
+ hint: item.path,
70
+ value: item,
71
+ type: "item",
72
+ }),
73
+ );
51
74
 
52
- const selected = await promptSelect(items, { title: `Select ${slug}` });
53
- if (!selected) return;
54
- project = selected.value;
55
- }
56
-
57
- if (options.json) {
58
- process.stdout.write(JSON.stringify(project, null, 2));
59
- process.stdout.write("\n");
60
- return;
61
- }
62
-
63
- const description =
64
- project.user?.description ?? project.auto.description ?? "—";
65
- // Compute git status live
66
- const hasGit = await isInsideGitRepo(project.path);
67
- const dirty = hasGit ? await getDirtyStatus(project.path) : undefined;
68
-
69
- if (process.stdin.isTTY) {
70
- const readme = await readReadmeContent(project.path);
71
- const markdown = readme ?? description;
72
-
73
- let Markdown: React.FC<{ children: string }> | null = null;
74
- try {
75
- const markdownModule = await import("ink-markdown");
76
- Markdown = (markdownModule.default ??
77
- markdownModule) as unknown as React.FC<{
78
- children: string;
79
- }>;
80
- } catch {
81
- Markdown = null;
75
+ const selected = await promptSelect(items, {
76
+ title: `Select ${slug}`,
77
+ });
78
+ if (!selected) return;
79
+ project = selected.value;
82
80
  }
83
81
 
84
- const view = (
85
- <Box flexDirection="column">
86
- <Table data={data} />
87
- <Text color="green" bold>
88
- {project.slug}
89
- </Text>
90
- <Text dimColor>{project.path}</Text>
91
- <Box marginTop={1} flexDirection="column">
92
- <Text bold>{`Root: ${project.rootName}`}</Text>
93
- <Text bold>{`Root path: ${project.root}`}</Text>
94
- <Text bold>{`Git: ${hasGit ? "yes" : "no"}`}</Text>
95
- <Text bold>{`README: ${project.hasReadme ? "yes" : "no"}`}</Text>
96
- <Text
97
- bold
98
- >{`Started: ${formatDate(project.auto.startedAt)}`}</Text>
99
- <Text
100
- bold
101
- >{`Last modified: ${formatDate(project.auto.lastModifiedAt)}`}</Text>
102
- <Text
103
- bold
104
- >{`Last indexed: ${formatDate(project.auto.lastIndexedAt)}`}</Text>
105
- <Text
106
- bold
107
- >{`Dirty: ${dirty === undefined ? "unknown" : dirty ? "yes" : "no"}`}</Text>
108
- {project.user?.tags?.length ? (
109
- <Text>{`Tags: ${project.user.tags.join(", ")}`}</Text>
110
- ) : null}
111
- {project.user?.onEnter ? (
112
- <Text>{`On enter: ${project.user.onEnter}`}</Text>
113
- ) : null}
114
- </Box>
115
- <Box marginTop={1} flexDirection="column">
116
- <Text>{chalk.bold("Description")}</Text>
117
- {Markdown ? (
82
+ if (options.json) {
83
+ process.stdout.write(JSON.stringify(project, null, 2));
84
+ process.stdout.write("\n");
85
+ return;
86
+ }
87
+
88
+ const description =
89
+ project.user?.description ?? project.auto.description ?? "";
90
+ // Compute git status live
91
+ const hasGit = await isInsideGitRepo(project.path);
92
+ const dirty = hasGit ? await getDirtyStatus(project.path) : undefined;
93
+
94
+ if (process.stdin.isTTY) {
95
+ const readme = options.full
96
+ ? await readReadmeContent(project.path, { full: true })
97
+ : null;
98
+ const markdown = readme ?? description;
99
+
100
+ const view = (
101
+ <Box flexDirection="column" width="100%">
102
+ <Box
103
+ width="100%"
104
+ borderStyle="single"
105
+ borderColor="green"
106
+ paddingX={1}
107
+ paddingY={1}
108
+ marginBottom={1}
109
+ flexDirection="column"
110
+ >
111
+ <Box width="100%" paddingBottom={1}>
112
+ <Text color="green" bold>
113
+ {project.slug}
114
+ </Text>
115
+ </Box>
116
+ <Box width="100%">
117
+ <Text color="cyan">{project.path}</Text>
118
+ </Box>
119
+ </Box>
120
+ <Box
121
+ borderStyle="round"
122
+ borderColor="cyan"
123
+ padding={1}
124
+ flexDirection="column"
125
+ marginBottom={1}
126
+ >
127
+ <Box marginBottom={1}>
128
+ <Text bold color="magenta">
129
+ Details
130
+ </Text>
131
+ </Box>
132
+ <Box flexDirection="column">
133
+ <MetaRow
134
+ label="Git"
135
+ value={hasGit ? "yes" : "no"}
136
+ valueColor={hasGit ? "green" : "yellow"}
137
+ />
138
+ <MetaRow
139
+ label="Git dirty"
140
+ value={
141
+ dirty === undefined ? "unknown" : dirty ? "yes" : "no"
142
+ }
143
+ valueColor={
144
+ dirty === undefined ? "yellow" : dirty ? "red" : "green"
145
+ }
146
+ />
147
+ <MetaRow
148
+ label="README"
149
+ value={project.hasReadme ? "yes" : "no"}
150
+ valueColor={project.hasReadme ? "green" : "yellow"}
151
+ />
152
+ <MetaRow
153
+ label="Started"
154
+ value={formatDate(project.auto.startedAt)}
155
+ />
156
+ <MetaRow
157
+ label="Last modified"
158
+ value={formatDate(project.auto.lastModifiedAt)}
159
+ />
160
+ <MetaRow
161
+ label="Last indexed"
162
+ value={formatDate(project.auto.lastIndexedAt)}
163
+ />
164
+
165
+ <MetaRow label="Root" value={project.rootName} />
166
+ <MetaRow label="Root path" value={project.root} />
167
+ {project.user?.tags?.length ? (
168
+ <Box>
169
+ <Text bold color="gray">{`Tags: `}</Text>
170
+ <Text color="magenta">
171
+ {project.user.tags.join(", ")}
172
+ </Text>
173
+ </Box>
174
+ ) : null}
175
+ {project.user?.onEnter ? (
176
+ <Box>
177
+ <Text bold color="gray">{`On enter: `}</Text>
178
+ <Text color="blue">{project.user.onEnter}</Text>
179
+ </Box>
180
+ ) : null}
181
+ </Box>
182
+ </Box>
183
+ <Box
184
+ borderStyle="round"
185
+ borderColor="magenta"
186
+ padding={1}
187
+ flexDirection="column"
188
+ >
189
+ <Box marginBottom={1}>
190
+ <Text bold color="magenta">
191
+ Description
192
+ </Text>
193
+ </Box>
118
194
  <Markdown>{markdown}</Markdown>
119
- ) : (
120
- <Text>{markdown}</Text>
121
- )}
195
+ </Box>
196
+ {!options.full && project.hasReadme ? (
197
+ <Box marginTop={1}>
198
+ <Text color="yellow">
199
+ Tip: Run <Text bold>bet info {project.slug} --full</Text> to
200
+ read the full README.
201
+ </Text>
202
+ </Box>
203
+ ) : null}
122
204
  </Box>
123
- </Box>
124
- );
205
+ );
206
+
207
+ const { unmount } = render(view, { stdout: process.stdout });
208
+ await new Promise((resolve) => setTimeout(resolve, 0));
209
+ unmount();
210
+ return;
211
+ }
125
212
 
126
- const { unmount } = render(view, { stdout: process.stdout });
127
- await new Promise((resolve) => setTimeout(resolve, 0));
128
- unmount();
129
- return;
130
- }
131
-
132
- process.stdout.write(`${chalk.bold(project.slug)}\n`);
133
- process.stdout.write(`${chalk.dim(project.path)}\n\n`);
134
-
135
- process.stdout.write(`${chalk.bold("Root:")} ${project.rootName}\n`);
136
- process.stdout.write(`${chalk.bold("Root path:")} ${project.root}\n`);
137
- process.stdout.write(`${chalk.bold("Git:")} ${hasGit ? "yes" : "no"}\n`);
138
- process.stdout.write(
139
- `${chalk.bold("README:")} ${project.hasReadme ? "yes" : "no"}\n\n`,
140
- );
141
-
142
- process.stdout.write(`${chalk.bold("Description:")} ${description}\n`);
143
- process.stdout.write(
144
- `${chalk.bold("Started:")} ${formatDate(project.auto.startedAt)}\n`,
145
- );
146
- process.stdout.write(
147
- `${chalk.bold("Last modified:")} ${formatDate(project.auto.lastModifiedAt)}\n`,
148
- );
149
- process.stdout.write(
150
- `${chalk.bold("Last indexed:")} ${formatDate(project.auto.lastIndexedAt)}\n`,
151
- );
152
- process.stdout.write(
153
- `${chalk.bold("Dirty:")} ${dirty === undefined ? "unknown" : dirty ? "yes" : "no"}\n`,
154
- );
155
-
156
- if (project.user?.tags?.length) {
213
+ process.stdout.write(`${chalk.bold(project.slug)}\n`);
214
+ process.stdout.write(`${chalk.dim(project.path)}\n\n`);
215
+
216
+ process.stdout.write(`${chalk.bold("Root:")} ${project.rootName}\n`);
217
+ process.stdout.write(`${chalk.bold("Root path:")} ${project.root}\n`);
218
+ process.stdout.write(
219
+ `${chalk.bold("Git:")} ${hasGit ? "yes" : "no"}\n`,
220
+ );
157
221
  process.stdout.write(
158
- `${chalk.bold("Tags:")} ${project.user.tags.join(", ")}\n`,
222
+ `${chalk.bold("README:")} ${project.hasReadme ? "yes" : "no"}\n\n`,
159
223
  );
160
- }
161
224
 
162
- if (project.user?.onEnter) {
225
+ const descToShow =
226
+ options.full && project.hasReadme
227
+ ? ((await readReadmeContent(project.path, { full: true })) ??
228
+ description)
229
+ : description;
230
+ process.stdout.write(`${chalk.bold("Description:")} ${descToShow}\n`);
231
+ process.stdout.write(
232
+ `${chalk.bold("Started:")} ${formatDate(project.auto.startedAt)}\n`,
233
+ );
234
+ process.stdout.write(
235
+ `${chalk.bold("Last modified:")} ${formatDate(project.auto.lastModifiedAt)}\n`,
236
+ );
237
+ process.stdout.write(
238
+ `${chalk.bold("Last indexed:")} ${formatDate(project.auto.lastIndexedAt)}\n`,
239
+ );
163
240
  process.stdout.write(
164
- `${chalk.bold("On enter:")} ${project.user.onEnter}\n`,
241
+ `${chalk.bold("Dirty:")} ${dirty === undefined ? "unknown" : dirty ? "yes" : "no"}\n`,
165
242
  );
166
- }
167
- });
243
+
244
+ if (project.user?.tags?.length) {
245
+ process.stdout.write(
246
+ `${chalk.bold("Tags:")} ${project.user.tags.join(", ")}\n`,
247
+ );
248
+ }
249
+
250
+ if (project.user?.onEnter) {
251
+ process.stdout.write(
252
+ `${chalk.bold("On enter:")} ${project.user.onEnter}\n`,
253
+ );
254
+ }
255
+ },
256
+ );
168
257
  }