agent: an agent's own provider named as one of magpie's isn't magpie's, in every agent that spells a model provider/model (the rest of #835) — OpenHanako's own deepseek/deepseek-v4-pro read as magpie's deepseek, and so did a model of any agent's own provider whose name magpie has a provider by: Pi's, Crush's, Hermes's, OpenCode's relay/glm-4.6 with magpie's relay had the row connected on the agent's own model, and Disconnect from magpie put that model back and left the row connected. An agent says how magpie's values are spelled in it (Spelled, replacing #835's Prefixed): magpie/<ref> for Antigravity CLI, Pi, OmO, Aside, Goose, Zed, VS Code, Crush, DeepSeek Harness, Command Code, fx, omp, Hermes, Mister Morph, Kimi Code, Empryo, MiniMax Code, Droid, Qoder, Qoder CN, Grok Build, AtomCode, Cline and OpenHanako, whose every Get spells magpie's so (a sandbox Set of magpie/relay/glm-4.6 read back as that in each); for OpenCode, MiMo Code and OpenChamber also a provider of the config's own that sends to magpie's gateway, as openCodeRef names a model there. A stopped WSL distro's agent keeps its live one's. Left as they are: Claude Code, Claude Desktop, Codex, Gemini CLI, Cursor, Copilot, Devin (magpie's are bare refs there), Muse Code (a model with / is magpie's by design: Meta's have none), Alma (its own are providerId:model), Air (magpie's only). TestOwnProviderNamedAsMagpies (26 agents: own relay/glm-4.6 not connected, Connect, Disconnect puts it back and the row is off) fails on the old code in each; TestOpenCodeGatewayProviderIsMagpies. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
This commit is contained in:
1 parent
b590edb5ef
commit
165fee0df5
25 files changed
+131
-32
No files matched your search
@@ -176,12 +176,13 @@ type Agent struct {
|
||||
// names, never taken for one of magpie's models nor moved by what
|
||||
// matches the picker (RenameRefs moves the names in it).
|
||||
SplitSuffix func(v string) (model, suffix string, one bool)
|
||||
// Prefixed, when set, is what each of magpie's values in the agent's
|
||||
// fields starts with ("magpie/", its provider there): a value without
|
||||
// it is a model of one of the agent's own providers, never magpie's,
|
||||
// even when magpie has a provider of the same name (OpenHanako's own
|
||||
// deepseek/deepseek-v4-pro, #835).
|
||||
Prefixed string
|
||||
// Spelled, when set, says whether a value of the agent's fields is
|
||||
// spelled as one of magpie's there (prefixed: it starts with
|
||||
// "magpie/", its provider in the agent): one that isn't is a model of
|
||||
// one of the agent's own providers, never magpie's, even when magpie
|
||||
// has a provider of the same name (OpenHanako's own
|
||||
// deepseek/deepseek-v4-pro read as magpie's deepseek, #835).
|
||||
Spelled func(v string) bool
|
||||
// detect, when set, says whether the agent is here in place of looking
|
||||
// for its files and binary: a distro's, probed once.
|
||||
detect func() bool
|
||||
|
||||
@@ -135,6 +135,30 @@ func usesMagpie(vals ...string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// prefixed is an agent's Spelled whose values of magpie's are all
|
||||
// magpie/<ref>, as its provider/model names them.
|
||||
func prefixed(v string) bool { return strings.HasPrefix(v, magpieID+"/") }
|
||||
|
||||
// openCodeSpelled is the Spelled of an OpenCode config at path: magpie's
|
||||
// provider, or one of the file's own that sends to magpie's gateway (v1),
|
||||
// as openCodeRef names a model there.
|
||||
func openCodeSpelled(path string, v1 func() string) func(string) bool {
|
||||
return func(v string) bool {
|
||||
if prefixed(v) {
|
||||
return true
|
||||
}
|
||||
p, ref, ok := strings.Cut(v, "/")
|
||||
if !ok || p == "" {
|
||||
return false
|
||||
}
|
||||
if !strings.Contains(p, ".") {
|
||||
base, _ := edit.GetJSON(path, "provider."+p+".options.baseURL")
|
||||
return sameGateway(base, v1())
|
||||
}
|
||||
return ownGatewayProvider(path, ref, v1()) == p
|
||||
}
|
||||
}
|
||||
|
||||
// pair joins a provider field and a model field into one "provider/model"
|
||||
// value, which is how OpenCode already spells it and how people think of it.
|
||||
func pairGet(get func(string) (string, bool), pKey, mKey string) func() string {
|
||||
@@ -581,7 +605,7 @@ func openCodeLike(at place, id, name, icon, bin, dir, auth string, ua []string,
|
||||
}
|
||||
}
|
||||
return &Agent{
|
||||
ID: id, Name: name, Icon: icon, Aliases: aliases,
|
||||
ID: id, Name: name, Icon: icon, Aliases: aliases, Spelled: openCodeSpelled(path, at.v1),
|
||||
UA: ua,
|
||||
Bin: bin, Dir: dir, Path: path,
|
||||
Check: func() string {
|
||||
@@ -772,7 +796,7 @@ func piLike(at place, id, name, dir string) *Agent {
|
||||
return edit.SetJSON(modelsPath, edit.KV{Path: "providers." + magpieID, Value: magpieProviderJSONAt("pi", id, at.gw())})
|
||||
}
|
||||
return &Agent{
|
||||
ID: id, Name: name, Icon: id, Bin: id, Dir: dir, Path: path,
|
||||
ID: id, Name: name, Icon: id, Bin: id, Dir: dir, Path: path, Spelled: prefixed,
|
||||
Check: func() string {
|
||||
if p, _ := get("defaultProvider"); p != magpieID {
|
||||
return ""
|
||||
@@ -866,7 +890,7 @@ func goose(home, cfg string) *Agent {
|
||||
set := func(kvs ...edit.KV) error { return edit.SetYAMLTop(path, kvs...) }
|
||||
provider := gooseProviderPath(path)
|
||||
return &Agent{
|
||||
ID: "goose", Name: "Goose", Icon: "goose", Bin: "goose", Dir: filepath.Dir(path), Path: path,
|
||||
ID: "goose", Name: "Goose", Icon: "goose", Bin: "goose", Dir: filepath.Dir(path), Path: path, Spelled: prefixed,
|
||||
UA: []string{"goose"},
|
||||
Check: func() string {
|
||||
if p, _ := get("GOOSE_PROVIDER"); p != gooseProviderID {
|
||||
@@ -1125,7 +1149,7 @@ func crushAt(at place, path, data string) *Agent {
|
||||
}
|
||||
}
|
||||
return &Agent{
|
||||
ID: "crush", Name: "Crush", Icon: "crush", Bin: "crush", Dir: filepath.Dir(path), Path: path,
|
||||
ID: "crush", Name: "Crush", Icon: "crush", Bin: "crush", Dir: filepath.Dir(path), Path: path, Spelled: prefixed,
|
||||
UA: []string{"crush"},
|
||||
Check: func() string {
|
||||
large, _ := pick("models.large.provider")
|
||||
|
||||
@@ -98,7 +98,7 @@ func agy(home string) *Agent {
|
||||
return edit.SetJSON(path, edit.KV{Path: key, Value: v})
|
||||
}
|
||||
return &Agent{
|
||||
ID: "agy", Name: "Antigravity CLI", Icon: "antigravity-color", Aliases: []string{"antigravity-cli"},
|
||||
ID: "agy", Name: "Antigravity CLI", Icon: "antigravity-color", Aliases: []string{"antigravity-cli"}, Spelled: prefixed,
|
||||
Bin: "agy", Dir: dir, Path: path,
|
||||
Launch: func() string {
|
||||
if v := get(); usesMagpie(v) {
|
||||
|
||||
@@ -218,7 +218,7 @@ func magpieValue(a *Agent, f Field, v string, vals map[string]string) bool {
|
||||
return false
|
||||
}
|
||||
v, _, one := a.split(v)
|
||||
if !one || a.Prefixed != "" && !strings.HasPrefix(v, a.Prefixed) {
|
||||
if !one || a.Spelled != nil && !a.Spelled(v) {
|
||||
return false
|
||||
}
|
||||
if isMagpie(v) {
|
||||
|
||||
@@ -61,7 +61,7 @@ func asideIn(at place) *Agent {
|
||||
return nil
|
||||
}
|
||||
a := &Agent{
|
||||
ID: "aside", Name: "Aside", Icon: "aside", Bin: "aside", Path: path,
|
||||
ID: "aside", Name: "Aside", Icon: "aside", Bin: "aside", Path: path, Spelled: prefixed,
|
||||
// ~/.aside, the folder the installer makes, and not the account's:
|
||||
// ~/.aside/u/0 only appears once someone has signed in, so an Aside
|
||||
// that is installed and not yet used is not detected by it, and the
|
||||
|
||||
@@ -108,7 +108,7 @@ func atomcode(home string) *Agent {
|
||||
return edit.SetTOMLTablesMatching(path, atomcodeAccountNames, atomcodeTablePrefixes, nil)
|
||||
}
|
||||
return atomic(&Agent{
|
||||
ID: "atomcode", Name: "AtomCode", Icon: "atomcode",
|
||||
ID: "atomcode", Name: "AtomCode", Icon: "atomcode", Spelled: prefixed,
|
||||
UA: []string{"atomcode"},
|
||||
Bin: "atomcode", Dir: dir, Path: path,
|
||||
Sync: func() error {
|
||||
|
||||
@@ -157,7 +157,7 @@ func cline(home string) *Agent {
|
||||
return "cline"
|
||||
}
|
||||
return &Agent{
|
||||
ID: "cline", Name: "Cline", Icon: "cline", Aliases: []string{"cline-cli"},
|
||||
ID: "cline", Name: "Cline", Icon: "cline", Aliases: []string{"cline-cli"}, Spelled: prefixed,
|
||||
UA: []string{"cline"},
|
||||
Bin: "cline", Dir: dir, Path: path,
|
||||
Sync: func() error {
|
||||
|
||||
@@ -39,7 +39,7 @@ func commandCodeIn(at place) *Agent {
|
||||
return edit.DelJSON(providers, "provider."+magpieID)
|
||||
}
|
||||
return &Agent{
|
||||
ID: "commandcode", Name: "Command Code", Icon: "commandcode", Aliases: []string{"command-code", "cmd"},
|
||||
ID: "commandcode", Name: "Command Code", Icon: "commandcode", Aliases: []string{"command-code", "cmd"}, Spelled: prefixed,
|
||||
UA: []string{"command-code", "commandcode"},
|
||||
Bin: "command-code", Dir: dir, Path: path,
|
||||
Sync: func() error {
|
||||
|
||||
@@ -205,7 +205,7 @@ func droidIn(at place) *Agent {
|
||||
return droidCustom{}, false
|
||||
}
|
||||
return &Agent{
|
||||
ID: "droid", Name: "Droid", Icon: "factory", Aliases: []string{"factory", "factory-droid"},
|
||||
ID: "droid", Name: "Droid", Icon: "factory", Aliases: []string{"factory", "factory-droid"}, Spelled: prefixed,
|
||||
// droid's requests to a custom model carry factory-cli/<version>
|
||||
UA: []string{"factory-cli"},
|
||||
Bin: "droid", Dir: dir, Path: path,
|
||||
|
||||
@@ -93,7 +93,7 @@ func dshAt(at place) *Agent {
|
||||
path = files[0]
|
||||
}
|
||||
return &Agent{
|
||||
ID: "dsh", Name: "DeepSeek Harness", Icon: "deepseek-color", Aliases: []string{"deepseek-harness"},
|
||||
ID: "dsh", Name: "DeepSeek Harness", Icon: "deepseek-color", Aliases: []string{"deepseek-harness"}, Spelled: prefixed,
|
||||
UA: []string{"deepseek-harness"},
|
||||
Bin: "dsh", Dir: dir, Path: path,
|
||||
Sync: func() error { return dshSync(dir, gw()) },
|
||||
|
||||
@@ -102,7 +102,7 @@ func empryoAt(at place) *Agent {
|
||||
return edit.SetJSON(path, edit.KV{Path: empryoModel, Value: v})
|
||||
}
|
||||
return atomic(&Agent{
|
||||
ID: "empryo", Name: "Empryo", Icon: "empryo-color", Aliases: []string{"soulforge"},
|
||||
ID: "empryo", Name: "Empryo", Icon: "empryo-color", Aliases: []string{"soulforge"}, Spelled: prefixed,
|
||||
Bin: "empryo", Dir: dir, Path: path,
|
||||
Notice: func() string {
|
||||
if Running(`(^|/)empryo( |$)`) {
|
||||
|
||||
@@ -57,7 +57,7 @@ func fxIn(at place) *Agent {
|
||||
return edit.DelJSON(path, keys...)
|
||||
}
|
||||
return &Agent{
|
||||
ID: "fx", Name: "fx", Icon: "fx",
|
||||
ID: "fx", Name: "fx", Icon: "fx", Spelled: prefixed,
|
||||
Bin: "fx", Dir: dir, Path: path,
|
||||
Sync: func() error {
|
||||
return syncJSON(path, "providers."+magpieID, func() any { return fxProvider(fxModels(path)[magpieID]) })
|
||||
|
||||
@@ -105,7 +105,7 @@ func grokIn(at place) *Agent {
|
||||
return catalog.Efforts(magpieModels("grok"), ref)
|
||||
}
|
||||
return atomic(&Agent{
|
||||
ID: "grok", Name: "Grok Build", Icon: "xai", Aliases: []string{"grok-build", "grok-cli"},
|
||||
ID: "grok", Name: "Grok Build", Icon: "xai", Aliases: []string{"grok-build", "grok-cli"}, Spelled: prefixed,
|
||||
UA: []string{"grok-shell", "grok-pager", "xai-grok-build"}, // grok-pager: its terminal front end
|
||||
Dir: dir, Path: path,
|
||||
Sync: func() error {
|
||||
|
||||
@@ -93,7 +93,7 @@ func hanako(home string) *Agent {
|
||||
Dir: dir, Path: path,
|
||||
// its own providers' models are provider/id as magpie's are: one
|
||||
// is magpie's only on magpie's provider
|
||||
Prefixed: magpieID + "/",
|
||||
Spelled: prefixed,
|
||||
Sync: func() error {
|
||||
cur, ok := hanakoCurrent(dir)
|
||||
if !ok || hanakoSame(cur, hanakoProvider()) {
|
||||
|
||||
@@ -52,7 +52,7 @@ func hermesIn(at place) *Agent {
|
||||
return edit.SetYAML(path, kvs...)
|
||||
}
|
||||
return &Agent{
|
||||
ID: "hermes", Name: "Hermes Agent", Icon: "hermes", Aliases: []string{"hermes-agent"},
|
||||
ID: "hermes", Name: "Hermes Agent", Icon: "hermes", Aliases: []string{"hermes-agent"}, Spelled: prefixed,
|
||||
UA: []string{"hermes-agent"},
|
||||
Bin: "hermes", Dir: dir, Path: path,
|
||||
Sync: func() error {
|
||||
|
||||
@@ -155,7 +155,7 @@ func kimiIn(at place) *Agent {
|
||||
return err == nil && t != nil
|
||||
}
|
||||
return atomic(&Agent{
|
||||
ID: "kimi", Name: "Kimi Code", Icon: "kimi", Aliases: []string{"kimi-code", "kimi-cli"},
|
||||
ID: "kimi", Name: "Kimi Code", Icon: "kimi", Aliases: []string{"kimi-code", "kimi-cli"}, Spelled: prefixed,
|
||||
UA: []string{"kimicli"},
|
||||
Bin: "kimi", Dir: dir, Path: path,
|
||||
Sync: func() error {
|
||||
|
||||
@@ -76,7 +76,7 @@ func miniMaxAt(at place, dir string) *Agent {
|
||||
return edit.SetYAML(path, kvs...)
|
||||
}
|
||||
return atomic(&Agent{
|
||||
ID: "minimax-code", Name: "MiniMax Code", Icon: "minimax-color", Aliases: []string{"mcode"},
|
||||
ID: "minimax-code", Name: "MiniMax Code", Icon: "minimax-color", Aliases: []string{"mcode"}, Spelled: prefixed,
|
||||
UA: []string{mcodeUA},
|
||||
Bin: "mcode", Dir: dir, Path: path,
|
||||
Sync: func() error {
|
||||
|
||||
@@ -90,7 +90,7 @@ func morphIn(at place) *Agent {
|
||||
return 0
|
||||
}
|
||||
return &Agent{
|
||||
ID: "morph", Name: "Mister Morph", Icon: "mistermorph-color", Aliases: []string{"mistermorph", "mister-morph"},
|
||||
ID: "morph", Name: "Mister Morph", Icon: "mistermorph-color", Aliases: []string{"mistermorph", "mister-morph"}, Spelled: prefixed,
|
||||
UA: []string{morphUA},
|
||||
// no Bin: morph is other tools' name too; ~/.morph is made by its
|
||||
// first run, `morph install` or the desktop app's setup
|
||||
|
||||
@@ -270,7 +270,7 @@ func ompAt(at place, dir string, entry func() ompProviderEntry) *Agent {
|
||||
}
|
||||
}
|
||||
return &Agent{
|
||||
ID: "omp", Name: "omp", Icon: "omp", Aliases: []string{"oh-my-pi"},
|
||||
ID: "omp", Name: "omp", Icon: "omp", Aliases: []string{"oh-my-pi"}, Spelled: prefixed,
|
||||
UA: []string{"oh-my-pi"},
|
||||
Bin: "omp", Dir: dir, Path: path,
|
||||
// a role's thinking level is omp's, after whichever model it is on;
|
||||
|
||||
@@ -182,7 +182,7 @@ func openChamber(home, cfg string) *Agent {
|
||||
return edit.DelJSON(ocPath, "provider."+magpieID)
|
||||
}
|
||||
return &Agent{
|
||||
ID: "openchamber", Name: "OpenChamber", Icon: "openchamber", Aliases: []string{"chamber"},
|
||||
ID: "openchamber", Name: "OpenChamber", Icon: "openchamber", Aliases: []string{"chamber"}, Spelled: openCodeSpelled(ocPath, gatewayV1),
|
||||
// its model requests are its OpenCode's, and counted as OpenCode's
|
||||
Bin: "openchamber", Dir: s.dir, Path: s.prefs(),
|
||||
Notice: func() string {
|
||||
|
||||
@@ -113,7 +113,7 @@ func qoderAt(at place, b qoderBuild) *Agent {
|
||||
return ok && qoderKeyed(get(slot+".apiKey"), b.id)
|
||||
}
|
||||
return &Agent{
|
||||
ID: b.id, Name: b.name, Icon: "qoder", Aliases: b.aliases,
|
||||
ID: b.id, Name: b.name, Icon: "qoder", Aliases: b.aliases, Spelled: prefixed,
|
||||
Bin: b.bin, Dir: dir, Path: path,
|
||||
Sync: func() error {
|
||||
ref, ok := cutMagpie(get("model.name"))
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// An agent that spells a model provider/model, magpie's as magpie/<ref>:
|
||||
// one of its own providers named as one of magpie's (its own relay, and
|
||||
// magpie's relay) is its own model, not magpie's. The row isn't connected
|
||||
// on it, and Disconnect from magpie puts it back and is done, rather than
|
||||
// leaving the row connected on the model it went back to (#835, OpenHanako's
|
||||
// own deepseek/deepseek-v4-pro).
|
||||
func TestOwnProviderNamedAsMagpies(t *testing.T) {
|
||||
ids := []string{"agy", "opencode", "openchamber", "mimocode", "pi", "aside", "omo", "goose", "zed", "vscode",
|
||||
"crush", "dsh", "commandcode", "fx", "omp", "hermes", "morph", "kimi", "empryo", "minimax-code", "droid",
|
||||
"qoder", "qoder-cn", "grok", "atomcode", "cline"}
|
||||
for _, id := range ids {
|
||||
t.Run(id, func(t *testing.T) {
|
||||
syncHome(t)
|
||||
a, err := Find(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f := &a.Fields[0]
|
||||
if id == "cline" {
|
||||
// a model of OpenRouter's, the provider Cline is on
|
||||
hanakoWrite(t, a.Path, `{"version":1,"modes":{},"providers":{},"lastUsedProvider":"openrouter"}`, 0o600)
|
||||
}
|
||||
if err := f.Set("relay/glm-4.6"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v := f.Get(); v != "relay/glm-4.6" {
|
||||
t.Fatalf("own model reads %q", v)
|
||||
}
|
||||
if a.Wired() {
|
||||
t.Fatal("its own relay/glm-4.6 reads as magpie's")
|
||||
}
|
||||
if err := a.Connect(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !a.Wired() {
|
||||
t.Fatalf("not connected after Connect: %v", a.Values())
|
||||
}
|
||||
if err := a.Disconnect(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a.Wired() {
|
||||
t.Fatalf("still connected after Disconnect: %v", a.Values())
|
||||
}
|
||||
if v := f.Get(); v != "relay/glm-4.6" {
|
||||
t.Errorf("Disconnect left %q, not its own relay/glm-4.6", v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// OpenCode's own provider that sends to magpie's gateway is magpie's all
|
||||
// the same (openCodeRef names a model there by it).
|
||||
func TestOpenCodeGatewayProviderIsMagpies(t *testing.T) {
|
||||
syncHome(t)
|
||||
a, err := Find("opencode")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hanakoWrite(t, a.Path, `{"provider":{"relay":{"options":{"baseURL":"`+gatewayV1()+`"},"models":{"relay/glm-4.6":{}}}},"model":"relay/relay/glm-4.6"}`, 0o644)
|
||||
if !a.Wired() {
|
||||
t.Fatalf("a model through its own provider at the gateway isn't magpie's: %v (%s)", a.Values(), filepath.Base(a.Path))
|
||||
}
|
||||
hanakoWrite(t, a.Path, `{"provider":{"relay":{"options":{"baseURL":"https://x/v1"},"models":{"glm-4.6":{}}}},"model":"relay/glm-4.6"}`, 0o644)
|
||||
if a.Wired() {
|
||||
t.Fatalf("its own relay elsewhere reads as magpie's: %v", a.Values())
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func vscodeAt(dir string) *Agent {
|
||||
return edit.DelJSON(path, vscodeDefault)
|
||||
}
|
||||
return atomic(&Agent{
|
||||
ID: "vscode", Name: "VS Code", Icon: "vscode", Aliases: []string{"vs-code", "copilot-chat", "vscode-chat"},
|
||||
ID: "vscode", Name: "VS Code", Icon: "vscode", Aliases: []string{"vs-code", "copilot-chat", "vscode-chat"}, Spelled: prefixed,
|
||||
Bin: "code", Dir: dir, Path: path,
|
||||
UA: []string{vscodeUA},
|
||||
Notice: func() string {
|
||||
|
||||
@@ -477,7 +477,7 @@ func wslAgent(k wslKind, d distro) *Agent {
|
||||
// files, which starts the distro.
|
||||
func asleep(live *Agent, k wslKind, d distro) *Agent {
|
||||
started := false
|
||||
a := &Agent{ID: live.ID, Name: live.Name, Icon: live.Icon, WSL: d.Name, detect: live.detect,
|
||||
a := &Agent{ID: live.ID, Name: live.Name, Icon: live.Icon, WSL: d.Name, detect: live.detect, Spelled: live.Spelled,
|
||||
Notice: func() string {
|
||||
if started {
|
||||
return live.Notice()
|
||||
|
||||
@@ -38,7 +38,7 @@ func zedAt(dir string) *Agent {
|
||||
model := pairGet(func(k string) (string, bool) { return edit.GetJSON(path, k) }, zedModel+".provider", zedModel+".model")
|
||||
key := "zed:" + path + ":"
|
||||
return atomic(&Agent{
|
||||
ID: "zed", Name: "Zed", Icon: "zed", Bin: "zed", Dir: dir, Path: path,
|
||||
ID: "zed", Name: "Zed", Icon: "zed", Bin: "zed", Dir: dir, Path: path, Spelled: prefixed,
|
||||
UA: []string{"zed"},
|
||||
Notice: func() string {
|
||||
if usesMagpie(model()) && Running(`(^|/)(zed|zeditor|zed-editor)( |$)`) {
|
||||
|
||||
Reference in new issue
Block a user