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
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,54 @@ Note: running `x auth -v` windows might flag the tool as a threat this is becaus

## Docs
[Docs](https://deepwiki.com/devhindo/x)

## Detailed Documentation

The CLI has been updated to use a more robust command structure.

### Global Options
- `-h, --help`: Help for any command

### Commands

#### `x [message]`
Post a tweet directly.
```bash
x "Hello World"
```

#### `tweet` (alias: `t`)
Post a tweet.
```bash
x tweet "Hello World"
x t "Hello World"
```

#### `auth`
Manage authentication.
- `x auth`: Start authentication flow
- `x auth --verify` (or `-v`): Verify authentication status
- `x auth --clear` (or `-c`): Clear stored credentials
- `x auth --url`: Display the authorization URL

#### `future` (alias: `f`)
Schedule a tweet for later.
```bash
x future "Tweet later" 2h30m
x f "Tweet later" 5h
```
Arguments:
1. Message: The tweet content
2. Duration: Time to wait (e.g., "1h", "30m", "1h30m")

#### `version` (alias: `v`)
Print the CLI version.
```bash
x version
```

### Legacy Support
The following legacy flag styles are still supported for backward compatibility:
- `x -t "msg"` -> `x t "msg"`
- `x -f "msg" "time"` -> `x f "msg" "time"`
- `x -v` -> `x version`
10 changes: 5 additions & 5 deletions src/cli/auth/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package auth
import (
"crypto/rand"
"crypto/sha256"
"math/big"
"encoding/base64"
"math/big"
)

/*
* code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
*/
*/

func (u *User) generate_code_challenge() {
// Base64-URL-encoded string of the SHA256 hash of the code verifier
Expand Down Expand Up @@ -45,7 +45,7 @@ func (u *User) generate_code_verifier() {
}
b[i] = chars[n.Int64()]
}

u.Code_verifier = string(b)
}

Expand All @@ -56,7 +56,7 @@ func (u *User) generate_state(stateLength int) {
panic(err)
}

state := base64.URLEncoding.EncodeToString(b)
state := base64.RawURLEncoding.EncodeToString(b)

