Skip to content

Add --json/jsonl and --params options#120

Draft
ninoseki wants to merge 8 commits into
mainfrom
params-json
Draft

Add --json/jsonl and --params options#120
ninoseki wants to merge 8 commits into
mainfrom
params-json

Conversation

@ninoseki
Copy link
Copy Markdown
Collaborator

@ninoseki ninoseki commented Mar 9, 2026

This PR adds --json/jsonl (JSON payload) and --params (query parameters) for making this CLI is more agent frendly. (ref. https://justin.poehnelt.com/posts/rewrite-your-cli-for-ai-agents/)

I think this is good for human too. A user can send an arbitary request even if we forget adding a new flag so.

How It Works

  • --json/--params is mapped as extra field in a struct
  • Use extra field value to orverride data to be sent (JSON body, query parameters)

@ninoseki ninoseki requested review from cdnsyseng and fw42 March 9, 2026 07:35
@fw42
Copy link
Copy Markdown
Contributor

fw42 commented Mar 9, 2026

Hmm there are 12 identical implementations of MarshalJSON in this PR. Can we not pull that into a helper function somehow?

Copy link
Copy Markdown
Contributor

@fw42 fw42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I don't know if this is really an improvement. What is the motivation for this PR? Did anyone request this feature? I feel like if you're using --json, you might as well just use curl. This seems to add some extra complexity and quite a bit of extra code (this PR is over 1000 lines of new code) for a small edge case.

Comment thread api/channel_test.go Outdated
}
}

func TestChannelOptionsMarshalJSON_WithoutExtra(t *testing.T) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test seems a bit unnecessary. It's already tested implicitly through the other one, isn't it?

Comment thread api/hostname.go

// size (number of results per batch) is "limit" in this API endpoint
// size is an important parameter for iteration
// so pick it up from extra if it's given
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm why is that needed if we already have size and limit?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size (limit in the hostname API) is an important param for iterating :

t.HasMore = len(r.Results) >= it.size

If it's given from --params, the iterator needs to pick it up from there.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant is: Why do we have to be able to extract size from the new extra field if we already have a --size parameter that could be used? I thought this was all to give access to fields that we haven't implemented yet (but size is already implemented, so why do we need to be able to also use it via the extra thing)?

Comment thread api/scan.go
OverrideSafety *string `json:"overrideSafety,omitempty"`
Country *string `json:"country,omitempty"`
URL string `json:"url"`
CustomAgent string `json:"customagent,omitempty"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why all the changes from *string to string?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I have to comment that.
I noticed that using pointer against strings in the struct is not meaningful.

type A struct {
    foo *string `json:"foo,omitempty"`
}

type B struct {
    foo string `json:"foo,omitempty"`
}

Both produce {} in Go so.

Also using pointer in the struct is inconsistent since other JSON related structs don't use pointer.

Comment thread cmd/flags/common.go
}

func AddParamsFlag(cmd *cobra.Command) {
cmd.Flags().String("params", "", "Query string parameters as JSON (e.g. '{\"key\":\"value\"}')")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why as JSON? Seems a bit unintuitive. Why not -params "foo=bar&bla=blabla" 🤷‍♂️

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I referenced gws and it has --params <JSON> URL/Query parameters as JSON flag. Also I've used a use the same way like this and don't think it's so strange.

Comment thread cmd/flags/common.go
}

func AddJSONLFlag(cmd *cobra.Command) {
cmd.Flags().String("jsonl", "", "JSONL payload to send as request bodies (one JSON payload per line)")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm how do I use this command? Do I have to pass a string with multiple lines as parameter? like -jsonl "{ "foo": "bar" }\n{"bla": "bla"}\n"?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's right.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's right.

I'm confused. In the docs below it sounds like the JSONL payload comes from either a file that's being read or from STDIN. But here you just said that the payload is being given as a command line argument. Which one is it?

Comment thread cmd/flags/common.go
Comment thread cmd/pro/channel/create.go

var createCmdExample = ` urlscan pro channel create -n <name>`
var createCmdExample = ` urlscan pro channel create -n <name>
urlscan pro channel create --json '{"channel":{"name":"...","type":"webhook","webhookURL":"https://..."}}'`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm adding it to the example like this kind of makes it look like that this is the recommended way that we want people to use this tool. Whereas it's more meant like a last resort in case we forget to implement something, right?

Co-Authored-By: Claude <noreply@anthropic.com>
@ninoseki ninoseki requested a review from fw42 March 17, 2026 02:35
Copy link
Copy Markdown
Contributor

@fw42 fw42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced this PR is an improvement. Seems like a lot of extra code and additional complexity. I'll leave it up to you to decide whether to merge it though.

Comment thread api/hostname.go

// size (number of results per batch) is "limit" in this API endpoint
// size is an important parameter for iteration
// so pick it up from extra if it's given
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant is: Why do we have to be able to extract size from the new extra field if we already have a --size parameter that could be used? I thought this was all to give access to fields that we haven't implemented yet (but size is already implemented, so why do we need to be able to also use it via the extra thing)?

}
if json != nil {
opts = append(opts, api.WithIncidentExtra(json))
} else {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I reading this right that the new --json command and the existing command flags can't be combined? If there is a json then all the other commands will just be ignored?

Comment thread cmd/flags/common.go
}

func AddJSONLFlag(cmd *cobra.Command) {
cmd.Flags().String("jsonl", "", "JSONL payload to send as request bodies (one JSON payload per line)")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's right.

I'm confused. In the docs below it sounds like the JSONL payload comes from either a file that's being read or from STDIN. But here you just said that the payload is being given as a command line argument. Which one is it?

Comment thread cmd/scan/bulk.go
urlscan scan bulk-submit list_of_urls.txt <url>`
urlscan scan bulk-submit list_of_urls.txt <url>
# submit with JSONL file where each line is a JSON payload
urlscan scan bulk-submit --jsonl payloads.jsonl
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this loading for reading .jsonl files? I don't see it

@ninoseki ninoseki marked this pull request as draft April 10, 2026 04:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants