Skip to content

Commit 9c4ee1c

Browse files
authored
omarchy: detect tdl command conflict and suggest different installation path (#56)
1 parent fa78724 commit 9c4ee1c

15 files changed

Lines changed: 94 additions & 20 deletions

File tree

install.sh

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,5 +360,63 @@ adjust_binary
360360
# compute URL to download
361361
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${NAME}
362362

363+
# check if the existing 'tdl' in PATH is a ThreeDotsLabs binary
364+
is_our_tdl() {
365+
existing_tdl=$(command -v tdl 2>/dev/null) || return 1
366+
strings "$existing_tdl" 2>/dev/null | grep -q "ThreeDotsLabs"
367+
}
368+
369+
# detect naming conflicts with other tools (e.g. omarchy) before installing
370+
detect_conflict() {
371+
HAS_OMARCHY=false
372+
HAS_OTHER_TDL=false
373+
374+
if [ -d "$HOME/.local/share/omarchy" ]; then
375+
HAS_OMARCHY=true
376+
fi
377+
378+
if is_command tdl && ! is_our_tdl; then
379+
HAS_OTHER_TDL=true
380+
fi
381+
382+
if [ "$HAS_OMARCHY" = false ] && [ "$HAS_OTHER_TDL" = false ]; then
383+
return
384+
fi
385+
386+
echo "" >/dev/tty
387+
if [ "$HAS_OMARCHY" = true ]; then
388+
echo "NOTE: Omarchy detected on this system." >/dev/tty
389+
echo " Omarchy provides its own 'tdl' command (Tmux Dev Layout) as a bash function," >/dev/tty
390+
echo " which would shadow the ThreeDotsLabs CLI binary." >/dev/tty
391+
elif [ "$HAS_OTHER_TDL" = true ]; then
392+
existing=$(command -v tdl)
393+
echo "NOTE: Another 'tdl' command was found at: ${existing}" >/dev/tty
394+
echo " Installing the ThreeDotsLabs CLI as 'tdl' would conflict with it." >/dev/tty
395+
fi
396+
397+
echo "" >/dev/tty
398+
echo " Options:" >/dev/tty
399+
echo " 1) Install as 'threedots' instead of 'tdl' (recommended)" >/dev/tty
400+
echo " 2) Install as 'tdl' anyway" >/dev/tty
401+
if [ "$HAS_OMARCHY" = true ]; then
402+
echo "" >/dev/tty
403+
echo " Tip: To disable omarchy's 'tdl' and use the ThreeDotsLabs CLI as 'tdl'," >/dev/tty
404+
echo " add this line to your ~/.bashrc (after omarchy's rc is sourced):" >/dev/tty
405+
echo " unset -f tdl" >/dev/tty
406+
fi
407+
echo "" >/dev/tty
408+
409+
printf " Choose [1/2] (default: 1): " >/dev/tty
410+
read choice </dev/tty 2>/dev/null || choice="1"
411+
case "$choice" in
412+
2) log_info "installing as 'tdl' (user chose to keep the name)" ;;
413+
*) BINARY="threedots"
414+
log_info "installing as 'threedots' to avoid conflict"
415+
;;
416+
esac
417+
}
418+
419+
detect_conflict
420+
363421
# do it
364422
execute

internal/binary_name.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package internal
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
)
7+
8+
func BinaryName() string {
9+
return filepath.Base(os.Args[0])
10+
}