u.State = state
}
}
14 changes: 7 additions & 7 deletions src/cli/auth/oauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ func TestGenerateCodeVerifier(t *testing.T) {
func TestGenerateCodeChallenge(t *testing.T) {
u := &User{}
// Set a fixed verifier for reproducibility
u.Code_verifier = "hello_world_verifier_1234567890"
u.Code_verifier = "hello_world_verifier_1234567890"

// sha256("hello_world_verifier_1234567890")
// echo -n "hello_world_verifier_1234567890" | openssl sha256 -binary | base64
// hash = 84c3c33379967676e828114f851080d859e3557451965b1285268c375531d041
// Base64URL(hash) (without padding)

u.generate_code_challenge()

// Verify the result
Expand All @@ -72,17 +72,17 @@ func TestGenerateState(t *testing.T) {

// The implementation generates random bytes of 'length' then Base64 URL encodes them.
// So the resulting string length will be roughly length * 4/3.

if u.State == "" {
t.Error("generate_state() produced empty state")
}

// Decode back to check byte length
decoded, err := base64.URLEncoding.DecodeString(u.State)
decoded, err := base64.RawURLEncoding.DecodeString(u.State)
if err != nil {
t.Errorf("generate_state() produced invalid base64: %v", err)
}

if len(decoded) != length {
t.Errorf("generate_state() decoded length = %d, want %d", len(decoded), length)
}
Expand Down
4 changes: 2 additions & 2 deletions src/cli/auth/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package auth
import (
"fmt"

"github.com/devhindo/x/src/cli/utils"
"github.com/devhindo/x/src/cli/lock"
"github.com/devhindo/x/src/cli/utils"
)

type User struct {
Expand Down Expand Up @@ -32,7 +32,7 @@ func (u *User) add_user_to_db() {
if status != 200 {
fmt.Println("error adding user")
} else {

err := lock.WriteLicenseKeyToFile(u.License)
if err != nil {
fmt.Println("coudln't write license key to file")
Expand Down
34 changes: 34 additions & 0 deletions src/cli/cmd/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cmd

import (
"github.com/devhindo/x/src/cli/auth"
"github.com/devhindo/x/src/cli/clear"
"github.com/spf13/cobra"
)

var authCmd = &cobra.Command{
Use: "auth",
Short: "Authenticate with X",
Run: func(cmd *cobra.Command, args []string) {
verify, _ := cmd.Flags().GetBool("verify")
clearFlag, _ := cmd.Flags().GetBool("clear")
url, _ := cmd.Flags().GetBool("url")

if verify {
auth.Verify()
} else if clearFlag {
clear.StartOver()
} else if url {
auth.Get_url_db()
} else {
auth.Auth()
}
},
}

func init() {
rootCmd.AddCommand(authCmd)
authCmd.Flags().BoolP("verify", "v", false, "Verify authentication")
authCmd.Flags().BoolP("clear", "c", false, "Clear authentication")
authCmd.Flags().Bool("url", false, "Get authorization URL")
}
22 changes: 22 additions & 0 deletions src/cli/cmd/future.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cmd

import (
"github.com/devhindo/x/src/cli/tweet"
"github.com/spf13/cobra"
)

var futureCmd = &cobra.Command{
Use: "future [message] [time]",
Short: "Post a future tweet",
Aliases: []string{"f"},
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
// Construct fake os.Args for compatibility
fakeArgs := []string{"x", "f", args[0], args[1]}
tweet.PostFutureTweet(fakeArgs)
},
}

func init() {
rootCmd.AddCommand(futureCmd)
}
29 changes: 29 additions & 0 deletions src/cli/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package cmd

import (
"os"

"github.com/devhindo/x/src/cli/tweet"
"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "x",
Short: "x CLI - Post tweets from your terminal",
Long: `x is a CLI tool that allows you to post tweets to X (formerly Twitter) directly from your terminal.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) > 0 {
// Join args just in case, but usually it's one arg "msg"
// The original code uses os.Args[1] or args[0]
tweet.POST_tweet(args[0])
} else {
cmd.Help()
}
},
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
20 changes: 20 additions & 0 deletions src/cli/cmd/tweet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cmd

import (
"github.com/devhindo/x/src/cli/tweet"
"github.com/spf13/cobra"
)

var tweetCmd = &cobra.Command{
Use: "tweet [message]",
Short: "Post a tweet",
Aliases: []string{"t"},
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
tweet.POST_tweet(args[0])
},
}

func init() {
rootCmd.AddCommand(tweetCmd)
}
19 changes: 19 additions & 0 deletions src/cli/cmd/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package cmd

import (
"github.com/devhindo/x/src/cli/x"
"github.com/spf13/cobra"
)

var versionCmd = &cobra.Command{
Use: "version",
Short: "Print version",
Aliases: []string{"v"},
Run: func(cmd *cobra.Command, args []string) {
x.Version()
},
}

func init() {
rootCmd.AddCommand(versionCmd)
}
6 changes: 6 additions & 0 deletions src/cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ module github.com/devhindo/x/src/cli
go 1.22.2

require github.com/google/uuid v1.3.1

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
)
11 changes: 11 additions & 0 deletions src/cli/go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
19 changes: 17 additions & 2 deletions src/cli/main.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
package main

import "github.com/devhindo/x/src/cli/x"
import (
"os"

"github.com/devhindo/x/src/cli/cmd"
)

func main() {
x.Run()
// Support legacy commands starting with dash by rewriting them to their aliased subcommands
if len(os.Args) > 1 {
switch os.Args[1] {
case "-t":
os.Args[1] = "t"
case "-f":
os.Args[1] = "f"
case "-v":
os.Args[1] = "v"
}
}

cmd.Execute()
}
Loading