btca 0.2.0 → 0.2.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
@@ -1,15 +1,137 @@
1
- # cli
1
+ # btca
2
2
 
3
- To install dependencies:
3
+ A CLI tool for asking questions about technologies using their source code repositories.
4
+
5
+ ## Installation
4
6
 
5
7
  ```bash
6
8
  bun install
7
9
  ```
8
10
 
9
- To run:
11
+ ## Usage
12
+
13
+ ```bash
14
+ bun run src/index.ts
15
+ ```
16
+
17
+ Or after building:
18
+
19
+ ```bash
20
+ btca <command>
21
+ ```
22
+
23
+ ## Commands
24
+
25
+ ### `btca`
26
+
27
+ Show version information.
28
+
29
+ ### `btca ask`
30
+
31
+ Ask a question about a technology.
32
+
33
+ ```bash
34
+ btca ask -t <tech> -q <question>
35
+ btca ask --tech svelte --question "How do I create a reactive store?"
36
+ ```
37
+
38
+ Options:
39
+ - `-t, --tech` - The technology/repo to query
40
+ - `-q, --question` - The question to ask
41
+
42
+ ### `btca chat`
43
+
44
+ Start an interactive TUI chat session.
45
+
46
+ ```bash
47
+ btca chat -t <tech>
48
+ btca chat --tech nextjs
49
+ ```
50
+
51
+ Options:
52
+ - `-t, --tech` - The technology/repo to chat about
53
+
54
+ ### `btca serve`
55
+
56
+ Start an HTTP server to answer questions via API.
57
+
58
+ ```bash
59
+ btca serve
60
+ btca serve -p 3000
61
+ ```
62
+
63
+ Options:
64
+ - `-p, --port` - Port to listen on (default: 8080)
65
+
66
+ Endpoint:
67
+ - `POST /question` - Send `{ "tech": "svelte", "question": "..." }` to get answers
68
+
69
+ ### `btca open`
70
+
71
+ Hold an OpenCode instance in the background for faster subsequent queries.
72
+
73
+ ```bash
74
+ btca open
75
+ ```
76
+
77
+ ### `btca config`
78
+
79
+ Manage CLI configuration. Shows the config file path when run without subcommands.
10
80
 
11
81
  ```bash
12
- bun run index.ts
82
+ btca config
13
83
  ```
14
84
 
15
- This project was created using `bun init` in bun v1.3.3. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
85
+ #### `btca config model`
86
+
87
+ View or set the model and provider.
88
+
89
+ ```bash
90
+ # View current model/provider
91
+ btca config model
92
+
93
+ # Set model and provider
94
+ btca config model -p <provider> -m <model>
95
+ btca config model --provider anthropic --model claude-3-opus
96
+ ```
97
+
98
+ Options:
99
+ - `-p, --provider` - The provider to use
100
+ - `-m, --model` - The model to use
101
+
102
+ Both options must be specified together when updating.
103
+
104
+ #### `btca config repos list`
105
+
106
+ List all configured repositories.
107
+
108
+ ```bash
109
+ btca config repos list
110
+ ```
111
+
112
+ #### `btca config repos add`
113
+
114
+ Add a new repository to the configuration.
115
+
116
+ ```bash
117
+ btca config repos add -n <name> -u <url> [-b <branch>] [--notes <notes>]
118
+ btca config repos add --name react --url https://github.com/facebook/react --branch main
119
+ ```
120
+
121
+ Options:
122
+ - `-n, --name` - Unique name for the repo (required)
123
+ - `-u, --url` - Git repository URL (required)
124
+ - `-b, --branch` - Branch to use (default: "main")
125
+ - `--notes` - Special instructions for the AI when using this repo
126
+
127
+ ## Configuration
128
+
129
+ Configuration is stored at `~/.config/btca/btca.json`. The config file includes:
130
+
131
+ - `promptsDirectory` - Directory for system prompts
132
+ - `reposDirectory` - Directory where repos are cloned
133
+ - `port` - Default server port
134
+ - `maxInstances` - Maximum concurrent OpenCode instances
135
+ - `repos` - Array of configured repositories
136
+ - `model` - AI model to use
137
+ - `provider` - AI provider to use
Binary file
Binary file
Binary file
Binary file
package/dist/index.js CHANGED
@@ -60472,9 +60472,10 @@ var DEFAULT_CONFIG = {
60472
60472
  specialNotes: "This is the svelte docs website repo, not the actual svelte repo. Use the docs to answer questions about svelte."
60473
60473
  },
