diff --git a/README.md b/README.md index a560e67..2e5baab 100644 --- a/README.md +++ b/README.md @@ -9,20 +9,36 @@ Inspired by [linear-cli](https://github.com/schpet/linear-cli) - stay in the ter **Works great with AI agents** — includes a [skill](#skills) that lets agents search, create, and manage your Notion workspace alongside your code. +> **This is a fork** of [lox/notion-cli](https://github.com/lox/notion-cli) that adds a +> generic [`tools call`](#calling-any-mcp-tool) command for invoking any Notion MCP +> tool by name (e.g. `notion-query-data-sources`), plus `--json` input schemas in +> `tools list`. Everything else is upstream. + ## Installation -### From Source +### As a dependency (install the pinned binary) + +Install the CLI straight from this repository — this is the recommended way to +depend on it from another project's tooling, scripts, or CI: ```bash -go install github.com/lox/notion-cli@latest +# latest from the default branch +go install github.com/miguelpalomera/notion-cli@latest + +# or pin an exact tag / commit for reproducible builds (recommended for CI) +go install github.com/miguelpalomera/notion-cli@v0.1.0 +go install github.com/miguelpalomera/notion-cli@ ``` +This drops a `notion-cli` binary in `$(go env GOPATH)/bin` (put that on your `PATH`). +Requires Go 1.22+. In CI, run the same `go install …@` step before invoking `notion-cli`. + ### Build Locally ```bash -git clone https://github.com/lox/notion-cli +git clone https://github.com/miguelpalomera/notion-cli cd notion-cli -mise run build +mise run build # or: go build -o notion-cli . ``` ## Quick Start @@ -161,13 +177,37 @@ The comment commands accept a page URL, ID, or name. `comment list` includes bot ```bash notion-cli tools # List available MCP tools -notion-cli tools --json # Output tools as JSON +notion-cli tools --json # Output tools as JSON (includes each tool's inputSchema) notion-cli version # Show version notion-cli --version # Alias for version notion-cli -v # Short alias for version notion-cli --help # Show help ``` +### Calling any MCP tool + +`tools call` is a generic escape hatch: it invokes **any** Notion MCP tool by name with +a JSON argument object, so you can reach capabilities that have no dedicated subcommand +(for example querying a data source's rows). Discover tool names and their argument +schemas with `notion-cli tools --json`. + +```bash +# arguments inline as a JSON object +notion-cli tools call notion-fetch --args '{"id":""}' + +# arguments from a file, or from stdin with "-" +notion-cli tools call notion-query-data-sources --args-file ./query.json +echo '{"data":{"mode":"sql","data_source_urls":["collection://"],"query":"SELECT * FROM \"collection://\" LIMIT 5"}}' \ + | notion-cli tools call notion-query-data-sources -f - + +# --raw prints the full tool result (all content blocks + isError) as JSON; +# the default prints just the text content +notion-cli tools call notion-fetch --args '{"id":""}' --raw +``` + +By default the command prints the tool's text content and exits non-zero if the tool +reports an error. Use `--raw`/`-r` when you need the structured result. + ## Configuration The CLI uses Notion's remote MCP server with OAuth authentication. On first run, `notion-cli auth login` will open your browser to authorize the CLI with your Notion workspace. diff --git a/cmd/auth.go b/cmd/auth.go index 285b1bd..3285c9d 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -12,10 +12,10 @@ import ( "time" "github.com/fatih/color" - "github.com/lox/notion-cli/internal/cli" - "github.com/lox/notion-cli/internal/config" - "github.com/lox/notion-cli/internal/mcp" - "github.com/lox/notion-cli/internal/output" + "github.com/miguelpalomera/notion-cli/internal/cli" + "github.com/miguelpalomera/notion-cli/internal/config" + "github.com/miguelpalomera/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/output" "golang.org/x/term" ) diff --git a/cmd/auth_api_test.go b/cmd/auth_api_test.go index 37f59d6..0ff7e54 100644 --- a/cmd/auth_api_test.go +++ b/cmd/auth_api_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "github.com/lox/notion-cli/internal/config" + "github.com/miguelpalomera/notion-cli/internal/config" ) func captureStdout(t *testing.T, fn func()) string { diff --git a/cmd/auth_test.go b/cmd/auth_test.go index 7ef6b44..6f60cc1 100644 --- a/cmd/auth_test.go +++ b/cmd/auth_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" - "github.com/lox/notion-cli/internal/config" - "github.com/lox/notion-cli/internal/mcp" "github.com/mark3labs/mcp-go/client/transport" + "github.com/miguelpalomera/notion-cli/internal/config" + "github.com/miguelpalomera/notion-cli/internal/mcp" ) func isolateAuthConfig(t *testing.T) { diff --git a/cmd/comment.go b/cmd/comment.go index 6d8ef15..2c56eb4 100644 --- a/cmd/comment.go +++ b/cmd/comment.go @@ -4,9 +4,9 @@ import ( "context" "strings" - "github.com/lox/notion-cli/internal/cli" - "github.com/lox/notion-cli/internal/mcp" - "github.com/lox/notion-cli/internal/output" + "github.com/miguelpalomera/notion-cli/internal/cli" + "github.com/miguelpalomera/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/output" "golang.org/x/net/html" ) diff --git a/cmd/comment_test.go b/cmd/comment_test.go index de81f64..2ab9f1d 100644 --- a/cmd/comment_test.go +++ b/cmd/comment_test.go @@ -6,8 +6,8 @@ import ( "reflect" "testing" - "github.com/lox/notion-cli/internal/mcp" - "github.com/lox/notion-cli/internal/output" + "github.com/miguelpalomera/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/output" ) func TestBuildCommentListRequest(t *testing.T) { diff --git a/cmd/db.go b/cmd/db.go index b0554f4..7b57dd0 100644 --- a/cmd/db.go +++ b/cmd/db.go @@ -5,9 +5,9 @@ import ( "os" "strings" - "github.com/lox/notion-cli/internal/cli" - "github.com/lox/notion-cli/internal/mcp" - "github.com/lox/notion-cli/internal/output" + "github.com/miguelpalomera/notion-cli/internal/cli" + "github.com/miguelpalomera/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/output" ) type DBCmd struct { diff --git a/cmd/page.go b/cmd/page.go index 2dec616..16d906f 100644 --- a/cmd/page.go +++ b/cmd/page.go @@ -8,10 +8,10 @@ import ( "path/filepath" "strings" - "github.com/lox/notion-cli/internal/api" - "github.com/lox/notion-cli/internal/cli" - "github.com/lox/notion-cli/internal/mcp" - "github.com/lox/notion-cli/internal/output" + "github.com/miguelpalomera/notion-cli/internal/api" + "github.com/miguelpalomera/notion-cli/internal/cli" + "github.com/miguelpalomera/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/output" ) type PageCmd struct { diff --git a/cmd/page_edit_test.go b/cmd/page_edit_test.go index 02f85da..19464ba 100644 --- a/cmd/page_edit_test.go +++ b/cmd/page_edit_test.go @@ -7,8 +7,8 @@ import ( "strings" "testing" - "github.com/lox/notion-cli/internal/cli" - "github.com/lox/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/cli" + "github.com/miguelpalomera/notion-cli/internal/mcp" ) func TestBuildPageEditRequestReplace(t *testing.T) { diff --git a/cmd/page_local_images.go b/cmd/page_local_images.go index c025de7..3f40857 100644 --- a/cmd/page_local_images.go +++ b/cmd/page_local_images.go @@ -9,10 +9,10 @@ import ( "golang.org/x/sync/errgroup" - "github.com/lox/notion-cli/internal/api" - "github.com/lox/notion-cli/internal/cli" - "github.com/lox/notion-cli/internal/mcp" - "github.com/lox/notion-cli/internal/output" + "github.com/miguelpalomera/notion-cli/internal/api" + "github.com/miguelpalomera/notion-cli/internal/cli" + "github.com/miguelpalomera/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/output" ) // localImageUploadConcurrency caps concurrent local image uploads. Each diff --git a/cmd/page_local_images_test.go b/cmd/page_local_images_test.go index 11b8fb2..00eef38 100644 --- a/cmd/page_local_images_test.go +++ b/cmd/page_local_images_test.go @@ -11,8 +11,8 @@ import ( "strings" "testing" - "github.com/lox/notion-cli/internal/api" - "github.com/lox/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/api" + "github.com/miguelpalomera/notion-cli/internal/mcp" ) type fakePageUpdater struct { diff --git a/cmd/page_view_test.go b/cmd/page_view_test.go index fe96e54..9f23f49 100644 --- a/cmd/page_view_test.go +++ b/cmd/page_view_test.go @@ -5,8 +5,8 @@ import ( "errors" "testing" - "github.com/lox/notion-cli/internal/mcp" - "github.com/lox/notion-cli/internal/output" + "github.com/miguelpalomera/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/output" ) func TestShouldLoadPageViewComments(t *testing.T) { diff --git a/cmd/root.go b/cmd/root.go index 3e63ade..ad2c1ab 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,6 +1,6 @@ package cmd -import "github.com/lox/notion-cli/internal/config" +import "github.com/miguelpalomera/notion-cli/internal/config" type Context struct { Profile string diff --git a/cmd/search.go b/cmd/search.go index ca46085..ff1ca9d 100644 --- a/cmd/search.go +++ b/cmd/search.go @@ -3,9 +3,9 @@ package cmd import ( "context" - "github.com/lox/notion-cli/internal/cli" - "github.com/lox/notion-cli/internal/mcp" - "github.com/lox/notion-cli/internal/output" + "github.com/miguelpalomera/notion-cli/internal/cli" + "github.com/miguelpalomera/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/output" ) type SearchCmd struct { diff --git a/cmd/tools.go b/cmd/tools.go index 94049ef..be02a71 100644 --- a/cmd/tools.go +++ b/cmd/tools.go @@ -6,56 +6,192 @@ import ( "fmt" "io" "os" + "strings" - "github.com/lox/notion-cli/internal/cli" - "github.com/lox/notion-cli/internal/output" + "github.com/mark3labs/mcp-go/mcp" + "github.com/miguelpalomera/notion-cli/internal/cli" + "github.com/miguelpalomera/notion-cli/internal/output" ) +// ToolsCmd groups the MCP tool commands. `list` stays the default so +// `notion-cli tools` keeps listing tools; `call` invokes any tool generically. type ToolsCmd struct { + List ToolsListCmd `cmd:"" default:"1" help:"List available MCP tools"` + Call ToolsCallCmd `cmd:"" help:"Call an MCP tool by name with JSON arguments"` +} + +// --------------------------------------------------------------------------- +// tools list +// --------------------------------------------------------------------------- + +type ToolsListCmd struct { JSON bool `help:"Output as JSON" short:"j"` } type toolSummary struct { Name string `json:"name"` Description string `json:"description"` + InputSchema any `json:"inputSchema,omitempty"` } -func (c *ToolsCmd) Run(ctx *Context) error { - ctx.JSON = c.JSON - +func (c *ToolsListCmd) Run(_ *Context) error { client, err := cli.RequireClient() if err != nil { return err } defer func() { _ = client.Close() }() - bgCtx := context.Background() - tools, err := client.ListTools(bgCtx) + tools, err := client.ListTools(context.Background()) if err != nil { output.PrintError(err) return err } + return printTools(os.Stdout, newToolSummaries(tools, c.JSON), c.JSON) +} + +// newToolSummaries projects MCP tools onto the CLI's summary DTO. The input +// schema is carried only for JSON output, where it is machine-useful. +func newToolSummaries(tools []mcp.Tool, includeSchema bool) []toolSummary { summaries := make([]toolSummary, len(tools)) for i, t := range tools { summaries[i] = toolSummary{Name: t.Name, Description: t.Description} + if includeSchema { + summaries[i].InputSchema = t.InputSchema + } } - - return printTools(os.Stdout, summaries, c.JSON) + return summaries } func printTools(w io.Writer, tools []toolSummary, asJSON bool) error { if asJSON { - enc := json.NewEncoder(w) - enc.SetIndent("", " ") - return enc.Encode(tools) + return encodeJSON(w, tools) } - for _, t := range tools { if _, err := fmt.Fprintf(w, "%s\n %s\n\n", t.Name, t.Description); err != nil { return err } } + return nil +} + +// --------------------------------------------------------------------------- +// tools call +// --------------------------------------------------------------------------- + +// ToolsCallCmd invokes any MCP tool by name with a JSON argument object. This +// is a generic escape hatch so callers can reach tools that do not have a +// dedicated subcommand (for example querying a data source's rows). Arguments +// are supplied as a JSON object via --args, --args-file, or stdin ("-"). +type ToolsCallCmd struct { + Name string `arg:"" help:"MCP tool name (e.g. notion-query-data-sources)"` + Args string `help:"Tool arguments as a JSON object" short:"a" default:"{}"` + ArgsFile string `help:"Read JSON arguments from a file, or '-' for stdin" short:"f"` + Raw bool `help:"Print the full tool result as JSON (all content blocks + isError)" short:"r"` +} + +func (c *ToolsCallCmd) Run(_ *Context) error { + args, err := c.loadArgs(os.Stdin) + if err != nil { + output.PrintError(err) + return err + } + + client, err := cli.RequireClient() + if err != nil { + return err + } + defer func() { _ = client.Close() }() + + result, err := client.CallTool(context.Background(), c.Name, args) + if err != nil { + output.PrintError(err) + return err + } + + if err := renderToolResult(os.Stdout, c.Name, result, c.Raw); err != nil { + output.PrintError(err) + return err + } + return nil +} + +// loadArgs resolves the JSON argument object from --args, or from --args-file +// (a path, or "-" to read stdin). stdin is injected so it is unit-testable. +func (c *ToolsCallCmd) loadArgs(stdin io.Reader) (map[string]any, error) { + raw := c.Args + if c.ArgsFile != "" { + data, err := readArgsSource(c.ArgsFile, stdin) + if err != nil { + return nil, err + } + raw = data + } + + if strings.TrimSpace(raw) == "" { + return map[string]any{}, nil + } + + var args map[string]any + if err := json.Unmarshal([]byte(raw), &args); err != nil { + return nil, &output.UserError{Message: "invalid JSON arguments: " + err.Error()} + } + return args, nil +} + +func readArgsSource(path string, stdin io.Reader) (string, error) { + if path == "-" { + data, err := io.ReadAll(stdin) + return string(data), err + } + data, err := os.ReadFile(path) + return string(data), err +} + +// renderToolResult writes the tool result to w: raw mode emits the full result +// as JSON, otherwise the text content. A tool-reported error surfaces as a +// returned error (non-zero exit) in either mode. +func renderToolResult(w io.Writer, toolName string, result *mcp.CallToolResult, raw bool) error { + if raw { + if err := encodeJSON(w, result); err != nil { + return err + } + } else if !isToolError(result) { + if _, err := fmt.Fprintln(w, toolResultText(result)); err != nil { + return err + } + } + if isToolError(result) { + text := toolResultText(result) + if text == "" { + text = "tool call failed" + } + return &output.UserError{Message: fmt.Sprintf("tool %q returned an error: %s", toolName, text)} + } return nil } + +func isToolError(result *mcp.CallToolResult) bool { + return result != nil && result.IsError +} + +func toolResultText(result *mcp.CallToolResult) string { + if result == nil { + return "" + } + var b strings.Builder + for _, content := range result.Content { + if tc, ok := content.(mcp.TextContent); ok { + b.WriteString(tc.Text) + } + } + return b.String() +} + +// encodeJSON writes v as indented JSON, matching the CLI's JSON output style. +func encodeJSON(w io.Writer, v any) error { + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(v) +} diff --git a/cmd/tools_call_test.go b/cmd/tools_call_test.go new file mode 100644 index 0000000..2001e6b --- /dev/null +++ b/cmd/tools_call_test.go @@ -0,0 +1,129 @@ +package cmd + +import ( + "bytes" + "strings" + "testing" + + "github.com/mark3labs/mcp-go/mcp" +) + +func TestToolsCallLoadArgs(t *testing.T) { + t.Run("inline JSON object", func(t *testing.T) { + c := &ToolsCallCmd{Args: `{"id":"abc","n":2}`} + got, err := c.loadArgs(strings.NewReader("")) + if err != nil { + t.Fatalf("loadArgs() error = %v", err) + } + if got["id"] != "abc" { + t.Fatalf("id = %v, want abc", got["id"]) + } + if got["n"].(float64) != 2 { + t.Fatalf("n = %v, want 2", got["n"]) + } + }) + + t.Run("empty defaults to empty object", func(t *testing.T) { + c := &ToolsCallCmd{Args: " "} + got, err := c.loadArgs(strings.NewReader("")) + if err != nil { + t.Fatalf("loadArgs() error = %v", err) + } + if len(got) != 0 { + t.Fatalf("len = %d, want 0", len(got)) + } + }) + + t.Run("reads from stdin when args-file is '-'", func(t *testing.T) { + c := &ToolsCallCmd{ArgsFile: "-"} + got, err := c.loadArgs(strings.NewReader(`{"from":"stdin"}`)) + if err != nil { + t.Fatalf("loadArgs() error = %v", err) + } + if got["from"] != "stdin" { + t.Fatalf("from = %v, want stdin", got["from"]) + } + }) + + t.Run("invalid JSON errors", func(t *testing.T) { + c := &ToolsCallCmd{Args: "not json"} + if _, err := c.loadArgs(strings.NewReader("")); err == nil { + t.Fatal("expected error for invalid JSON, got nil") + } + }) +} + +func TestNewToolSummaries(t *testing.T) { + tools := []mcp.Tool{{Name: "a", Description: "desc-a"}} + tools[0].InputSchema.Type = "object" + + withSchema := newToolSummaries(tools, true) + if withSchema[0].InputSchema == nil { + t.Fatal("expected inputSchema to be carried when includeSchema=true") + } + + withoutSchema := newToolSummaries(tools, false) + if withoutSchema[0].InputSchema != nil { + t.Fatal("expected inputSchema to be omitted when includeSchema=false") + } +} + +func TestRenderToolResult(t *testing.T) { + textResult := &mcp.CallToolResult{ + Content: []mcp.Content{mcp.NewTextContent("hello world")}, + } + errResult := &mcp.CallToolResult{ + IsError: true, + Content: []mcp.Content{mcp.NewTextContent("boom")}, + } + + t.Run("text mode writes the content", func(t *testing.T) { + var buf bytes.Buffer + if err := renderToolResult(&buf, "t", textResult, false); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if strings.TrimSpace(buf.String()) != "hello world" { + t.Fatalf("output = %q, want %q", buf.String(), "hello world") + } + }) + + t.Run("raw mode writes JSON", func(t *testing.T) { + var buf bytes.Buffer + if err := renderToolResult(&buf, "t", textResult, true); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !strings.Contains(buf.String(), `"hello world"`) { + t.Fatalf("raw output missing content: %q", buf.String()) + } + }) + + t.Run("tool error returns an error and writes no text", func(t *testing.T) { + var buf bytes.Buffer + err := renderToolResult(&buf, "mytool", errResult, false) + if err == nil { + t.Fatal("expected an error for IsError result") + } + if !strings.Contains(err.Error(), "mytool") || !strings.Contains(err.Error(), "boom") { + t.Fatalf("error = %q, want tool name + message", err.Error()) + } + if buf.Len() != 0 { + t.Fatalf("expected no stdout for tool error, got %q", buf.String()) + } + }) +} + +func TestToolResultText(t *testing.T) { + if got := toolResultText(nil); got != "" { + t.Fatalf("nil result = %q, want empty", got) + } + + result := &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent("hello "), + mcp.NewTextContent("world"), + }, + } + if got := toolResultText(result); got != "hello world" { + t.Fatalf("toolResultText() = %q, want %q", got, "hello world") + } +} diff --git a/go.mod b/go.mod index cdd1d87..c3a6d19 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/lox/notion-cli +module github.com/miguelpalomera/notion-cli go 1.25.6 diff --git a/internal/api/client.go b/internal/api/client.go index 9c1dd01..11c9011 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -15,7 +15,7 @@ import ( "strings" "time" - "github.com/lox/notion-cli/internal/config" + "github.com/miguelpalomera/notion-cli/internal/config" ) const defaultHTTPTimeout = 20 * time.Second diff --git a/internal/api/client_test.go b/internal/api/client_test.go index 1134ca4..3d7669e 100644 --- a/internal/api/client_test.go +++ b/internal/api/client_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "github.com/lox/notion-cli/internal/config" + "github.com/miguelpalomera/notion-cli/internal/config" ) func TestGetSelf(t *testing.T) { diff --git a/internal/cli/context.go b/internal/cli/context.go index 6794d10..0d486f6 100644 --- a/internal/cli/context.go +++ b/internal/cli/context.go @@ -7,8 +7,8 @@ import ( "os" "time" - "github.com/lox/notion-cli/internal/mcp" - "github.com/lox/notion-cli/internal/output" + "github.com/miguelpalomera/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/output" ) var accessToken string diff --git a/internal/cli/official_api.go b/internal/cli/official_api.go index 452fc8b..29440cd 100644 --- a/internal/cli/official_api.go +++ b/internal/cli/official_api.go @@ -3,8 +3,8 @@ package cli import ( "fmt" - "github.com/lox/notion-cli/internal/api" - "github.com/lox/notion-cli/internal/config" + "github.com/miguelpalomera/notion-cli/internal/api" + "github.com/miguelpalomera/notion-cli/internal/config" ) type OfficialAPIConfig struct { diff --git a/internal/cli/resolve.go b/internal/cli/resolve.go index af8c97b..df5ebf4 100644 --- a/internal/cli/resolve.go +++ b/internal/cli/resolve.go @@ -7,8 +7,8 @@ import ( "strings" "unicode" - "github.com/lox/notion-cli/internal/mcp" - "github.com/lox/notion-cli/internal/output" + "github.com/miguelpalomera/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/output" ) type PageRefKind int diff --git a/internal/cli/resolve_test.go b/internal/cli/resolve_test.go index fa76605..8b0f65a 100644 --- a/internal/cli/resolve_test.go +++ b/internal/cli/resolve_test.go @@ -3,7 +3,7 @@ package cli import ( "testing" - "github.com/lox/notion-cli/internal/mcp" + "github.com/miguelpalomera/notion-cli/internal/mcp" ) func TestParsePageRef(t *testing.T) { diff --git a/internal/mcp/token_store.go b/internal/mcp/token_store.go index d2d6154..b4335fe 100644 --- a/internal/mcp/token_store.go +++ b/internal/mcp/token_store.go @@ -9,8 +9,8 @@ import ( "sync" "time" - "github.com/lox/notion-cli/internal/config" "github.com/mark3labs/mcp-go/client/transport" + "github.com/miguelpalomera/notion-cli/internal/config" ) var ErrNoToken = errors.New("no token available") diff --git a/main.go b/main.go index 9ef9ab7..b19eaf9 100644 --- a/main.go +++ b/main.go @@ -4,9 +4,9 @@ import ( "os" "github.com/alecthomas/kong" - "github.com/lox/notion-cli/cmd" - "github.com/lox/notion-cli/internal/cli" - "github.com/lox/notion-cli/internal/config" + "github.com/miguelpalomera/notion-cli/cmd" + "github.com/miguelpalomera/notion-cli/internal/cli" + "github.com/miguelpalomera/notion-cli/internal/config" ) var version = "dev"