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 CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  Keep your house in order. Explore and jump between local projects.
4
4
 
5
- **bet** is a lightweight project index for your machine: point it at one or more “root” folders (like `~/code`), let it scan for projects, then use fast commands to **search**, **inspect**, and **jump** to them.
5
+ **bet** is a lightweight project index for your machine: point it at one or more “root” folders (like `~/code`), let it scan for projects, then use fast commands to **search**, **inspect**, and **jump** to them — for you _and_ for the AI coding agents working inside your repos.
6
6
 
7
- If your `~/code` folder is chaos, **bet turns it into a map**.
7
+ If your `~/code` folder is chaos, **bet turns it into a map**. The same map an agent can read in a handful of tokens instead of burning a context window on a sprawling `find` dump.
8
8
 
9
9
  ```
10
10
 
@@ -33,6 +33,7 @@ If your `~/code` folder is chaos, **bet turns it into a map**.
33
33
  - **No guessing paths**
34
34
  - **No brittle aliases**
35
35
  - Just indexed homes you can find instantly
36
+ - **Context-efficient for AI agents** — `bet path X` resolves a project name to a path in a handful of tokens; `bet list --json | jq` answers questions across hundreds of projects without spending tokens on a full `find` tree
36
37
 
37
38
  “bet” (𐤁) is the Phoenician letter for **house**. Every project is a house—bet builds your registry of houses.
38
39
 
@@ -42,6 +43,7 @@ If your `~/code` folder is chaos, **bet turns it into a map**.
42
43
  - `bet update` scans those roots and builds an index.
43
44
  - Projects are detected using simple signals (today: folders containing **`.git`** and/or a **`README.md`**, with common build dirs ignored).
44
45
  - Commands like `list`, `search`, `info`, `path`, and `go` read that index.
46
+ - Commands like `list`, `search`, `info`, `path`, `go`, and `edit` read that index.
45
47
 
46
48
  ## Install
47
49
 
@@ -123,6 +125,9 @@ Then Tab-complete the slug argument after `bet go `, `bet path `, or `bet info `
123
125
  - **`bet go <slug>`**: Jump to a project.
124
126
  - **`--print`**: print selected path only (no shell `cd`)
125
127
  - **`--no-enter`**: do not run the project’s `onEnter` hook (if configured)
128
+ - **`bet edit <slug>`**: Open a project in your editor.
129
+ - Uses `editor` from `config.json` when set.
130
+ - Falls back to the system default app opener when `editor` is not set.
126
131
  - **`bet shell`**: Print the shell integration snippet (see above).
127
132
  - **`bet completion [bash|zsh]`**: Print shell completion script for project name autocompletion (see above).
128
133
 
@@ -132,6 +137,7 @@ bet stores its data in:
132
137
 
133
138
  - **Config dir**: `~/.config/bet/` (or `$XDG_CONFIG_HOME/bet/`)
134
139
  - **Roots**: `config.json` — each root is `{ "path": "/absolute/path", "name": "display-name" }`. The name defaults to the top folder name and is used when listing/grouping projects.
140
+ - **editor** (optional): In `config.json`, a command string used by `bet edit`, for example `"code -n"` or `"cursor"`.
135
141
  - **slugParentFolders** (optional): In `config.json`, an array of folder names. When a discovered project path ends in one of these (e.g. `src` or `app`), the project slug is taken from the parent directory name instead. Default in code is `["src", "app"]` when the key is not set.
136
142
  - **Project index**: `projects.json`
137
143
 
@@ -163,6 +169,22 @@ You can combine `bet list --json` with [jq](https://stedolan.github.io/jq/) for
163
169
 
164
170
  You can customize these jq expressions to target any field present in the project index for fully custom workflows.
165
171
 
172
+ ## Use with AI agents
173
+
174
+ bet ships an agent skill at [`skills/bet/SKILL.md`](skills/bet/SKILL.md) that teaches an LLM-driven coding agent (Claude Code, Cursor, etc.) how to drive the CLI from natural-language requests like _"jump to my payments project"_, _"open the api repo in my editor"_, or _"which of my projects have uncommitted changes?"_.
175
+
176
+ The skill codifies:
177
+
178
+ - The **intent → command map** (`path`, `info`, `list`, `search`, `edit`, `update`, `ignore`)
179
+ - The **mandatory `--plain` / `--json` flags** so the interactive TUI does not hang the agent
180
+ - The **`cd "$(bet path X)"` pattern** for non-interactive shells, since `bet go` relies on shell integration the agent cannot load
181
+ - The **`bet list --json` schema** and ready-made `jq` recipes for dirty / recently modified / stale / grouped queries
182
+ - **Slug rules** (including `slugParentFolders`) and common pitfalls
183
+
184
+ To use it with Claude Code, drop the `skills/bet/` folder into a discovered skills directory (e.g. `~/.claude/skills/bet/`) — or import the skill into whichever harness your agent uses. Once loaded, `/bet` invokes it explicitly, and natural-language triggers in the description load it automatically.
185
+
186
+ This means an agent can navigate hundreds of projects on your machine using a few-line skill instead of spending tokens on directory listings, ad-hoc shell aliases, or hard-coded paths in its prompt.
187
+
166
188
  ### Development setup (contributors)
167
189
 
168
190
  To work on bet locally (without necessarily linking it globally), install deps and build:
@@ -0,0 +1,48 @@
1
+ import { readConfig } from "../lib/config.js";
2
+ import { openProjectInEditor } from "../lib/editor.js";
3
+ import { findBySlug, listProjects, projectLabel } from "../lib/projects.js";
4
+ import { promptSelect } from "../ui/prompt.js";
5
+ export function registerEdit(program) {
6
+ program
7
+ .command("edit <slug>")
8
+ .description("Open a project in your editor")
9
+ .action(async (slug) => {
10
+ try {
11
+ const config = await readConfig();
12
+ const projects = listProjects(config);
13
+ const matches = findBySlug(projects, slug);
14
+ if (matches.length === 0) {
15
+ process.stderr.write(`No project found for slug "${slug}".\n`);
16
+ process.exitCode = 1;
17
+ return;
18
+ }
19
+ let project = matches[0];
20
+ if (matches.length > 1) {
21
+ if (!process.stdin.isTTY) {
22
+ process.stderr.write(`Slug "${slug}" is ambiguous. Matches:\n`);
23
+ for (const item of matches) {
24
+ process.stderr.write(` ${projectLabel(item)} ${item.path}\n`);
25
+ }
26
+ process.exitCode = 1;
27
+ return;
28
+ }
29
+ const items = matches.map((item) => ({
30
+ label: projectLabel(item),
31
+ hint: item.path,
32
+ value: item,
33
+ type: "item",
34
+ }));
35
+ const selected = await promptSelect(items, { title: `Select ${slug}` });
36
+ if (!selected)
37
+ return;
38
+ project = selected.value;
39
+ }
40
+ await openProjectInEditor(project.path, config.editor);
41
+ }
42
+ catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ process.stderr.write(`Error: ${message}\n`);
45
+ process.exitCode = 1;
46
+ }
47
+ });
48
+ }
@@ -40,7 +40,9 @@ export function registerInfo(program) {
40
40
  value: item,
41
41
  type: "item",
42
42
  }));
43
- const selected = await promptSelect(items, { title: `Select ${slug}` });
43
+ const selected = await promptSelect(items, {
44
+ title: `Select ${slug}`,
45
+ });
44
46
  if (!selected)
45
47
  return;
46
48
  project = selected.value;
@@ -59,7 +61,7 @@ export function registerInfo(program) {
59
61
  ? await readReadmeContent(project.path, { full: true })
60
62
  : null;
61
63
  const markdown = readme ?? description;
62
- const view = (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsxs(Box, { width: "100%", borderStyle: "single", borderColor: "green", paddingX: 1, paddingY: 1, marginBottom: 1, children: [_jsx(Text, { color: "green", bold: true, children: project.slug }), _jsx(Text, { color: "cyan", children: project.path })] }), _jsxs(Box, { borderStyle: "round", borderColor: "cyan", padding: 1, flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "magenta", children: "Details" }) }), _jsxs(Box, { flexDirection: "column", children: [_jsx(MetaRow, { label: "Root", value: project.rootName }), _jsx(MetaRow, { label: "Root path", value: project.root }), _jsx(MetaRow, { label: "Git", value: hasGit ? "yes" : "no", valueColor: hasGit ? "green" : "yellow" }), _jsx(MetaRow, { label: "README", value: project.hasReadme ? "yes" : "no", valueColor: project.hasReadme ? "green" : "yellow" }), _jsx(MetaRow, { label: "Started", value: formatDate(project.auto.startedAt) }), _jsx(MetaRow, { label: "Last modified", value: formatDate(project.auto.lastModifiedAt) }), _jsx(MetaRow, { label: "Last indexed", value: formatDate(project.auto.lastIndexedAt) }), _jsx(MetaRow, { label: "Dirty", value: dirty === undefined ? "unknown" : dirty ? "yes" : "no", valueColor: dirty === undefined ? "yellow" : dirty ? "red" : "green" }), project.user?.tags?.length ? (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "gray", children: `Tags: ` }), _jsx(Text, { color: "magenta", children: project.user.tags.join(", ") })] })) : null, project.user?.onEnter ? (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "gray", children: `On enter: ` }), _jsx(Text, { color: "blue", children: project.user.onEnter })] })) : null] })] }), _jsxs(Box, { borderStyle: "round", borderColor: "magenta", padding: 1, flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "magenta", children: "Description" }) }), _jsx(Markdown, { children: markdown })] })] }));
64
+ const view = (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsxs(Box, { width: "100%", borderStyle: "single", borderColor: "green", paddingX: 1, paddingY: 1, marginBottom: 1, flexDirection: "column", children: [_jsx(Box, { width: "100%", paddingBottom: 1, children: _jsx(Text, { color: "green", bold: true, children: project.slug }) }), _jsx(Box, { width: "100%", children: _jsx(Text, { color: "cyan", children: project.path }) })] }), _jsxs(Box, { borderStyle: "round", borderColor: "cyan", padding: 1, flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "magenta", children: "Details" }) }), _jsxs(Box, { flexDirection: "column", children: [_jsx(MetaRow, { label: "Git", value: hasGit ? "yes" : "no", valueColor: hasGit ? "green" : "yellow" }), _jsx(MetaRow, { label: "Git dirty", value: dirty === undefined ? "unknown" : dirty ? "yes" : "no", valueColor: dirty === undefined ? "yellow" : dirty ? "red" : "green" }), _jsx(MetaRow, { label: "README", value: project.hasReadme ? "yes" : "no", valueColor: project.hasReadme ? "green" : "yellow" }), _jsx(MetaRow, { label: "Started", value: formatDate(project.auto.startedAt) }), _jsx(MetaRow, { label: "Last modified", value: formatDate(project.auto.lastModifiedAt) }), _jsx(MetaRow, { label: "Last indexed", value: formatDate(project.auto.lastIndexedAt) }), _jsx(MetaRow, { label: "Root", value: project.rootName }), _jsx(MetaRow, { label: "Root path", value: project.root }), project.user?.tags?.length ? (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "gray", children: `Tags: ` }), _jsx(Text, { color: "magenta", children: project.user.tags.join(", ") })] })) : null, project.user?.onEnter ? (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "gray", children: `On enter: ` }), _jsx(Text, { color: "blue", children: project.user.onEnter })] })) : null] })] }), _jsxs(Box, { borderStyle: "round", borderColor: "magenta", padding: 1, flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "magenta", children: "Description" }) }), _jsx(Markdown, { children: markdown })] }), !options.full && project.hasReadme ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["Tip: Run ", _jsxs(Text, { bold: true, children: ["bet info ", project.slug, " --full"] }), " to read the full README."] }) })) : null] }));
63
65
  const { unmount } = render(view, { stdout: process.stdout });
64
66
  await new Promise((resolve) => setTimeout(resolve, 0));
65
67
  unmount();
@@ -72,7 +74,8 @@ export function registerInfo(program) {
72
74
  process.stdout.write(`${chalk.bold("Git:")} ${hasGit ? "yes" : "no"}\n`);
73
75
  process.stdout.write(`${chalk.bold("README:")} ${project.hasReadme ? "yes" : "no"}\n\n`);
74
76
  const descToShow = options.full && project.hasReadme
75
- ? (await readReadmeContent(project.path, { full: true })) ?? description
77
+ ? ((await readReadmeContent(project.path, { full: true })) ??
78
+ description)
76
79
  : description;
77
80
  process.stdout.write(`${chalk.bold("Description:")} ${descToShow}\n`);
78
81
  process.stdout.write(`${chalk.bold("Started:")} ${formatDate(project.auto.startedAt)}\n`);
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import readline from "node:readline";
3
3
  import { readConfig, resolveRoots, writeConfig } from "../lib/config.js";
4
4
  import { normalizeAbsolute } from "../utils/paths.js";
5
- import { installUpdateCron, uninstallUpdateCron, parseCronSchedule, formatScheduleLabel } from "../lib/cron.js";
5
+ import { installUpdateCron, uninstallUpdateCron, parseCronSchedule, formatScheduleLabel, } from "../lib/cron.js";
6
6
  import { scanRoots } from "../lib/scan.js";
7
7
  import { computeMetadata } from "../lib/metadata.js";
8
8
  import { getEffectiveIgnores, isPathIgnored } from "../lib/ignore.js";
@@ -23,8 +23,7 @@ function pathsToRootConfigs(paths) {
23
23
  });
24
24
  }
25
25
  export function willOverrideRoots(providedRootConfigs, configRoots) {
26
- return !!(providedRootConfigs !== undefined &&
27
- configRoots.length > 0);
26
+ return !!(providedRootConfigs !== undefined && configRoots.length > 0);
28
27
  }
29
28
  const DEFAULT_SLUG_PARENT_FOLDERS = ["src", "app"];
30
29
  export { DEFAULT_SLUG_PARENT_FOLDERS };
@@ -36,7 +35,10 @@ export function projectSlug(pathName, slugParentFolders) {
36
35
  return folderName;
37
36
  }
38
37
  async function promptYesNo(question, defaultNo = true) {
39
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
38
+ const rl = readline.createInterface({
39
+ input: process.stdin,
40
+ output: process.stderr,
41
+ });
40
42
  return new Promise((resolve) => {
41
43
  const defaultHint = defaultNo ? "y/N" : "Y/n";
42
44
  rl.question(question + " [" + defaultHint + "] ", (answer) => {
@@ -136,8 +138,12 @@ export function registerUpdate(program) {
136
138
  projects,
137
139
  updatedAt: new Date().toISOString(),
138
140
  ...(config.ignores !== undefined && { ignores: config.ignores }),
139
- ...(config.ignoredPaths !== undefined && { ignoredPaths: config.ignoredPaths }),
140
- ...(config.slugParentFolders !== undefined && { slugParentFolders: config.slugParentFolders }),
141
+ ...(config.ignoredPaths !== undefined && {
142
+ ignoredPaths: config.ignoredPaths,
143
+ }),
144
+ ...(config.slugParentFolders !== undefined && {
145
+ slugParentFolders: config.slugParentFolders,
146
+ }),
141
147
  };
142
148
  await writeConfig(nextConfig);
143
149
  const projectCount = Object.keys(projects).length;
@@ -60,11 +60,18 @@ function normalizeIgnoredPaths(parsed) {
60
60
  const list = parsed.filter((x) => typeof x === "string").map((x) => normalizeAbsolute(x));
61
61
  return list.length === 0 ? undefined : list;
62
62
  }
63
+ function normalizeEditor(parsed) {
64
+ if (typeof parsed !== "string")
65
+ return undefined;
66
+ const trimmed = parsed.trim();
67
+ return trimmed.length === 0 ? undefined : trimmed;
68
+ }
63
69
  async function readAppConfig() {
64
70
  try {
65
71
  const raw = await fs.readFile(CONFIG_PATH, "utf8");
66
72
  const parsed = JSON.parse(raw);
67
73
  const roots = normalizeRoots(parsed.roots ?? []);
74
+ const editor = normalizeEditor(parsed.editor);
68
75
  const ignores = normalizeIgnores(parsed.ignores);
69
76
  const ignoredPaths = normalizeIgnoredPaths(parsed.ignoredPaths);
70
77
  const slugParentFolders = normalizeSlugParentFolders(parsed.slugParentFolders);
@@ -72,6 +79,7 @@ async function readAppConfig() {
72
79
  ...DEFAULT_APP_CONFIG,
73
80
  version: parsed.version ?? 1,
74
81
  roots,
82
+ ...(editor !== undefined && { editor }),
75
83
  ...(ignores !== undefined && { ignores }),
76
84
  ...(ignoredPaths !== undefined && { ignoredPaths }),
77
85
  ...(slugParentFolders !== undefined && { slugParentFolders }),
@@ -134,6 +142,7 @@ export async function writeConfig(config) {
134
142
  const appConfig = {
135
143
  version: config.version,
136
144
  roots: config.roots,
145
+ ...(config.editor !== undefined && { editor: config.editor }),
137
146
  ...(config.ignores !== undefined && { ignores: config.ignores }),
138
147
  ...(config.ignoredPaths !== undefined && { ignoredPaths: config.ignoredPaths }),
139
148
  ...(config.slugParentFolders !== undefined && { slugParentFolders: config.slugParentFolders }),
@@ -0,0 +1,97 @@
1
+ import { spawn } from "node:child_process";
2
+ function tokenizeCommand(input) {
3
+ const tokens = [];
4
+ let current = "";
5
+ let inSingle = false;
6
+ let inDouble = false;
7
+ let escaped = false;
8
+ for (const char of input) {
9
+ if (escaped) {
10
+ current += char;
11
+ escaped = false;
12
+ continue;
13
+ }
14
+ if (char === "\\") {
15
+ escaped = true;
16
+ continue;
17
+ }
18
+ if (char === "'" && !inDouble) {
19
+ inSingle = !inSingle;
20
+ continue;
21
+ }
22
+ if (char === '"' && !inSingle) {
23
+ inDouble = !inDouble;
24
+ continue;
25
+ }
26
+ if (!inSingle && !inDouble && /\s/.test(char)) {
27
+ if (current.length > 0) {
28
+ tokens.push(current);
29
+ current = "";
30
+ }
31
+ continue;
32
+ }
33
+ current += char;
34
+ }
35
+ if (escaped || inSingle || inDouble) {
36
+ throw new Error("Invalid editor command in config.");
37
+ }
38
+ if (current.length > 0) {
39
+ tokens.push(current);
40
+ }
41
+ return tokens;
42
+ }
43
+ export function parseEditorCommand(editor) {
44
+ const trimmed = editor.trim();
45
+ if (!trimmed) {
46
+ throw new Error("Config editor must not be empty.");
47
+ }
48
+ const tokens = tokenizeCommand(trimmed);
49
+ if (tokens.length === 0) {
50
+ throw new Error("Config editor must not be empty.");
51
+ }
52
+ const [command, ...args] = tokens;
53
+ return { command, args };
54
+ }
55
+ export function getSystemOpenCommand(targetPath, platform = process.platform) {
56
+ if (platform === "darwin") {
57
+ return { command: "open", args: [targetPath] };
58
+ }
59
+ if (platform === "win32") {
60
+ return { command: "cmd", args: ["/c", "start", "", targetPath] };
61
+ }
62
+ return { command: "xdg-open", args: [targetPath] };
63
+ }
64
+ function getEnvEditor(env) {
65
+ const visual = env.VISUAL?.trim();
66
+ if (visual)
67
+ return visual;
68
+ const editor = env.EDITOR?.trim();
69
+ if (editor)
70
+ return editor;
71
+ return undefined;
72
+ }
73
+ function spawnDetached(command, args) {
74
+ return new Promise((resolve, reject) => {
75
+ const child = spawn(command, args, {
76
+ detached: true,
77
+ stdio: "ignore",
78
+ });
79
+ child.once("error", (error) => {
80
+ reject(error);
81
+ });
82
+ child.once("spawn", () => {
83
+ child.unref();
84
+ resolve();
85
+ });
86
+ });
87
+ }
88
+ export async function openProjectInEditor(projectPath, configuredEditor, env = process.env) {
89
+ const preferredEditor = configuredEditor?.trim() || getEnvEditor(env);
90
+ if (preferredEditor) {
91
+ const parsed = parseEditorCommand(preferredEditor);
92
+ await spawnDetached(parsed.command, [...parsed.args, projectPath]);
93
+ return;
94
+ }
95
+ const fallback = getSystemOpenCommand(projectPath);
96
+ await spawnDetached(fallback.command, fallback.args);
97
+ }
package/dist/lib/git.js CHANGED
@@ -1,10 +1,10 @@
1
- import { execFile } from 'node:child_process';
2
- import { promisify } from 'node:util';
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
3
  const execFileAsync = promisify(execFile);
4
4
  async function runGit(cwd, args) {
5
5
  try {
6
- const { stdout } = await execFileAsync('git', ['-C', cwd, ...args], {
7
- encoding: 'utf8',
6
+ const { stdout } = await execFileAsync("git", ["-C", cwd, ...args], {
7
+ encoding: "utf8",
8
8
  });
9
9
  return stdout.trim();
10
10
  }
@@ -13,16 +13,22 @@ async function runGit(cwd, args) {
13
13
  }
14
14
  }
15
15
  export async function getFirstCommitDate(cwd) {
16
- const output = await runGit(cwd, ['log', '--reverse', '--format=%cI', '-n', '1']);
16
+ const output = await runGit(cwd, [
17
+ "log",
18
+ "--max-parents=0",
19
+ "--format=%cd",
20
+ "--date=iso-strict",
21
+ "HEAD",
22
+ ]);
17
23
  return output || undefined;
18
24
  }
19
25
  export async function getDirtyStatus(cwd) {
20
- const output = await runGit(cwd, ['status', '--porcelain']);
26
+ const output = await runGit(cwd, ["status", "--porcelain"]);
21
27
  if (output === null)
22
28
  return undefined;
23
29
  return output.length > 0;
24
30
  }
25
31
  export async function isInsideGitRepo(cwd) {
26
- const output = await runGit(cwd, ['rev-parse', '--is-inside-work-tree']);
27
- return output === 'true';
32
+ const output = await runGit(cwd, ["rev-parse", "--is-inside-work-tree"]);
33
+ return output === "true";
28
34
  }
package/dist/lib/help.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Help } from "commander";
2
- const GROUP_1 = ["list", "search", "info", "go", "path"];
2
+ const GROUP_1 = ["list", "search", "info", "go", "edit", "path"];
3
3
  const GROUP_2 = ["shell", "completion"];
4
4
  const GROUP_3 = ["update", "ignore", "help"];
5
5
  const GROUPS = [
@@ -40,12 +40,16 @@ export class GroupedHelp extends Help {
40
40
  output.push(helper.wrap(commandDescription, helpWidth, 0), "");
41
41
  }
42
42
  // Arguments
43
- const argumentList = helper.visibleArguments(cmd).map((argument) => formatItem(helper.argumentTerm(argument), helper.argumentDescription(argument)));
43
+ const argumentList = helper
44
+ .visibleArguments(cmd)
45
+ .map((argument) => formatItem(helper.argumentTerm(argument), helper.argumentDescription(argument)));
44
46
  if (argumentList.length > 0) {
45
47
  output.push("Arguments:", formatList(argumentList), "");
46
48
  }
47
49
  // Options
48
- const optionList = helper.visibleOptions(cmd).map((option) => formatItem(helper.optionTerm(option), helper.optionDescription(option)));
50
+ const optionList = helper
51
+ .visibleOptions(cmd)
52
+ .map((option) => formatItem(helper.optionTerm(option), helper.optionDescription(option)));
49
53
  if (optionList.length > 0) {
50
54
  output.push("Options:", formatList(optionList), "");
51
55
  }
@@ -76,6 +80,14 @@ export class GroupedHelp extends Help {
76
80
  output.push(`${heading}:`, formatList(commandList), "");
77
81
  }
78
82
  }
83
+ output.push("AI agent skill:", formatList([
84
+ "bet ships an agent skill that teaches AI coding agents (Claude Code,",
85
+ "Cursor, etc.) how to drive this CLI from natural-language requests.",
86
+ "Install it to supercharge your agent — drop the skills/bet/ folder",
87
+ "into your harness's skills directory (e.g. ~/.claude/skills/bet/).",
88
+ "",
89
+ " https://github.com/kenzic/bet-cli/tree/main/skills/bet",
90
+ ]), "");
79
91
  return output.join("\n");
80
92
  }
81
93
  }
package/dist/main.js CHANGED
@@ -9,23 +9,24 @@ import { registerPath } from "./commands/path.js";
9
9
  import { registerShell } from "./commands/shell.js";
10
10
  import { registerCompletion } from "./commands/completion.js";
11
11
  import { registerIgnore } from "./commands/ignore.js";
12
+ import { registerEdit } from "./commands/edit.js";
12
13
  const ASCII_HEADER = `
