Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ StarCLI is a versatile tool that provides a convenient environment for running S
- Base64 encoding/decoding
- Email functionality
- LLM (Language Model) integration
- Templating, data formats & utilities (Liquid, TOML/YAML, QR codes, TOTP, emoji, in-memory cache)
- And many more

## Installation
Expand Down Expand Up @@ -77,7 +78,7 @@ Usage of ./starcli:
--log-format string log file format: console (human) or json (machine) (default "console")
--max-output uint max top-level output entries per run (0=unlimited)
--max-steps uint max Starlark execution steps per run, guards runaway loops (0=unlimited)
-m, --module strings allowed modules to preload and load (default [args,atom,base64,cmd,csv,email,file,go_idiomatic,gum,hashlib,http,json,llm,log,markdown,math,net,path,random,re,regex,runtime,serial,sqlite,stats,string,struct,sys,time,web])
-m, --module strings allowed modules to preload and load (default [args,atom,base64,cache,cmd,csv,email,emoji,file,go_idiomatic,gum,hashlib,http,json,liquid,llm,log,markdown,math,net,path,qrcode,random,re,regex,runtime,serial,sqlite,stats,string,struct,sys,time,toml,totp,web,yaml])
-o, --output string output printer: none,stdout,stderr,basic,lineno,since,auto (default "auto")
--record string record the complete session output (stdout+stderr) to this transcript file
-r, --recursion allow recursion in Starlark code
Expand Down
7 changes: 7 additions & 0 deletions cli/capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ const modCmd = "cmd"
var starpkgCaps = map[string]starlet.ModuleCapability{
"args": starlet.CapPure, // argparse-style parsing of the captured argv
"markdown": starlet.CapPure, // goldmark render, no host effect
"cache": starlet.CapPure, // in-process key/value cache, no host effect
"emoji": starlet.CapPure, // emoji/symbol text conversion
"liquid": starlet.CapPure, // Shopify Liquid template render
"qrcode": starlet.CapPure, // QR encoding to string/bytes, no file writes
"toml": starlet.CapPure, // TOML decode/encode
"totp": starlet.CapPure, // TOTP code generation/validation
"yaml": starlet.CapPure, // YAML decode/encode
"sys": starlet.CapProcess, // argv/platform/host + stdin
"gum": starlet.CapProcess, // interactive terminal I/O
"email": starlet.CapNetwork, // Resend API
Expand Down
44 changes: 44 additions & 0 deletions cli/capability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ package cli
// - allowedModules filtering
// - open default loads everything; invalid tier errors
// - end-to-end gating through Process under an explicit tier
// - wired pure domain modules are classified pure and pass the safe gate

import (
"strings"
"testing"

"github.com/1set/starlet"
)

func TestModuleAllowed(t *testing.T) {
Expand Down Expand Up @@ -171,3 +174,44 @@ func TestProcess_CapabilityGate_EndToEnd(t *testing.T) {
})
}
}

// TestAllWiredModulesClassified guards against wiring a module without
// classifying it: every name in cliMods must have a capability (starpkg here or
// a starlet builtin), or it would slip through the capability gate unpredictably.
func TestAllWiredModulesClassified(t *testing.T) {
for _, name := range cliMods {
if _, ok := moduleCaps(name); !ok {
t.Errorf("wired CLI module %q has no capability classification", name)
}
}
}