tdl/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ var configureFlags = []cli.Flag{
8484
var tokenDocs = fmt.Sprintf("token from %s", internal.WebsiteAddress)
8585

8686
var app = &cli.App{
87-
Name: "tdl",
87+
Name: internal.BinaryName(),
8888
Usage: "https://threedots.tech/ CLI.",
8989
Compiled: time.Now(),
9090
Copyright: "(c) Three Dots Labs",

trainings/config/global.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func (c Config) GlobalConfig() GlobalConfig {
4141
if !c.dirOrFileExists(c.osFs, configPath) {
4242
panic(errors.Errorf(
4343
"trainings are not configured, please visit %s to get credentials and run %s",
44-
internal.WebsiteAddress, internal.SprintCommand("tdl training configure"),
44+
internal.WebsiteAddress, internal.SprintCommand(internal.BinaryName()+" training configure"),
4545
))
4646
}
4747

trainings/errors.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"github.com/sirupsen/logrus"
99
"google.golang.org/grpc/codes"
1010
"google.golang.org/grpc/status"
11+
12+
"github.com/ThreeDotsLabs/cli/internal"
1113
)
1214

1315
type UserFacingError struct {
@@ -30,7 +32,7 @@ To start fresh, create a new directory and re-initialize:
3032
%s
3133
3234
This will re-download all your existing solutions.`,
33-
color.CyanString("tdl training init %s .", trainingName),
35+
color.CyanString(internal.BinaryName()+" training init %s .", trainingName),
3436
)
3537
}
3638

@@ -53,7 +55,7 @@ func formatServerError(err error) error {
5355
case codes.Unauthenticated:
5456
return UserFacingError{
5557
Msg: "Authentication failed.",
56-
SolutionHint: "Run " + color.CyanString("tdl training configure <token>") + " to set up your token.",
58+
SolutionHint: "Run " + color.CyanString(internal.BinaryName()+" training configure <token>") + " to set up your token.",
5759
}
5860
case codes.ResourceExhausted:
5961
return UserFacingError{

trainings/errors_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func TestRecoveryHint(t *testing.T) {
1818
if !strings.Contains(hint, "mkdir my-training") {
1919
t.Error("expected recovery hint to create new directory")
2020
}
21-
if !strings.Contains(hint, "tdl training init go-event-driven .") {
21+
if !strings.Contains(hint, "training init go-event-driven .") {
2222
t.Error("expected recovery hint to include training name and dot")
2323
}
2424
if !strings.Contains(hint, "re-download all your existing solutions") {
@@ -51,7 +51,7 @@ func TestFormatGitError(t *testing.T) {
5151
if !strings.Contains(msg, "git branch: fatal: branch already exists: exit status 128") {
5252
t.Error("expected error to contain raw git error")
5353
}
54-
if !strings.Contains(msg, "tdl training init go-event-driven .") {
54+
if !strings.Contains(msg, "training init go-event-driven .") {
5555
t.Error("expected error to contain recovery hint with training name")
5656
}
5757
}

trainings/git_config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func (h *Handlers) Settings(opts SettingsOptions) error {
4747
printCurrentSettings(cfg, globalCfg, gitAvailable)
4848
fmt.Println()
4949
fmt.Println("To change settings, use flags:")
50-
fmt.Println(" " + color.CyanString("tdl training settings --auto-commit=on --mcp=off"))
50+
fmt.Println(" " + color.CyanString(internal.BinaryName()+" training settings --auto-commit=on --mcp=off"))
5151
return nil
5252
}
5353

@@ -67,7 +67,7 @@ func (h *Handlers) applySettingsFlags(opts SettingsOptions, cfg *config.Training
6767
gitFlagSet := opts.AutoCommit != nil || opts.AutoSync != nil || opts.SyncMode != nil
6868
if gitFlagSet && !gitAvailable {
6969
fmt.Println("Git integration is not enabled for this training.")
70-
fmt.Println("To enable it, reinitialize with: " + color.CyanString("tdl training init"))
70+
fmt.Println("To enable it, reinitialize with: " + color.CyanString(internal.BinaryName()+" training init"))
7171
return nil
7272
}
7373

trainings/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ func showGitDefaults() {
223223
fmt.Println(" Your work is saved to a backup branch first (never destructive).")
224224
fmt.Println()
225225
fmt.Printf(" Defaults: auto-commit on, auto-sync off.\n")
226-
fmt.Printf(" To change: %s\n\n", color.CyanString("tdl training settings"))
226+
fmt.Printf(" To change: %s\n\n", color.CyanString(internal.BinaryName()+" training settings"))
227227
}
228228

229229
// printGitUnavailableNotice shows a recommendation banner when git is missing or too old.

trainings/mcp/tools.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"github.com/mark3labs/mcp-go/mcp"
1414
"github.com/mark3labs/mcp-go/server"
1515
"github.com/sirupsen/logrus"
16+
17+
"github.com/ThreeDotsLabs/cli/internal"
1618
)
1719

1820
func registerTools(srv *server.MCPServer, state *LoopState) {
@@ -122,7 +124,7 @@ func handleGetExerciseInfo(state *LoopState) server.ToolHandlerFunc {
122124
if updateAvailable, updateVersion, updateNotes := state.GetUpdateAvailable(); updateAvailable {
123125
resp.UpdateAvailable = true
124126
resp.UpdateVersion = updateVersion
125-
resp.UpdateCommand = "tdl update"
127+
resp.UpdateCommand = internal.BinaryName() + " update"
126128
resp.UpdateReleaseNotes = updateNotes
127129
}
128130

@@ -333,7 +335,7 @@ func sendCommand(state *LoopState, cmdType CommandType, successMsg string, respo
333335
if state.ShouldShowUpdateNoticeMCP() {
334336
if updateAvailable, updateVersion, _ := state.GetUpdateAvailable(); updateAvailable {
335337
msg += fmt.Sprintf(
336-
"\n\nNote: a new CLI version (%s) is available. Run `tdl update` in your terminal.",
338+
"\n\nNote: a new CLI version (%s) is available. Run `"+internal.BinaryName()+" update` in your terminal.",
337339
updateVersion,
338340
)
339341
state.MarkUpdateNoticeShownMCP()

trainings/mcp_detect.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ func promptMCPSetup() bool {
9090
fmt.Println()
9191
fmt.Println(" If you plan to use an AI coding tool (Claude Code, Cursor, etc.) alongside")
9292
fmt.Println(" this training, the MCP server lets it run exercises and check results.")
93-
fmt.Println(" It listens on 127.0.0.1 (localhost only) while " + color.CyanString("tdl tr run") + " is active.")
93+
fmt.Println(" It listens on 127.0.0.1 (localhost only) while " + color.CyanString(internal.BinaryName()+" tr run") + " is active.")
9494
fmt.Println()
9595
fmt.Println(" The training works perfectly fine without it.")
9696
fmt.Println(" It will integrate your coding agent with this CLI.")
9797
fmt.Println(color.New(color.Bold).Sprint(" Writing by hand is still the default, and may help the ideas stick."))
98-
fmt.Println(" You can change this later with: " + color.CyanString("tdl training settings"))
98+
fmt.Println(" You can change this later with: " + color.CyanString(internal.BinaryName()+" training settings"))
9999
fmt.Println()
100100

101101
choice := internal.Prompt(

0 commit comments

Comments
 (0)