Skip to content

Commit 730e7cf

Browse files
committed
perf(init): already-installed fast-paths + progress lines + timeouts for caveman/ponytail wiring
1 parent ab65bee commit 730e7cf

4 files changed

Lines changed: 102 additions & 19 deletions

File tree

internal/commands/init.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,12 @@ func RunInit(opts InitOptions) int {
181181
wireBar.Begin(agent.Label)
182182
var failed []string
183183
wireOut, _ := util.CaptureLogs(func() error {
184-
for _, tool := range tools {
184+
for ti, tool := range tools {
185185
fn, ok := tool.WireFor[agentID]
186186
if !ok {
187187
continue
188188
}
189+
wireBar.Step("installing "+tool.Label, float64(ti+1)/float64(len(tools)))
189190
if tool.NeedsGit && !gitOK {
190191
util.L.Err(tool.Label + " needs git — https://git-scm.com/downloads")
191192
failed = append(failed, tool.Label)

internal/tools/caveman.go

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package tools
22

33
import (
4+
"context"
45
"os"
56
"path/filepath"
67
"strings"
8+
"time"
79

810
"github.com/HoangP8/tokless/internal/core"
911
"github.com/HoangP8/tokless/internal/util"
@@ -17,7 +19,9 @@ func cavemanExec(bin string, args []string, opts core.RunOpts, dryHint string, e
1719
if isTest() {
1820
return true, nil
1921
}
20-
r := util.Run(bin, args, util.RunOptions{Capture: true, Env: env})
22+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
23+
defer cancel()
24+
r := util.Run(bin, args, util.RunOptions{Capture: true, Env: env, Ctx: ctx})
2125
if r.Code != 0 {
2226
util.L.Err("caveman command failed: " + clip(r.Stderr))
2327
return false, nil
@@ -33,6 +37,32 @@ func cavemanOpencodeInstallEnv() []string {
3337
return []string{"XDG_CONFIG_HOME=" + filepath.Dir(dir)}
3438
}
3539

40+
// resolveSkillsBin returns ("skills", args[1:]) if skills binary is available,
41+
// otherwise ("npx", original npx args).
42+
func resolveSkillsBin(npxArgs []string) (string, []string) {
43+
if util.Which("skills") != "" {
44+
return "skills", npxArgs[2:] // strip "-y skills" prefix
45+
}
46+
return "npx", npxArgs
47+
}
48+
49+
// resolveCavemanBin returns ("caveman", cavemanArgs) if global caveman binary
50+
// is available, else ("npx", npxArgs).
51+
func resolveCavemanBin(agent string, upgrade bool) (string, []string) {
52+
if util.Which("caveman") != "" {
53+
args := []string{"--only", agent, "--no-mcp-shrink"}
54+
if upgrade {
55+
args = append(args, "--force")
56+
}
57+
return "caveman", args
58+
}
59+
args := []string{"-y", "github:JuliusBrussee/caveman", "--", "--only", agent, "--no-mcp-shrink"}
60+
if upgrade {
61+
args = append(args, "--force")
62+
}
63+
return "npx", args
64+
}
65+
3666
func ensureOpencodeCommandsDir() {
3767
_ = os.MkdirAll(filepath.Join(util.OpenCodePathsResolved().Dir, "commands"), 0o755)
3868
}
@@ -305,7 +335,18 @@ var caveman = &core.ToolManifest{
305335
Channel: core.ChannelGitHub,
306336
NotTrackable: true,
307337
Install: func(opts core.RunOpts) (bool, error) {
308-
opts.Reportf("installed per agent", 1)
338+
if !opts.DryRun && !isTest() && !opts.Upgrade && util.Which("caveman") != "" {
339+
opts.Reportf("already installed", 1)
340+
return true, nil
341+
}
342+
opts.Reportf("installing from GitHub", 0.3)
343+
if !opts.DryRun && !isTest() && util.Which("npm") != "" {
344+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
345+
_ = util.Run("npm", []string{"install", "-g", "github:JuliusBrussee/caveman"},
346+
util.RunOptions{Capture: true, Ctx: ctx})
347+
cancel()
348+
}
349+
opts.Reportf("ready", 1)
309350
return true, nil
310351
},
311352
WireFor: map[string]core.AgentFn{
@@ -328,19 +369,16 @@ var caveman = &core.ToolManifest{
328369
return ran, err
329370
},
330371
"opencode": func(opts core.RunOpts) (bool, error) {
372+
if !opts.Upgrade && opencodePluginInstalled() && opencodePluginFilesPresent() {
373+
WriteOwner("opencode", "caveman")
374+
return true, nil
375+
}
331376
if !opts.DryRun && !isTest() {
332377
ensureOpencodeCommandsDir()
333378
}
334-
args := []string{"-y", "github:JuliusBrussee/caveman", "--", "--only", "opencode", "--no-mcp-shrink"}
335-
if opts.Upgrade {
336-
args = append(args, "--force")
337-
}
338-
ran, err := cavemanExec("npx", args, opts, "npx -y github:JuliusBrussee/caveman -- --only opencode --no-mcp-shrink"+func() string {
339-
if opts.Upgrade {
340-
return " --force"
341-
}
342-
return ""
343-
}(), cavemanOpencodeInstallEnv()...)
379+
bin, args := resolveCavemanBin("opencode", opts.Upgrade)
380+
ran, err := cavemanExec(bin, args, opts, bin+" "+strings.Join(args, " "),
381+
cavemanOpencodeInstallEnv()...)
344382
WriteOwner("opencode", "caveman")
345383
if opts.DryRun || isTest() {
346384
return ran, err
@@ -353,8 +391,12 @@ var caveman = &core.ToolManifest{
353391
return opencodePluginInstalled(), err
354392
},
355393
"codex": func(opts core.RunOpts) (bool, error) {
356-
args := cavemanSkillsAddArgs("codex")
357-
ran, err := cavemanExec("npx", args, opts, "npx "+strings.Join(args, ""))
394+
if !opts.Upgrade && codexCavemanInstalled() {
395+
WriteOwner("codex", "caveman")
396+
return true, nil
397+
}
398+
bin, args := resolveSkillsBin(cavemanSkillsAddArgs("codex"))
399+
ran, err := cavemanExec(bin, args, opts, bin+" "+strings.Join(args, " "))
358400
WriteOwner("codex", "caveman")
359401
if opts.DryRun || isTest() {
360402
return ran, err
@@ -364,8 +406,12 @@ var caveman = &core.ToolManifest{
364406
return codexCavemanInstalled(), err
365407
},
366408
"antigravity": func(opts core.RunOpts) (bool, error) {
367-
args := cavemanSkillsAddArgs("antigravity")
368-
ran, err := cavemanExec("npx", args, opts, "npx "+strings.Join(args, " "))
409+
if !opts.Upgrade && antigravityCavemanInstalled() {
410+
WriteOwner("antigravity", "caveman")
411+
return true, nil
412+
}
413+
bin, args := resolveSkillsBin(cavemanSkillsAddArgs("antigravity"))
414+
ran, err := cavemanExec(bin, args, opts, bin+" "+strings.Join(args, " "))
369415
WriteOwner("antigravity", "caveman")
370416
if opts.DryRun || isTest() {
371417
return ran, err

internal/tools/ponytail.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package tools
22

33
import (
4+
"context"
45
"os"
56
"path/filepath"
67
"strings"
8+
"time"
79

810
"github.com/HoangP8/tokless/internal/core"
911
"github.com/HoangP8/tokless/internal/util"
@@ -20,7 +22,9 @@ func ponytailExec(bin string, args []string, opts core.RunOpts, dryHint string,
2022
if isTest() {
2123
return true, nil
2224
}
23-
r := util.Run(bin, args, util.RunOptions{Capture: true, Env: env})
25+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
26+
defer cancel()
27+
r := util.Run(bin, args, util.RunOptions{Capture: true, Env: env, Ctx: ctx})
2428
if r.Code != 0 {
2529
util.L.Err("ponytail command failed: " + clip(r.Stderr))
2630
return false, nil
@@ -207,6 +211,10 @@ func claudePluginListHasPonytail() bool {
207211

208212
// ponytailWireClaude installs the Claude plugin. AGENTS.md block is the floor.
209213
func ponytailWireClaude(opts core.RunOpts) (bool, error) {
214+
if !opts.Upgrade && claudePonytailInstalled() {
215+
WriteOwner("claude", "ponytail")
216+
return true, nil
217+
}
210218
if !opts.DryRun && !isTest() && util.Which("claude") == "" {
211219
util.L.Err("ponytail needs the claude CLI (`claude plugin …`); Claude Desktop alone is not enough — install the CLI and re-run")
212220
return false, nil
@@ -255,8 +263,14 @@ func ponytailWireOpencode(opts core.RunOpts) (bool, error) {
255263
WriteOwner("opencode", "ponytail")
256264
return ponytailOpencodeInstalled(), nil
257265
}
266+
if !opts.Upgrade && ponytailOpencodeInstalled() && ponytailOpencodeFilesPresent() {
267+
WriteOwner("opencode", "ponytail")
268+
return true, nil
269+
}
258270
if util.Which("npm") != "" {
259-
_ = util.Run("npm", []string{"install", "-g", ponytailOpencodePkg}, util.RunOptions{Capture: true})
271+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
272+
defer cancel()
273+
_ = util.Run("npm", []string{"install", "-g", ponytailOpencodePkg}, util.RunOptions{Capture: true, Ctx: ctx})
260274
}
261275
registerPonytailOpencode()
262276
stampPonytailVersion()
@@ -295,6 +309,10 @@ func removePonytailCodexSkillCopies() {
295309
// ponytailWireCodex adds the marketplace when possible and writes the baseline.
296310
// Final plugin install/trust is interactive in Codex (/plugins + /hooks).
297311
func ponytailWireCodex(opts core.RunOpts) (bool, error) {
312+
if !opts.Upgrade && codexPonytailInstalled() {
313+
WriteOwner("codex", "ponytail")
314+
return true, nil
315+
}
298316
if opts.DryRun {
299317
util.L.Sub("[dry-run] would run: codex plugin marketplace add " + ponytailRepo + "; then write AGENTS.md baseline; finish in /plugins + /hooks")
300318
WriteOwner("codex", "ponytail")
@@ -321,6 +339,10 @@ func ponytailUnwireCodex(opts core.RunOpts) (bool, error) {
321339

322340
// ponytailWireAntigravity installs the Antigravity extension.
323341
func ponytailWireAntigravity(opts core.RunOpts) (bool, error) {
342+
if !opts.Upgrade && antigravityPonytailInstalled() {
343+
WriteOwner("antigravity", "ponytail")
344+
return true, nil
345+
}
324346
if opts.DryRun {
325347
util.L.Sub("[dry-run] would run: agy plugin install https://github.com/" + ponytailRepo)
326348
WriteOwner("antigravity", "ponytail")

internal/util/progress.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type Progress struct {
3131
out *os.File
3232
treeStyle bool
3333
rows int
34+
lastNonTTY string
3435
}
3536

3637
func NewProgress(title string) *Progress {
@@ -89,6 +90,7 @@ func (p *Progress) Begin(label string) {
8990
p.current = label
9091
p.phase = ""
9192
p.frac = 0
93+
p.lastNonTTY = ""
9294
p.start = time.Now()
9395
p.active = true
9496
if p.tty {
@@ -117,7 +119,19 @@ func (p *Progress) Step(phase string, frac float64) {
117119
p.frac = frac
118120
if p.tty {
119121
p.repaint()
122+
p.mu.Unlock()
123+
return
124+
}
125+
if phase == "" || phase == p.lastNonTTY {
126+
p.mu.Unlock()
127+
return
128+
}
129+
p.lastNonTTY = phase
130+
label := p.current
131+
if label == "" {
132+
label = p.title
120133
}
134+
fmt.Fprintf(p.out, " %s %s\n", C.Dim(pick("·", ".")), label+" — "+phase)
121135
p.mu.Unlock()
122136
}
123137

0 commit comments

Comments
 (0)