// TestPureDomainModulesAreSafe pins the contract for the pure starpkg modules
// wired by default (cache/emoji/liquid/qrcode/toml/totp/yaml): each is classified
// CapPure, so it loads even under the strictest --caps safe gate. The emoji case
// also runs end-to-end to prove the module actually executes, not just resolves.
func TestPureDomainModulesAreSafe(t *testing.T) {
for _, name := range []string{"cache", "emoji", "liquid", "qrcode", "toml", "totp", "yaml"} {
caps, ok := moduleCaps(name)
if !ok {
t.Errorf("pure module %q is wired but not classified", name)
continue
}
if caps != starlet.CapPure {
t.Errorf("pure module %q caps=%v, want CapPure (no network/filesystem reach)", name, caps)
}
}

a := baseArgs()
a.OutputPrinter = "stdout"
a.Caps = "safe" // strictest gate; a pure module must still load
a.CodeContent = `load("emoji", "emojize"); print(emojize("hi :wave:"))`
var rc int
so, se := captureStd(t, func() { rc = Process(a) })
if rc != 0 {
t.Fatalf("safe gate should load pure emoji module: exit=%d stderr=%q", rc, se)
}
if !strings.Contains(so, "\U0001F44B") { // waving hand emoji
t.Errorf("emoji.emojize output=%q, want a waving-hand emoji", so)
}
}
6 changes: 5 additions & 1 deletion cli/characterization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,11 @@ func TestGetDefaultModules(t *testing.T) {
}