60474
60474
  {
60475
- name: "effect",
60476
- url: "https://github.com/Effect-TS/effect",
60477
- branch: "main"
60475
+ name: "tailwindcss",
60476
+ url: "https://github.com/tailwindlabs/tailwindcss.com",
60477
+ branch: "main",
60478
+ specialNotes: "This is the tailwindcss docs website repo, not the actual tailwindcss repo. Use the docs to answer questions about tailwindcss."
60478
60479
  },
60479
60480
  {
60480
60481
  name: "nextjs",
@@ -60485,6 +60486,29 @@ var DEFAULT_CONFIG = {
60485
60486
  model: "big-pickle",
60486
60487
  provider: "opencode"
60487
60488
  };
60489
+ var collapseHome = (path2) => {
60490
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
60491
+ if (home && path2.startsWith(home)) {
60492
+ return "~" + path2.slice(home.length);
60493
+ }
60494
+ return path2;
60495
+ };
60496
+ var writeConfig = (config2) => exports_Effect.gen(function* () {
60497
+ const path2 = yield* exports_Path.Path;
60498
+ const fs = yield* exports_FileSystem.FileSystem;
60499
+ const configDir = yield* expandHome(CONFIG_DIRECTORY);
60500
+ const configPath = path2.join(configDir, CONFIG_FILENAME);
60501
+ const configToWrite = {
60502
+ ...config2,
60503
+ promptsDirectory: collapseHome(config2.promptsDirectory),
60504
+ reposDirectory: collapseHome(config2.reposDirectory)
60505
+ };
60506
+ yield* fs.writeFileString(configPath, JSON.stringify(configToWrite, null, 2)).pipe(exports_Effect.catchAll((error4) => exports_Effect.fail(new ConfigError({
60507
+ message: "Failed to write config",
60508
+ cause: error4
60509
+ }))));
60510
+ return configToWrite;
60511
+ });
60488
60512
  var OPENCODE_CONFIG = (args2) => exports_Effect.gen(function* () {
60489
60513
  const path2 = yield* exports_Path.Path;
60490
60514
  return {
@@ -60583,7 +60607,7 @@ var onStartLoadConfig = exports_Effect.gen(function* () {
60583
60607
  var configService = exports_Effect.gen(function* () {
60584
60608
  const path2 = yield* exports_Path.Path;
60585
60609
  const loadedConfig = yield* onStartLoadConfig;
60586
- const { config: config2, configPath } = loadedConfig;
60610
+ let { config: config2, configPath } = loadedConfig;
60587
60611
  const getRepo = ({
60588
60612
  repoName,
60589
60613
  config: config3
@@ -60619,7 +60643,23 @@ var configService = exports_Effect.gen(function* () {
60619
60643
  specialNotes: repo?.specialNotes
60620
60644
  });
60621
60645
  }),
60622
- rawConfig: () => exports_Effect.succeed(config2)
60646
+ rawConfig: () => exports_Effect.succeed(config2),
60647
+ getRepos: () => exports_Effect.succeed(config2.repos),
60648
+ getModel: () => exports_Effect.succeed({ provider: config2.provider, model: config2.model }),
60649
+ updateModel: (args2) => exports_Effect.gen(function* () {
60650
+ config2 = { ...config2, provider: args2.provider, model: args2.model };
60651
+ yield* writeConfig(config2);
60652
+ return { provider: config2.provider, model: config2.model };
60653
+ }),
60654
+ addRepo: (repo) => exports_Effect.gen(function* () {
60655
+ const existing = config2.repos.find((r) => r.name === repo.name);
60656
+ if (existing) {
60657
+ return yield* exports_Effect.fail(new ConfigError({ message: `Repo "${repo.name}" already exists` }));
60658
+ }
60659
+ config2 = { ...config2, repos: [...config2.repos, repo] };
60660
+ yield* writeConfig(config2);
60661
+ return repo;
60662
+ })
60623
60663
  };
60624
60664
  });
60625
60665
 
@@ -60800,7 +60840,7 @@ class OcService extends exports_Effect.Service()("OcService", {
60800
60840
  }
60801
60841
 
60802
60842
  // src/services/cli.ts
60803
- var VERSION = "0.2.0";
60843
+ var VERSION = "0.2.1";
60804
60844
  var programLayer = exports_Layer.mergeAll(OcService.Default, ConfigService.Default);
60805
60845
  var questionOption = exports_Options.text("question").pipe(exports_Options.withAlias("q"));
60806
60846
  var techOption = exports_Options.text("tech").pipe(exports_Options.withAlias("t"));
@@ -60892,11 +60932,93 @@ var serveCommand = exports_Command2.make("serve", { port: portOption }, ({ port:
60892
60932
  const HttpLive = router.pipe(exports_HttpServer.serve(), exports_HttpServer.withLogAddress, exports_Layer.provide(ServerLive));
60893
60933
  return yield* exports_Layer.launch(HttpLive);
60894
60934
  }).pipe(exports_Effect.scoped, exports_Effect.provide(programLayer)));
60895
- var configPathCommand = exports_Command2.make("config", {}, () => exports_Effect.gen(function* () {
60935
+ var providerOption = exports_Options.text("provider").pipe(exports_Options.withAlias("p"), exports_Options.optional);
60936
+ var modelOption = exports_Options.text("model").pipe(exports_Options.withAlias("m"), exports_Options.optional);
60937
+ var configModelCommand = exports_Command2.make("model", { provider: providerOption, model: modelOption }, ({ provider, model }) => exports_Effect.gen(function* () {
60896
60938
  const config2 = yield* ConfigService;
60897
- const configPath = yield* config2.getConfigPath();
60898
- console.log(`Update your config file at ${configPath}`);
60939
+ if (provider._tag === "Some" && model._tag === "Some") {
60940
+ const result = yield* config2.updateModel({
60941
+ provider: provider.value,
60942
+ model: model.value
60943
+ });
60944
+ console.log(`Updated model configuration:`);
60945
+ console.log(` Provider: ${result.provider}`);
60946
+ console.log(` Model: ${result.model}`);
60947
+ } else if (provider._tag === "Some" || model._tag === "Some") {
60948
+ console.error("Error: Both --provider and --model must be specified together");
60949
+ process.exit(1);
60950
+ } else {
60951
+ const current2 = yield* config2.getModel();
60952
+ console.log(`Current model configuration:`);
60953
+ console.log(` Provider: ${current2.provider}`);
60954
+ console.log(` Model: ${current2.model}`);
60955
+ }
60956
+ }).pipe(exports_Effect.provide(programLayer)));
60957
+ var configReposListCommand = exports_Command2.make("list", {}, () => exports_Effect.gen(function* () {
60958
+ const config2 = yield* ConfigService;
60959
+ const repos = yield* config2.getRepos();
60960
+ if (repos.length === 0) {
60961
+ console.log("No repos configured.");
60962
+ return;
60963
+ }
60964
+ console.log(`Configured repos:
60965
+ `);
60966
+ for (const repo of repos) {
60967
+ console.log(` ${repo.name}`);
60968
+ console.log(` URL: ${repo.url}`);
60969
+ console.log(` Branch: ${repo.branch}`);
60970
+ if (repo.specialNotes) {
60971
+ console.log(` Notes: ${repo.specialNotes}`);
60972
+ }
60973
+ console.log();
60974
+ }
60899
60975
  }).pipe(exports_Effect.provide(programLayer)));
60976
+ var repoNameOption = exports_Options.text("name").pipe(exports_Options.withAlias("n"));
60977
+ var repoUrlOption = exports_Options.text("url").pipe(exports_Options.withAlias("u"));
60978
+ var repoBranchOption = exports_Options.text("branch").pipe(exports_Options.withAlias("b"), exports_Options.withDefault("main"));
60979
+ var repoNotesOption = exports_Options.text("notes").pipe(exports_Options.optional);
60980
+ var configReposAddCommand = exports_Command2.make("add", {
60981
+ name: repoNameOption,
60982
+ url: repoUrlOption,
60983
+ branch: repoBranchOption,
60984
+ notes: repoNotesOption
60985
+ }, ({ name, url: url2, branch, notes }) => exports_Effect.gen(function* () {
60986
+ const config2 = yield* ConfigService;
60987
+ const repo = {
60988
+ name,
60989
+ url: url2,
60990
+ branch,
60991
+ ...notes._tag === "Some" ? { specialNotes: notes.value } : {}
60992
+ };
60993
+ yield* config2.addRepo(repo);
60994
+ console.log(`Added repo "${name}":`);
60995
+ console.log(` URL: ${url2}`);
60996
+ console.log(` Branch: ${branch}`);
60997
+ if (notes._tag === "Some") {
60998
+ console.log(` Notes: ${notes.value}`);
60999
+ }
61000
+ }).pipe(exports_Effect.catchTag("ConfigError", (e) => exports_Effect.sync(() => {
61001
+ console.error(`Error: ${e.message}`);
61002
+ process.exit(1);
61003
+ })), exports_Effect.provide(programLayer)));
61004
+ var configReposCommand = exports_Command2.make("repos", {}, () => exports_Effect.sync(() => {
61005
+ console.log("Usage: btca config repos <command>");
61006
+ console.log("");
61007
+ console.log("Commands:");
61008
+ console.log(" list List all configured repos");
61009
+ console.log(" add Add a new repo");
61010
+ })).pipe(exports_Command2.withSubcommands([configReposListCommand, configReposAddCommand]));
61011
+ var configCommand = exports_Command2.make("config", {}, () => exports_Effect.gen(function* () {
61012
+ const config2 = yield* ConfigService;
61013
+ const configPath = yield* config2.getConfigPath();
61014
+ console.log(`Config file: ${configPath}`);
61015
+ console.log("");
61016
+ console.log("Usage: btca config <command>");
61017
+ console.log("");
61018
+ console.log("Commands:");
61019
+ console.log(" model View or set the model and provider");
61020
+ console.log(" repos Manage configured repos");
61021
+ }).pipe(exports_Effect.provide(programLayer))).pipe(exports_Command2.withSubcommands([configModelCommand, configReposCommand]));
60900
61022
  var mainCommand = exports_Command2.make("btca", {}, () => exports_Effect.sync(() => {
60901
61023
  console.log(`btca v${VERSION}. run btca --help for more information.`);
60902
61024
  })).pipe(exports_Command2.withSubcommands([
@@ -60904,7 +61026,7 @@ var mainCommand = exports_Command2.make("btca", {}, () => exports_Effect.sync(()
60904
61026
  serveCommand,
60905
61027
  openCommand,
60906
61028
  chatCommand,
60907
- configPathCommand
61029
+ configCommand
60908
61030
  ]));
60909
61031
  var cliService = exports_Effect.gen(function* () {
60910
61032
  return {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "btca",
3
3
  "author": "Ben Davis",
4
- "version": "0.2.0",
4
+ "version": "0.2.1",
5
5
  "description": "CLI tool for asking questions about technologies using OpenCode",
6
6
  "type": "module",
7
7
  "license": "MIT",