bet-cli 0.2.0 → 0.3.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/README.md +24 -2
- package/dist/commands/edit.js +48 -0
- package/dist/commands/info.js +6 -3
- package/dist/commands/update.js +12 -6
- package/dist/lib/config.js +9 -0
- package/dist/lib/editor.js +97 -0
- package/dist/lib/git.js +14 -8
- package/dist/lib/help.js +15 -3
- package/dist/main.js +17 -15
- package/landing-page/index.html +996 -0
- package/landing-page/vercel.json +25 -0
- package/package.json +1 -1
- package/skills/bet/SKILL.md +129 -0
- package/src/commands/edit.ts +56 -0
- package/src/commands/info.tsx +200 -175
- package/src/commands/update.ts +203 -149
- package/src/lib/config.ts +17 -1
- package/src/lib/editor.ts +131 -0
- package/src/lib/git.ts +20 -10
- package/src/lib/help.ts +28 -15
- package/src/lib/types.ts +2 -0
- package/src/main.ts +3 -1
- package/tests/config.test.ts +71 -0
- package/tests/editor.test.ts +167 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"headers": [
|
|
3
|
+
{
|
|
4
|
+
"source": "/(.*)",
|
|
5
|
+
"headers": [
|
|
6
|
+
{
|
|
7
|
+
"key": "Referrer-Policy",
|
|
8
|
+
"value": "strict-origin-when-cross-origin"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"key": "X-Content-Type-Options",
|
|
12
|
+
"value": "nosniff"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"key": "X-Frame-Options",
|
|
16
|
+
"value": "DENY"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"key": "Permissions-Policy",
|
|
20
|
+
"value": "camera=(), microphone=(), geolocation=()"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bet
|
|
3
|
+
description: Use the bet CLI to find, inspect, and jump between local projects from natural-language requests. Triggers on requests like "jump to my X project", "open the api repo in my editor", "what's the path to X", "find projects matching Y", "which of my projects have uncommitted changes", "list projects I haven't touched in N months", or any task where the user refers to a local project by name/topic instead of by path. Also use when the user types /bet.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# bet
|
|
7
|
+
|
|
8
|
+
`bet` is a local project index. It scans configured root folders, detects projects by `.git`/`README.md`, and exposes them through fast lookup commands. Treat it as the canonical way to resolve a project name → an absolute path on this machine.
|
|
9
|
+
|
|
10
|
+
## Quick command map
|
|
11
|
+
|
|
12
|
+
Map the user's intent to a command. Always use the slug (kebab-case folder name), not a fuzzy display name.
|
|
13
|
+
|
|
14
|
+
| User asks for… | Run |
|
|
15
|
+
|---|---|
|
|
16
|
+
| The path to project `X` | `bet path X` |
|
|
17
|
+
| Details / metadata about `X` (description, root, git state) | `bet info X --json` |
|
|
18
|
+
| The full README of `X` | `bet info X --full` |
|
|
19
|
+
| A list of all projects | `bet list --json` |
|
|
20
|
+
| Search by keyword `Y` | `bet search Y --json` |
|
|
21
|
+
| Open `X` in the user's editor | `bet edit X` |
|
|
22
|
+
| Re-scan disk for new projects | `bet update` |
|
|
23
|
+
| Add/remove/list ignored paths | `bet ignore add <path>` / `bet ignore rm <path>` / `bet ignore list` |
|
|
24
|
+
|
|
25
|
+
## Non-interactive flags are mandatory
|
|
26
|
+
|
|
27
|
+
`bet list` and `bet search` launch an interactive TUI by default. **Never** run them without `--plain` or `--json` from an agent context — they will hang waiting for keystrokes.
|
|
28
|
+
|
|
29
|
+
- `--json` — machine-readable, the right default for filtering/parsing
|
|
30
|
+
- `--plain` — line-per-project text, fine when only a name is needed
|
|
31
|
+
- `--print` — emit the selected absolute path only
|
|
32
|
+
|
|
33
|
+
## Resolving a path before `cd`
|
|
34
|
+
|
|
35
|
+
`bet go <slug>` only changes the shell's directory when the user has the shell integration (`eval "$(bet shell)"`) active. From a Bash tool call, that integration is **not** loaded, so `bet go` will not actually `cd`.
|
|
36
|
+
|
|
37
|
+
When you need to enter a project directory inside a Bash tool call, resolve the path first:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
cd "$(bet path X)" && <command>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This works without any shell integration and is the correct pattern for agent-driven workflows.
|
|
44
|
+
|
|
45
|
+
## The `--json` schema
|
|
46
|
+
|
|
47
|
+
`bet list --json` and `bet info <slug> --json` return objects with these fields:
|
|
48
|
+
|
|
49
|
+
```jsonc
|
|
50
|
+
{
|
|
51
|
+
"id": "/abs/path", // unique id (= path)
|
|
52
|
+
"slug": "my-project", // use this with all commands
|
|
53
|
+
"name": "my-project",
|
|
54
|
+
"path": "/abs/path",
|
|
55
|
+
"root": "/abs/root", // which configured root it lives under
|
|
56
|
+
"rootName": "code", // friendly root label
|
|
57
|
+
"hasGit": true,
|
|
58
|
+
"hasReadme": true,
|
|
59
|
+
"auto": {
|
|
60
|
+
"description": "...", // first paragraph of README
|
|
61
|
+
"startedAt": "ISO-8601", // first git commit date
|
|
62
|
+
"lastModifiedAt": "ISO-8601",// most recent file mtime
|
|
63
|
+
"lastIndexedAt": "ISO-8601",
|
|
64
|
+
"dirty": true // uncommitted changes
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`bet list --json` returns an array of these.
|
|
70
|
+
|
|
71
|
+
## `--json | jq` recipes
|
|
72
|
+
|
|
73
|
+
The single highest-leverage feature for an agent. Reach for these when the user asks anything analytical about their projects:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
# Projects with uncommitted work
|
|
77
|
+
bet list --json | jq 'map(select(.auto.dirty)) | .[].slug'
|
|
78
|
+
|
|
79
|
+
# Projects modified in the last 30 days, newest first
|
|
80
|
+
bet list --json | jq 'sort_by(.auto.lastModifiedAt) | reverse | map(select(.auto.lastModifiedAt > (now - 86400*30 | todateiso8601)))'
|
|
81
|
+
|
|
82
|
+
# Stale projects (untouched > 6 months)
|
|
83
|
+
bet list --json | jq --arg cutoff "$(date -u -v-6m +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '6 months ago' +%Y-%m-%dT%H:%M:%SZ)" \
|
|
84
|
+
'map(select(.auto.lastModifiedAt < $cutoff)) | .[].slug'
|
|
85
|
+
|
|
86
|
+
# Group projects by root
|
|
87
|
+
bet list --json | jq 'group_by(.rootName) | map({root: .[0].rootName, count: length, slugs: map(.slug)})'
|
|
88
|
+
|
|
89
|
+
# Projects whose description mentions a keyword
|
|
90
|
+
bet list --json | jq --arg q "rust" 'map(select(.auto.description | test($q; "i"))) | .[].slug'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Compose `jq` filters from `auto.*` fields rather than re-implementing logic in shell. The user's README at `bet --help` calls out this pattern explicitly.
|
|
94
|
+
|
|
95
|
+
## When `bet` returns nothing
|
|
96
|
+
|
|
97
|
+
If `bet list --json` is empty or `bet path X` errors with an unknown slug, the user has not indexed yet (or the index is stale). Run:
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
bet update
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
If this is their first run, `bet update` will fail without roots. Tell the user to run:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
bet update --roots "$HOME/code,$HOME/work"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
…with whatever roots make sense for their machine. Do not pick roots for them.
|
|
110
|
+
|
|
111
|
+
## Slug rules — what to actually pass
|
|
112
|
+
|
|
113
|
+
- The slug is the project folder's basename, kebab-cased.
|
|
114
|
+
- If the project sits inside a wrapper folder named in `slugParentFolders` (defaults: `src`, `app`), the slug is the **parent's** name. Example: `~/code/foo/src` → slug `foo`, not `src`.
|
|
115
|
+
- When unsure, run `bet search <fuzzy>` first and read back the slug from JSON.
|
|
116
|
+
|
|
117
|
+
## Common pitfalls
|
|
118
|
+
|
|
119
|
+
- **TUI hang** — `bet list` / `bet search` without `--plain`/`--json` will block waiting for keystrokes. Always pass one.
|
|
120
|
+
- **`bet go` in Bash** — won't actually change directory; use `cd "$(bet path X)"` instead.
|
|
121
|
+
- **Treating the name as the slug** — `bet info "My Project"` will fail; use the slug from `bet list --json`.
|
|
122
|
+
- **Stale index after creating a new project** — run `bet update` before assuming the new project is searchable. Suggest `bet update --cron 1h` if the user creates projects often.
|
|
123
|
+
- **Editing config by hand** — config lives at `~/.config/bet/config.json` (or `$XDG_CONFIG_HOME/bet/config.json`). Prefer commands (`bet update --roots`, `bet ignore add`) over hand-editing.
|
|
124
|
+
|
|
125
|
+
## File locations (for debugging only)
|
|
126
|
+
|
|
127
|
+
- Config + ignore list: `~/.config/bet/config.json`
|
|
128
|
+
- Project index: `~/.config/bet/projects.json`
|
|
129
|
+
- Logs: `~/Library/Logs/bet/bet.log` (macOS) or `~/.local/state/bet/bet.log` (Linux). Set `BET_LOG_LEVEL=debug` for verbose output.
|
|
@@ -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
|
+
}
|
package/src/commands/info.tsx
CHANGED
|
@@ -39,194 +39,219 @@ export function registerInfo(program: Command): void {
|
|
|
39
39
|
.description("Show project details")
|
|
40
40
|
.option("--json", "Print JSON output")
|
|
41
41
|
.option("--full", "Show full README content")
|
|
42
|
-
.action(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let project = matches[0];
|
|
54
|
-
|
|
55
|
-
if (matches.length > 1) {
|
|
56
|
-
if (!process.stdin.isTTY) {
|
|
57
|
-
process.stderr.write(`Slug "${slug}" is ambiguous. Matches:\n`);
|
|
58
|
-
for (const item of matches) {
|
|
59
|
-
process.stderr.write(` ${projectLabel(item)} ${item.path}\n`);
|
|
60
|
-
}
|
|
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`);
|
|
61
50
|
process.exitCode = 1;
|
|
62
51
|
return;
|
|
63
52
|
}
|
|
64
53
|
|
|
65
|
-
|
|
66
|
-
(item) => ({
|
|
67
|
-
label: projectLabel(item),
|
|
68
|
-
hint: item.path,
|
|
69
|
-
value: item,
|
|
70
|
-
type: "item",
|
|
71
|
-
}),
|
|
72
|
-
);
|
|
54
|
+
let project = matches[0];
|
|
73
55
|
|
|
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
|
-
|
|
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
|
+
);
|
|
74
|
+
|
|
75
|
+
const selected = await promptSelect(items, {
|
|
76
|
+
title: `Select ${slug}`,
|
|
77
|
+
});
|
|
78
|
+
if (!selected) return;
|
|
79
|
+
project = selected.value;
|
|
80
|
+
}
|
|
81
|
+
|
|
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>
|
|
123
119
|
</Box>
|
|
124
|
-
<Box
|
|
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
|
-
|
|
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>
|
|
168
182
|
</Box>
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
</
|
|
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>
|
|
194
|
+
<Markdown>{markdown}</Markdown>
|
|
180
195
|
</Box>
|
|
181
|
-
|
|
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}
|
|
182
204
|
</Box>
|
|
183
|
-
|
|
184
|
-
);
|
|
205
|
+
);
|
|
185
206
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
process.stdout.write(`${chalk.bold("Git:")} ${hasGit ? "yes" : "no"}\n`);
|
|
198
|
-
process.stdout.write(
|
|
199
|
-
`${chalk.bold("README:")} ${project.hasReadme ? "yes" : "no"}\n\n`,
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
const descToShow =
|
|
203
|
-
options.full && project.hasReadme
|
|
204
|
-
? (await readReadmeContent(project.path, { full: true })) ?? description
|
|
205
|
-
: description;
|
|
206
|
-
process.stdout.write(`${chalk.bold("Description:")} ${descToShow}\n`);
|
|
207
|
-
process.stdout.write(
|
|
208
|
-
`${chalk.bold("Started:")} ${formatDate(project.auto.startedAt)}\n`,
|
|
209
|
-
);
|
|
210
|
-
process.stdout.write(
|
|
211
|
-
`${chalk.bold("Last modified:")} ${formatDate(project.auto.lastModifiedAt)}\n`,
|
|
212
|
-
);
|
|
213
|
-
process.stdout.write(
|
|
214
|
-
`${chalk.bold("Last indexed:")} ${formatDate(project.auto.lastIndexedAt)}\n`,
|
|
215
|
-
);
|
|
216
|
-
process.stdout.write(
|
|
217
|
-
`${chalk.bold("Dirty:")} ${dirty === undefined ? "unknown" : dirty ? "yes" : "no"}\n`,
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
if (project.user?.tags?.length) {
|
|
207
|
+
const { unmount } = render(view, { stdout: process.stdout });
|
|
208
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
209
|
+
unmount();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
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`);
|
|
221
218
|
process.stdout.write(
|
|
222
|
-
`${chalk.bold("
|
|
219
|
+
`${chalk.bold("Git:")} ${hasGit ? "yes" : "no"}\n`,
|
|
220
|
+
);
|
|
221
|
+
process.stdout.write(
|
|
222
|
+
`${chalk.bold("README:")} ${project.hasReadme ? "yes" : "no"}\n\n`,
|
|
223
223
|
);
|
|
224
|
-
}
|
|
225
224
|
|
|
226
|
-
|
|
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
|
+
);
|
|
227
234
|
process.stdout.write(
|
|
228
|
-
`${chalk.bold("
|
|
235
|
+
`${chalk.bold("Last modified:")} ${formatDate(project.auto.lastModifiedAt)}\n`,
|
|
229
236
|
);
|
|
230
|
-
|
|
231
|
-
|
|
237
|
+
process.stdout.write(
|
|
238
|
+
`${chalk.bold("Last indexed:")} ${formatDate(project.auto.lastIndexedAt)}\n`,
|
|
239
|
+
);
|
|
240
|
+
process.stdout.write(
|
|
241
|
+
`${chalk.bold("Dirty:")} ${dirty === undefined ? "unknown" : dirty ? "yes" : "no"}\n`,
|
|
242
|
+
);
|
|
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
|
+
);
|
|
232
257
|
}
|