13
14
  ░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░
14
15
  ░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░
15
- ░░░░░░░░░░░░░ ░░░░░░ ░░░░░░
16
- ░░░░░░░░░░░░░░░░░ ░░░░░░ ░░░░░░
17
- ░░░░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░░░
18
- ░░░░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░░░
19
- ░░░░░░░░░░░░ ░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
20
- ░░░░░░░░░░░░░░░░ ░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
21
- ░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
22
- ░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
23
- ░░░░░░░░░ ░░░░░░ ░░░░░░
24
- ░░░░░ ░░░░░░ ░░░░░░
25
- ░░░░░ ░░░░░░ ░░░░░░
26
- ░░░░░ ░░░░░░ ░░░░░░
27
- ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
28
- ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
16
+ ░░░░░░░░░░░░░ ░░░░░░ ░░░░░░
17
+ ░░░░░░░░░░░░░░░░░ ░░░░░░ ░░░░░░
18
+ ░░░░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░░░
19
+ ░░░░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░░░
20
+ ░░░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░░░
21
+ ░░░░░░░░░░░░░░░░ ░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
22
+ ░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
23
+ ░░░░░░░░░░░░░░ ░░░░░░ ░░░░░░
24
+ ░░░░░░░░░ ░░░░░░ ░░░░░░
25
+ ░░░░░ ░░░░░░ ░░░░░░
26
+ ░░░░░ ░░░░░░ ░░░░░░
27
+ ░░░░░ ░░░░░░ ░░░░░░
28
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
29
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
29
30
  `;
30
31
  const program = new Command();
31
32
  program.createHelp = function createHelp() {
@@ -34,12 +35,13 @@ program.createHelp = function createHelp() {
34
35
  program
35
36
  .name("bet")
36
37
  .description("Explore and jump between local projects.")
37
- .version("0.2.0");
38
+ .version("0.3.0");
38
39
  registerUpdate(program);
39
40
  registerList(program);
40
41
  registerSearch(program);
41
42
  registerInfo(program);
42
43
  registerGo(program);
44
+ registerEdit(program);
43
45
  registerPath(program);
44
46
  registerShell(program);
45
47
  registerCompletion(program);