fs.go: Value receiver Result() returns new Result — must use return
value not discard it. Changed from r.Result(...); return *r to
return Result{}.Result(os.ReadDir(...)).
i18n: SetLanguage sets i.locale directly. Language() reads i.locale.
Translator reload is core/go-i18n's responsibility.
231 tests passing.
Co-Authored-By: Virgil <virgil@lethean.io>
Command(path, Command{Action: handler}) — typed struct input, Result output.
Command fields exported: Name, Description, Path, Action, Lifecycle, Flags, Hidden.
i18n.SetLanguage returns Result instead of error.
All public methods across core/go now return Result where applicable.
231 tests, 76.5% coverage.
Co-Authored-By: Virgil <virgil@lethean.io>
All 14 public Fs methods return Result instead of (value, error).
validatePath returns Result internally.
Tests updated to use r.OK / r.Value pattern.
231 tests, 77.1% coverage.
Co-Authored-By: Virgil <virgil@lethean.io>
Zero args returns Value. With args, sets Value from Go (value, error).
r.Result() // get
r.Result(file, err) // set — OK = err == nil
r.Result(value) // set — OK = true
One method. Get and set. Same pattern as Service(), Command().
Co-Authored-By: Virgil <virgil@lethean.io>
New() sets Value/OK on the receiver and returns *Result.
Result() returns the Value. Both pointer receivers.
r := &Result{}
r.New(file, err) // OK = err == nil
val := r.Result()
Co-Authored-By: Virgil <virgil@lethean.io>
Action, Query, QueryAll, Perform → Result
QueryHandler, TaskHandler → func returning Result
RegisterAction/RegisterActions → handler returns Result
ServiceStartup, ServiceShutdown → Result
LogError, LogWarn → Result
ACTION, QUERY, QUERYALL, PERFORM aliases → Result
Tests updated to match new signatures.
Co-Authored-By: Virgil <virgil@lethean.io>
Service is now a proper struct with OnStart/OnStop/OnReload lifecycle
functions — not a registry wrapping any. Packages create Service{} with
typed fields, same pattern as Command and Option.
Result drops generics — Value is any. The struct is the container,
Value is the generic. No more Result[T] ceremony.
Service(name, Service{}) to register, Service(name) to get — both
return Result. ServiceFactory returns Result not (any, error).
NewWithFactories/NewRuntime return Result.
232 tests, 77.8% coverage.
Co-Authored-By: Virgil <virgil@lethean.io>
Both registries now use Arg(0, args...) instead of ArgString directly.
Type checking flows through Arg's switch before assertion.
Co-Authored-By: Virgil <virgil@lethean.io>
Arg() detects the type at index and delegates to ArgString/ArgInt/ArgBool.
Index-first, args variadic. Typed extractors validate with ok check.
Co-Authored-By: Virgil <virgil@lethean.io>
core.Arg(args, 0) returns any with bounds check.
ArgString/ArgInt/ArgBool delegate through Arg() for type detection.
Co-Authored-By: Virgil <virgil@lethean.io>
core.ArgString(args, 0) replaces args[0].(string) pattern.
Bounds-checked, returns empty string on miss or wrong type.
Used by Command() and Service() registries.
Co-Authored-By: Virgil <virgil@lethean.io>
core.Concat("cmd.", key, ".description") — variadic string builder.
Gives a single point to add sanitisation, injection checks, or
encoding later. command.go I18nKey uses it.
Co-Authored-By: Virgil <virgil@lethean.io>
HasPrefix, HasSuffix, TrimPrefix, TrimSuffix, Contains, Split, SplitN,
StringJoin, Replace, Lower, Upper, Trim, RuneCount.
utils.go and command.go now use string.go helpers — zero direct
strings import in either file.
234 tests, 79.8% coverage.
Co-Authored-By: Virgil <virgil@lethean.io>
core.IsFlag(arg) checks if an argument starts with a dash.
Cli.go no longer imports strings — all string ops via utils.go helpers.
Co-Authored-By: Virgil <virgil@lethean.io>
core.Printl(w, format, args...) writes a formatted line to any writer,
defaulting to os.Stdout. Cli.Print() delegates to Printl.
Co-Authored-By: Virgil <virgil@lethean.io>
All CLI output goes through Cli.Print() instead of direct fmt calls.
SetOutput() allows redirecting (testing, logging, etc).
Co-Authored-By: Virgil <virgil@lethean.io>
- FilterArgs: removes empty strings and Go test runner flags
- ParseFlag: single dash (-v, -🔥) must be 1 char, double dash (--verbose) must be 2+ chars
- Cli.Run() now uses FilterArgs and ParseFlag — no test flag awareness in surface layer
- Invalid flags silently ignored (e.g. -verbose, --v)
221 tests, 79.7% coverage.
Co-Authored-By: Virgil <virgil@lethean.io>
Hit compress/compressFile via GeneratePack with actual asset files on disk.
Added SetOutput log test. Crash report test covers Reports() graceful nil.
Remaining 0%: getAllFiles (group dir scan), appendReport (unexported filePath).
Both are internal plumbing — public API is fully covered.
Co-Authored-By: Virgil <virgil@lethean.io>
Command is now a DTO with no root/child awareness:
- Path-based registration: c.Command("deploy/to/homelab", handler)
- Description is an i18n key derived from path: cmd.deploy.to.homelab.description
- Lifecycle: Run(), Start(), Stop(), Restart(), Reload(), Signal()
- All return core.Result — errors flow through Core internally
- Parent commands auto-created from path segments
Cli is now a surface layer that reads from Core's command registry:
- Resolves os.Args to command path
- Parses flags into Options (--port=8080 → Option{K:"port", V:"8080"})
- Calls command action with parsed Options
- Banner and help use i18n
Old Clir code preserved in tests/testdata/cli_clir.go.bak for reference.
211 tests, 77.5% coverage.
Co-Authored-By: Virgil <virgil@lethean.io>
164 tests, 41.3% coverage. Tests written against the public API only
(external test package, no _test.go in pkg/core/).
Covers: New(Options), Data, Drive, Config, Service, Error, IPC,
Fs, Cli, Lock, Array, Log, App, Runtime, Task.
Fixes: NewCommand now inits flagset, New() wires Cli root command.
Old tests removed — they referenced With*, RegisterService, and
other patterns that no longer exist.
Co-Authored-By: Virgil <virgil@lethean.io>
- All New* constructors removed (NewApp, NewIO, NewCoreCli, NewBus, NewService, NewCoreI18n, NewConfig)
- New() uses pure struct literals: &App{}, &Fs{}, &Config{ConfigOpts:}, &Cli{opts:}, &Service{}, &Ipc{}, &I18n{}
- Ipc methods moved to func (c *Core) — Ipc is now a DTO
- LockApply only called from WithServiceLock, not on every New()
- Service map lazy-inits on first write
- CliOpts DTO with Version/Name/Description
Co-Authored-By: Virgil <virgil@lethean.io>
C1: mnt_extract.go rename bug — source path was mutated before
reading from fs.FS. Now uses separate sourcePath/targetPath.
C2: cli_command.go os.Stderr = nil — replaced with
flags.SetOutput(io.Discard). No more global nil stderr.
C3: Cli() returned nil — now initialised in New() with
NewCliApp("", "", "").
Found by opus code-reviewer agent (final review pass).
Co-Authored-By: Virgil <virgil@lethean.io>
Brings go-log's errors and logger directly into the Core package:
core.E("pkg.Method", "msg", err) — structured errors
core.Err{Op, Msg, Err, Code} — error type
core.Wrap(err, op, msg) — error wrapping
core.NewLogger(opts) — structured logger
core.Info/Warn/Error/Debug(msg, kv) — logging functions
Removed:
pkg/core/e.go — was re-exporting from go-log, now source is inline
pkg/log/ — was re-exporting, no longer needed
Renames to avoid conflicts:
log.New() → core.NewLogger() (core.New is the DI constructor)
log.Message() → core.ErrorMessage() (core.Message is the IPC type)
go-log still exists as a separate module for external consumers.
Core framework now has errors + logging built-in. Zero deps.
Co-Authored-By: Virgil <virgil@lethean.io>
filepath.Clean("/"+p) returns absolute path, filepath.Join(root, "/abs")
drops root on Linux. Strip leading "/" before joining with sandbox root.
Currently not exploitable (validatePath handles it), but any future
caller of path() with active sandbox would escape. Defensive fix.
Found by Gemini Pro security review.
Co-Authored-By: Virgil <virgil@lethean.io>
Prevents external mutation of crash handler metadata after construction.
Uses maps.Clone (Go 1.21+) as suggested by Gemini Pro review.
Co-Authored-By: Virgil <virgil@lethean.io>
GetString/GetInt/GetBool now delegate to EtcGet[T].
Gemini Pro review finding — three identical functions collapsed to one generic.
Co-Authored-By: Virgil <virgil@lethean.io>
Brings go-io/local into Core as c.Io():
c.Io().Read("config.yaml")
c.Io().Write("output.txt", content)
c.Io().WriteMode("key.pem", data, 0600)
c.Io().IsFile("go.mod")
c.Io().List(".")
c.Io().Delete("temp.txt")
Default: rooted at "/" (full access like os package).
Sandbox: core.WithIO("./data") restricts all operations.
c.Mnt() stays for embedded/mounted assets (read-only).
c.Io() is for local filesystem (read/write/delete).
WithMount stays for mounting fs.FS subdirectories.
WithIO added for sandboxing local I/O.
Based on go-io/local/client.go (~300 lines), zero external deps.
Co-Authored-By: Virgil <virgil@lethean.io>