// every CLI module is in the default set...
for _, want := range []string{"args", "sys", "gum", "email", "llm", "markdown", "cmd", "sqlite", "web"} {
for _, want := range []string{
"args", "sys", "gum", "email", "llm", "markdown", "cmd", "sqlite", "web",
// pure domain modules wired by default
"cache", "emoji", "liquid", "qrcode", "toml", "totp", "yaml",
} {
if !seen[want] {
t.Errorf("default modules missing CLI module %q", want)
}
Expand Down
30 changes: 30 additions & 0 deletions cli/mods.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@ import (
"github.com/1set/starcli/module/sys"
"github.com/1set/starlet"
"github.com/samber/lo"
"github.com/starpkg/cache"
"github.com/starpkg/cmd"
"github.com/starpkg/email"
"github.com/starpkg/emoji"
"github.com/starpkg/gum"
"github.com/starpkg/liquid"
"github.com/starpkg/llm"
"github.com/starpkg/markdown"
"github.com/starpkg/qrcode"
"github.com/starpkg/sqlite"
"github.com/starpkg/toml"
"github.com/starpkg/totp"
"github.com/starpkg/web"
"github.com/starpkg/yaml"
)

var (
Expand All @@ -31,6 +38,15 @@ var (
cmd.ModuleName,
sqlite.ModuleName,
web.ModuleName,
// pure domain modules (no network / filesystem) — wired by default so
// the turnkey CLI matches the ecosystem's pure starpkg surface.
cache.ModuleName,
emoji.ModuleName,
liquid.ModuleName,
qrcode.ModuleName,
toml.ModuleName,
totp.ModuleName,
yaml.ModuleName,
}
)

Expand Down Expand Up @@ -90,6 +106,20 @@ func loadCLIModuleByName(opts *BoxOpts, name string) (starlet.ModuleLoader, erro
return sqlite.NewModule().LoadModule(), nil
case web.ModuleName:
return web.NewModule().LoadModule(), nil
case cache.ModuleName:
return cache.NewModule().LoadModule(), nil
case emoji.ModuleName:
return emoji.NewModule().LoadModule(), nil
case liquid.ModuleName:
return liquid.NewModule().LoadModule(), nil
case qrcode.ModuleName:
return qrcode.NewModule().LoadModule(), nil
case toml.ModuleName:
return toml.NewModule().LoadModule(), nil
case totp.ModuleName:
return totp.NewModule().LoadModule(), nil
case yaml.ModuleName:
return yaml.NewModule().LoadModule(), nil
// Add more modules as needed
default:
return nil, fmt.Errorf("unknown module: %s", name)
Expand Down
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ require (
github.com/samber/lo v1.50.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.16.0
github.com/starpkg/cache v0.1.0
github.com/starpkg/cmd v0.1.0
github.com/starpkg/email v0.1.0
github.com/starpkg/emoji v0.1.0
github.com/starpkg/gum v0.2.0
github.com/starpkg/liquid v0.1.0
github.com/starpkg/llm v0.1.0
github.com/starpkg/markdown v0.1.0
github.com/starpkg/qrcode v0.1.0
github.com/starpkg/sqlite v0.2.1
github.com/starpkg/toml v0.1.0
github.com/starpkg/totp v0.1.0
github.com/starpkg/web v0.1.0
github.com/starpkg/yaml v0.1.0
go.starlark.net v0.0.0-20260324133313-ffb3f39dd27a
go.uber.org/atomic v1.11.0
go.uber.org/zap v1.24.0
Expand All @@ -34,11 +41,13 @@ require (
charm.land/huh/v2 v2.0.3 // indirect
charm.land/lipgloss/v2 v2.0.4 // indirect
github.com/1set/starlight v0.2.0 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect
Expand Down Expand Up @@ -87,7 +96,10 @@ require (
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/osteele/liquid v1.4.0 // indirect
github.com/osteele/tuesday v1.0.3 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pquerna/otp v1.4.0 // indirect
github.com/psanford/memfs v0.0.0-20230130182539-4dbf7e3e865e // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/resend/resend-go/v2 v2.11.0 // indirect
Expand Down Expand Up @@ -120,6 +132,7 @@ require (
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.55.3 // indirect
Expand Down
26 changes: 25 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ github.com/1set/starlet v0.2.2 h1:TWEbuY5O3291GvRwxHLMiJCSGWhI1ZMdXe+nCtBejW0=
github.com/1set/starlet v0.2.2/go.mod h1:3Sz9ToVkumS1OMojGmb37iLqd69UVTr1PWFOXaiC+rA=
github.com/1set/starlight v0.2.0 h1:W9yulJYANolyMLMOH0M4xcW8RQVpi68opSdNmoipcic=
github.com/1set/starlight v0.2.0/go.mod h1:o9KiJBpy92daHyNHBUwS0nFIjjLxLM/XmRxAZf4FIaE=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
Expand All @@ -47,6 +48,9 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
Expand Down Expand Up @@ -192,13 +196,19 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/osteele/liquid v1.4.0 h1:WS6lT3MFWUAxNbveF22tMLluOWNghGnKCZHLn7NbJGs=
github.com/osteele/liquid v1.4.0/go.mod h1:VmzQQHa5v4E0GvGzqccfAfLgMwRk2V+s1QbxYx9dGak=
github.com/osteele/tuesday v1.0.3 h1:SrCmo6sWwSgnvs1bivmXLvD7Ko9+aJvvkmDjB5G4FTU=
github.com/osteele/tuesday v1.0.3/go.mod h1:pREKpE+L03UFuR+hiznj3q7j3qB1rUZ4XfKejwWFF2M=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/psanford/memfs v0.0.0-20230130182539-4dbf7e3e865e h1:51xcRlSMBU5rhM9KahnJGfEsBPVPz3182TgFRowA8yY=
github.com/psanford/memfs v0.0.0-20230130182539-4dbf7e3e865e/go.mod h1:tcaRap0jS3eifrEEllL6ZMd9dg8IlDpi2S1oARrQ+NI=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
Expand Down Expand Up @@ -234,20 +244,34 @@ github.com/spyzhov/ajson v0.9.6 h1:iJRDaLa+GjhCDAt1yFtU/LKMtLtsNVKkxqlpvrHHlpQ=
github.com/spyzhov/ajson v0.9.6/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8=
github.com/starpkg/base v0.1.1 h1:AeNOEIyuyQJoQjgU+AxAE3+1ebgLlsF1HmA49tdTOtU=
github.com/starpkg/base v0.1.1/go.mod h1:0QLSawzBUCFLNk1jMRpzNs6AZH9SVpumd2J7WLOPrR0=
github.com/starpkg/cache v0.1.0 h1:P3nJC2zIycblI+Xz57yyhaAJOLMsf8A6ngejGTG7GA4=
github.com/starpkg/cache v0.1.0/go.mod h1:iYiUOUPJlJ2Z3CQVAO1T6RsQ6gg6g4/Cw6ssGvx3X68=
github.com/starpkg/cmd v0.1.0 h1:RyB0u6zixc05tbVj1Q7SoNumDlO1ZTy3sAaJA9xRdIA=
github.com/starpkg/cmd v0.1.0/go.mod h1:gjMdstyJeMiK70fZVR8uqr7ub3XnmJjRHDqOnKqabfI=
github.com/starpkg/email v0.1.0 h1:KgV+9pmaE4fI9RTijjuYtZRjI3jRLzroW4wt+NW0Nr8=
github.com/starpkg/email v0.1.0/go.mod h1:V9CnDje/JydsHvGMNaukM8sn/bS6yUdT7qwgG8X8JQg=
github.com/starpkg/emoji v0.1.0 h1:GNYtnkZgD1rucptKtjfUVWiAbsqfzqPncoFqVcFVtlE=
github.com/starpkg/emoji v0.1.0/go.mod h1:X5D6vhF/IV3LRANYr7uBWQZ+OLZrwoZ72rwVDt4VbhY=
github.com/starpkg/gum v0.2.0 h1:FTcodBpGoAHFr9+ohenuRSIriw1hfxf1fVx/rImFOA4=
github.com/starpkg/gum v0.2.0/go.mod h1:roQiInvIbYI1KuUwWC/cfdtNBvXWQOanydc3FSXG+fA=
github.com/starpkg/liquid v0.1.0 h1:+CHfjJQaHmOqBIaiDxyCEo7r6t3LQfVicJo/MW4Be7o=
github.com/starpkg/liquid v0.1.0/go.mod h1:pzCS93ZJM8JqWk9cxzPFzfst/rNCMbcR6G5tU997SgA=
github.com/starpkg/llm v0.1.0 h1:CtTUTfL6zUpQPADFpNt0BpV5Z/ReRuUEvRPf8kcWNxk=
github.com/starpkg/llm v0.1.0/go.mod h1:/pvKpFCN1w2olXlmdw5f2PvYPuJPbCmop65hTj+IShA=
github.com/starpkg/markdown v0.1.0 h1:buLZwTcZ9SQWrVOUVeTcHqw/DCkxKPE8rdvHpbhjrE8=
github.com/starpkg/markdown v0.1.0/go.mod h1:JsRNXRFjvLPRrkjGrf6jXDTl3Qg9+d2/PByzW7WxYvg=
github.com/starpkg/qrcode v0.1.0 h1:VULD2XBVacMq0c2WxJgdG0d3k0Z4EOSaFdYdkOB7bME=
github.com/starpkg/qrcode v0.1.0/go.mod h1:itKiBubTuioyMM78bA+RTfwm/hH3EYG+W6NX0cscgh0=
github.com/starpkg/sqlite v0.2.1 h1:oTVkPCTQYhud3qDXztJsb/1cBY4SgXLF2PKonRhJPFI=
github.com/starpkg/sqlite v0.2.1/go.mod h1:Hfs3JZnFbDPefCL9ybSmRJIQ+IBVMYtj5FyZ+7XZpmc=
github.com/starpkg/toml v0.1.0 h1:geXbLw/SW7XcU9Tz/eqjxSNywsMbxqfndXYOlXXkSKE=
github.com/starpkg/toml v0.1.0/go.mod h1:8QtTjIjFNuw0WGYzDHFbynhQ21QM6BvdACzapKE1cr8=
github.com/starpkg/totp v0.1.0 h1:BQ1J5bbhR8iICQ+iRJ4LZgJNX3RhhOZtY2aoaf38WqY=
github.com/starpkg/totp v0.1.0/go.mod h1:rJSA5YkYkbVO6jc1ldSaenCuwpsY3rd7M5+bJoWLFww=
github.com/starpkg/web v0.1.0 h1:AAGf4SzD2f10BJZyaRCtUm5W9At2EjIhmOqCNEXKK2E=
github.com/starpkg/web v0.1.0/go.mod h1:1S47yJjbY5bye3Ioe5RfDWyA5WK0QWhMa2elEpJSAQE=
github.com/starpkg/yaml v0.1.0 h1:hA9SQBwJ/xEUAeF+ZpC3M1OgnX86zxWVd+cMTqIDOak=
github.com/starpkg/yaml v0.1.0/go.mod h1:PxvQ/rbS4ZoainKtjzDNRuWZAO8TwUmFCHF/Wji/rQM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand Down
Loading