Skip to content

Commit 2f154cb

Browse files
authored
refactor(sync): move autobuild core under internal (#10)
* refactor(sync): add internal autobuild package * refactor(sync): wire adapters through internal autobuild * refactor(cmd): rename runnable command package * test(architecture): cover internal import boundaries * docs(cli): align dry-run behavior * refactor(domain): move rune page limit error * fix(app): snapshot state before checking LCU status * docs: fix SyncResult field name in changelog * cli: reject positional args for subcommands * docs: refresh auth and usage notes * fix(config): restore top item default
1 parent f4977f1 commit 2f154cb

71 files changed

Lines changed: 3202 additions & 1982 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.goreleaser.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ before:
99

1010
builds:
1111
- id: lol-autobuild
12-
main: ./cmd/dev
12+
main: ./cmd/lol-autobuild
1313
binary: lol-autobuild
1414
env:
1515
- CGO_ENABLED=0

CHANGELOG.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
99
### Added
1010

1111
- Added champion and position autodetection through LCU champ select session reads.
12-
- Added detected selection fields to `SyncResult` (`DetectedChampionID`, `DetectedRole`, `DetectedQueueID`).
12+
- Added detected selection fields to `SyncResult` (`DetectedChampionID`, `DetectedPosition`, `DetectedQueueID`).
1313
- Added item set apply flow with managed set upsert in LCU.
1414
- Added staged item set block apply in LCU with ordered block support.
1515
- Added summoner spell apply flow with Flash slot preservation behavior.
1616
- Added rune page apply flow that reuses an `AutoBuild` page or creates one in LCU.
17-
- Added watch orchestration (`Service.Watch`) and `dev watch` command.
17+
- Added watch orchestration (`Service.Watch`) and `lol-autobuild watch` command.
1818
- Added LCU websocket event stream support with reconnect flow.
1919
- Added browser-assisted Coachless token capture.
2020
- Added persisted `sync` config settings for the local UI.
@@ -30,8 +30,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
3030
- Position detection now supports queue IDs `400`, `420`, `440`, and `3110`.
3131
- Sync item recommendations now build ordered staged blocks (`Starter`, `1st Item`, `2nd Item`, `Boots`, `3rd Item`, `4th+ Item`) with per-block filtering.
3232
- Item filtering uses Coachless' default 1000-occurrence threshold. Blocks remain when filtering would remove all items, and `recommendation.top_items: 0` disables per-block truncation.
33-
- `dev sync` and `dev watch` continue to default to `--dry-run=true`.
34-
- `dev watch` no longer syncs at startup. It syncs once per champ select when the session timer enters `FINALIZATION`.
33+
- `lol-autobuild sync` and `lol-autobuild watch` read `sync.dry_run` when callers omit the flag.
34+
- `lol-autobuild watch` no longer syncs at startup. It syncs once per champ select when the session timer enters `FINALIZATION`.
3535
- Free Coachless users now default to the latest non-Premium patch and cannot request the newest Premium patch.
3636
- The default command now opens the local settings UI.
3737
- Release archives now use the `lol-autobuild` binary name and include README, license, and sample config.

README.br.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Escolha o ZIP do seu sistema, extraia os arquivos e rode `lol-autobuild`.
2424

2525
## Sobre o Coachless
2626

27-
[Coachless](https://coachless.gg/) é um destaque entre os sites de análise para League of Legends. Ele usa Win Probability Added (WPA) para comparar itens com mais contexto que o win rate puro. Jogadores ganham um jeito mais inteligente de avaliar builds. [xPetu](https://x.com/xPetu) lidera o projeto; jogadores conhecem o trabalho dele pelo Shen em alto nível e pelas análises matemáticas sobre o jogo.
27+
[xPetu](https://x.com/xPetu) lidera o [Coachless](https://coachless.gg/), um site de análise para League of Legends. Ele usa Win Probability Added (WPA) para comparar itens com mais contexto que o win rate puro. `lol-autobuild` usa esses dados de runas, itens e spells durante a seleção de campeões.
2828

2929
## Primeiro uso
3030

@@ -56,7 +56,7 @@ Observar a seleção de campeões em modo de preview pela CLI:
5656
lol-autobuild watch --dry-run
5757
```
5858

59-
Comandos da CLI usam dry-run por padrão. Passe `--dry-run=false` para aplicar mudanças no client.
59+
Comandos da CLI leem `sync.dry_run` da configuração. Passe `--dry-run` para simular ou `--dry-run=false` para aplicar mudanças no client.
6060

6161
Comandos avançados, configuração e limites ficam em [USAGE.md](USAGE.md).
6262

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Pick the ZIP for your system, extract it, and run `lol-autobuild`.
2424

2525
## About Coachless
2626

27-
[Coachless](https://coachless.gg/) is a standout League of Legends analytics site. It uses Win Probability Added (WPA) to compare items with more context than raw win rate. Players get a smarter way to judge builds. [xPetu](https://x.com/xPetu) leads the project; players know him for high-level Shen play and math-based League analysis.
27+
[xPetu](https://x.com/xPetu) leads [Coachless](https://coachless.gg/), a League of Legends analytics site. It uses Win Probability Added (WPA) to compare items with more context than raw win rate. `lol-autobuild` uses its rune, item, and summoner spell data during champion select.
2828

2929
## First run
3030

@@ -56,7 +56,7 @@ Watch champion select in CLI preview mode:
5656
lol-autobuild watch --dry-run
5757
```
5858

59-
CLI commands use dry-run by default. Pass `--dry-run=false` to apply changes to the League Client.
59+
CLI commands read `sync.dry_run` from config. Pass `--dry-run` to preview or `--dry-run=false` to apply changes to the League Client.
6060

6161
Advanced commands, config, and limits live in [USAGE.md](USAGE.md).
6262

SECURITY.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ This document covers secret handling, token handling, logging hygiene, and vulne
1515
- Keep lockfile paths and process arguments that include LCU auth material out of shared logs.
1616
- Do not commit tokens, lockfiles, or local debug dumps that include credentials.
1717

18-
## Current auth implementation status
18+
## Auth flow
1919

20-
- Browser-assisted token capture source exists but is not implemented yet.
21-
- Manual fallback source works and should be treated as sensitive input.
20+
- The auth provider reads stored tokens first.
21+
- If a refresh token exists, the provider tries the Coachless refresh flow before asking for a new login.
22+
- Browser-assisted token capture opens Coachless login and stores tokens from the login response when `auth.auto_enabled` is true.
23+
- Manual fallback reads environment variables when `auth.manual_fallback_enabled` is true.
2224
- Token validity checks use configured skew (`auth.token_skew_seconds`) to avoid near-expiry usage.
2325

2426
## Logging and diagnostics rules
@@ -31,7 +33,7 @@ This document covers secret handling, token handling, logging hygiene, and vulne
3133

3234
If you find a security issue:
3335

34-
1. Report it privately to repository maintainers through your internal channel.
36+
1. Report it privately to repository maintainers. Use GitHub private vulnerability reporting if the repo enables it; otherwise use a private maintainer contact.
3537
2. Include impact, attack path, affected components, and reproduction steps.
3638
3. Avoid opening a public issue with exploit details or secrets.
3739
4. Wait for maintainer guidance before broad disclosure.

USAGE.md

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ For the short user guide, read [README.md](README.md). Portuguese version: [READ
1111
- Detects your current champion and position from the local LCU champ select session.
1212
- Pulls Coachless patch, rune, summoner spell, and item stats.
1313
- Builds recommendations.
14-
- Applies supported changes in LCU. Set `--dry-run=true` or `sync.dry_run: true` to preview the plan.
14+
- Applies supported changes in LCU. Preview with `--dry-run` or `sync.dry_run: true`.
1515

1616
## Capability matrix
1717

@@ -102,7 +102,7 @@ Flags:
102102
- `--apply-runes` (default `true`)
103103
- `--apply-spells` (default `true`)
104104
- `--config string` (default `"config.yaml"`)
105-
- `--dry-run` (default `true`)
105+
- `--dry-run` (CLI uses `sync.dry_run` when you omit the flag)
106106
- `--patch string` (empty = latest patch from Coachless)
107107

108108
### `lol-autobuild watch`
@@ -119,12 +119,12 @@ Flags:
119119
- `--apply-runes` (default `true`)
120120
- `--apply-spells` (default `true`)
121121
- `--config string` (default `"config.yaml"`)
122-
- `--dry-run` (default `true`)
122+
- `--dry-run` (CLI uses `sync.dry_run` when you omit the flag)
123123
- `--patch string` (empty = latest patch from Coachless)
124124

125125
`watch` waits for champ select finalization before it syncs. It does not run a sync cycle at startup.
126126

127-
CLI `sync` and `watch` default to `--dry-run=true`. Pass `--dry-run=false` to apply LCU changes from the CLI.
127+
CLI `sync` and `watch` use `sync.dry_run` when you omit `--dry-run`. Pass `--dry-run` to preview or `--dry-run=false` to apply LCU changes from the CLI.
128128

129129
## Config reference
130130

@@ -141,7 +141,7 @@ CLI `sync` and `watch` default to `--dry-run=true`. Pass `--dry-run=false` to ap
141141
| `env_file.path` | string | `""` | Optional path to `.env` file loaded before bootstrap. |
142142
| `secrets.service_name` | string | `lol-autobuild` | OS keyring service name. |
143143
| `recommendation.min_occurrence` | int | `1000` | Minimum occurrence count for recommendation candidates. |
144-
| `recommendation.top_items` | int | `6` | Max recommended item count per block. `0` disables the limit. |
144+
| `recommendation.top_items` | int | `10` | Max recommended item count per block. `0` disables the limit. |
145145
| `recommendation.top_spells` | int | `2` | Max recommended spell count. |
146146
| `lcu.enabled` | bool | `false` | Enables LCU detection and apply paths. |
147147
| `lcu.lockfile_path` | string | `""` | Optional lockfile fallback path. |
@@ -170,12 +170,7 @@ LCU connection discovery tries League process args first (`--app-port`, `--remot
170170
- Watch mode only reacts to champ select session `Create` and `Update` events from `/lol-champ-select/v1/session` when `data.timer.phase == "FINALIZATION"`.
171171
- Watch mode attempts one sync per champ select. A session `Delete` or a new non-finalized `Create` event resets that lock.
172172
- If the finalization sync fails, watch mode waits for the next champ select before it tries again.
173-
- The local UI uses the `sync` config section. CLI flags still control `sync` and `watch`.
173+
- The local UI and CLI read the `sync` config section. CLI flags override patch, apply, and dry-run fields.
174174
- Free Coachless tokens use the latest non-Premium patch when the patch setting is blank. Requesting the newest Premium patch or manual patch additions returns an error.
175175
- Rune page apply reuses a deletable `AutoBuild` page or creates one without deleting user pages. If replacing a managed page fails after delete, the app attempts to restore that page and reports the failure context.
176176
- Browser-assisted auth capture watches the Coachless login response and stores the token pair.
177-
178-
## Next work
179-
180-
- Expand queue coverage for position detection.
181-
- Add clearer diagnostics around auth and LCU failures.

cmd/dev/main_test.go

Lines changed: 0 additions & 42 deletions
This file was deleted.

cmd/lol-autobuild/app_adapters.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"sync"
7+
"time"
8+
9+
"github.com/controlado/lol-autobuild/internal/app"
10+
"github.com/controlado/lol-autobuild/internal/auth"
11+
"github.com/controlado/lol-autobuild/internal/config"
12+
"github.com/controlado/lol-autobuild/internal/lcu"
13+
"github.com/controlado/lol-autobuild/internal/update"
14+
)
15+
16+
type configSaver interface {
17+
Save(config.Config) error
18+
}
19+
20+
type appConfigStore struct {
21+
mu sync.Mutex
22+
store configSaver
23+
cfg config.Config
24+
}
25+
26+
func newAppConfigStore(store configSaver, cfg config.Config) *appConfigStore {
27+
return &appConfigStore{
28+
store: store,
29+
cfg: cfg,
30+
}
31+
}
32+
33+
func (s *appConfigStore) Save(newCfg app.RuntimeConfig) error {
34+
s.mu.Lock()
35+
defer s.mu.Unlock()
36+
37+
next := configFromRuntimeConfig(s.cfg, newCfg)
38+
if err := s.store.Save(next); err != nil {
39+
return err
40+
}
41+
42+
s.cfg = next
43+
return nil
44+
}
45+
46+
func (s *appConfigStore) configFor(appCfg app.RuntimeConfig) config.Config {
47+
s.mu.Lock()
48+
defer s.mu.Unlock()
49+
50+
return configFromRuntimeConfig(s.cfg, appCfg)
51+
}
52+
53+
func configFromRuntimeConfig(base config.Config, appCfg app.RuntimeConfig) config.Config {
54+
base.Sync = config.SyncConfig{
55+
Patch: appCfg.Settings.Patch,
56+
PatchAdditionsMode: appCfg.Settings.PatchAdditionsMode,
57+
PatchAdditions: appCfg.Settings.PatchAdditions,
58+
LeagueTierPreset: appCfg.Settings.LeagueTierPreset,
59+
ApplyItems: appCfg.Settings.ApplyItems,
60+
ApplyRunes: appCfg.Settings.ApplyRunes,
61+
ApplySpells: appCfg.Settings.ApplySpells,
62+
KeepFlash: appCfg.Settings.KeepFlash,
63+
DryRun: appCfg.Settings.DryRun,
64+
}
65+
base.LCU.Enabled = appCfg.Settings.LCUEnabled
66+
base.Watch.DebounceMillis = int(appCfg.WatchDebounce / time.Millisecond)
67+
return base
68+
}
69+
70+
func runtimeConfigFromConfig(cfg config.Config) app.RuntimeConfig {
71+
return app.RuntimeConfig{
72+
Settings: app.Settings{
73+
Patch: cfg.Sync.Patch,
74+
PatchAdditionsMode: cfg.Sync.PatchAdditionsMode,
75+
PatchAdditions: cfg.Sync.PatchAdditions,
76+
LeagueTierPreset: cfg.Sync.LeagueTierPreset,
77+
ApplyItems: cfg.Sync.ApplyItems,
78+
ApplyRunes: cfg.Sync.ApplyRunes,
79+
ApplySpells: cfg.Sync.ApplySpells,
80+
KeepFlash: cfg.Sync.KeepFlash,
81+
DryRun: cfg.Sync.DryRun,
82+
LCUEnabled: cfg.LCU.Enabled,
83+
},
84+
WatchDebounce: time.Duration(cfg.Watch.DebounceMillis) * time.Millisecond,
85+
}
86+
}
87+
88+
func appLCUStatusFromLCU(status lcu.ConnectionStatus) app.LCUStatus {
89+
return app.LCUStatus{
90+
State: appLCUConnectionStateFromLCU(status.State),
91+
Message: status.Message,
92+
Source: status.Source,
93+
}
94+
}
95+
96+
func appLCUConnectionStateFromLCU(state lcu.ConnectionState) app.LCUConnectionState {
97+
switch state {
98+
case lcu.ConnectionStateOff:
99+
return app.LCUConnectionStateOff
100+
case lcu.ConnectionStateConnected:
101+
return app.LCUConnectionStateConnected
102+
default:
103+
return app.LCUConnectionStateNotConnected
104+
}
105+
}
106+
107+
type updateSource interface {
108+
CurrentVersion() string
109+
Check(context.Context) (update.Result, error)
110+
}
111+
112+
type appUpdateChecker struct {
113+
source updateSource
114+
}
115+
116+
func (c appUpdateChecker) CurrentVersion() string {
117+
return c.source.CurrentVersion()
118+
}
119+
120+
func (c appUpdateChecker) Check(ctx context.Context) (app.UpdateCheckResult, error) {
121+
result, err := c.source.Check(ctx)
122+
out := app.UpdateCheckResult{
123+
CurrentVersion: result.CurrentVersion,
124+
LatestVersion: result.LatestVersion,
125+
DownloadURL: result.DownloadURL,
126+
Available: result.Available,
127+
}
128+
if errors.Is(err, update.ErrUnavailable) {
129+
return out, app.ErrUpdateUnavailable
130+
}
131+
132+
return out, err
133+
}
134+
135+
func appMessageFromErr(err error) app.UserMessage {
136+
switch {
137+
case err == nil:
138+
return app.UserMessage{}
139+
case errors.Is(err, lcu.ErrNotConfigured):
140+
return app.UserMessage{Code: app.MessageCodeLCUOff, Text: "LCU is off."}
141+
case errors.Is(err, lcu.ErrLockfileNotFound):
142+
return app.UserMessage{Code: app.MessageCodeLCULockfileNotFound, Text: "League Client is not open."}
143+
case errors.Is(err, lcu.ErrChampSelectUnavailable):
144+
return app.UserMessage{Code: app.MessageCodeLCUChampSelectUnavailable, Text: "Champ select is not ready."}
145+
case errors.Is(err, lcu.ErrChampionNotSelected):
146+
return app.UserMessage{Code: app.MessageCodeLCUChampionNotSelected, Text: "Select a champion first."}
147+
case errors.Is(err, auth.ErrAccessTokenUnavailable):
148+
return app.UserMessage{Code: app.MessageCodeCoachlessLoginMissing, Text: "Coachless login is missing."}
149+
default:
150+
return app.UserMessage{Text: err.Error()}
151+
}
152+
}

0 commit comments

Comments
 (0)