schwab_rb 0.6.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 123cd13fd78778da2f5f2d08f9dc8f7594a07b8fcd93659bd49df3b1cd28ddb7
4
- data.tar.gz: 0760e50de57a1a2d207cf1cc4744441f4a08fc2fc983859317feda22df701b15
3
+ metadata.gz: e92f8e69c61f92f34a54eb5ae7b5b07a8734dbe71a221006972a0988aedc5fd5
4
+ data.tar.gz: a0635b9f44605a22ee31140f8b8601d7f0cf64d28dcdf41e5f711d680efa7dfd
5
5
  SHA512:
6
- metadata.gz: bc5b094add1c5002f1a799ef41ca023b18e3857c39476c91282174640728fd7be0fe8f7de59a265cdf7c86a893a0c04dce91f7fb7551ee2818a5e371f9b1f792
7
- data.tar.gz: f29fc656583df33e6ff787715713075fb0ffc094ca5ae81915fb37fca4c0dbbefa362488a687f6dcc9dd957dd65c65917b66ba98fc4401467139adf68ea03f4a
6
+ metadata.gz: c30b898de7b8e933960f38c62b82364210ec89abe603b773bf7e0b012e74ce8e3230f6f28c015fc100a46a2a1a7596e6896758f3a695dd9707c63ef92eb1df8d
7
+ data.tar.gz: 8f0e880a9a0227ad9b02c1d8ed95f9b66d49e2303ac5ad955f8cd9cc7fa0cabd893e72a5091f6990c352eacf5939e9a75eee292cf0f592dbe7c74b53baff897c
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(gh pr edit:*)"
5
+ ],
6
+ "deny": [],
7
+ "ask": []
8
+ }
9
+ }
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: schwab-rb-cli
3
+ description: Use the installed schwab_rb CLI for authentication and price history downloads. Activate when a user wants to run the schwab_rb command, log in to Schwab, fetch market data, troubleshoot CLI installation or environment variables, or save price history from the command line.
4
+ ---
5
+
6
+ # Schwab RB CLI Skill
7
+
8
+ Use this skill when the task should be completed through the installed `schwab_rb` command rather than by editing Ruby code directly.
9
+
10
+ ## When to use
11
+
12
+ - The user wants to run `schwab_rb help`, `schwab_rb login`, or `schwab_rb price-history`
13
+ - The user wants to download price history data to JSON or CSV
14
+ - The user needs help with CLI installation, `asdf` shims, or missing `SCHWAB_*` environment variables
15
+ - The user wants the agent to verify the CLI works on the local machine
16
+
17
+ ## Quick workflow
18
+
19
+ 1. Prefer the bundled wrapper script:
20
+
21
+ ```bash
22
+ skills/schwab-rb-cli/scripts/run_schwab_rb.sh help
23
+ ```
24
+
25
+ The wrapper only attempts to execute the globally installed `schwab_rb` command from `PATH`. It does not use repo-relative paths or Bundler fallbacks.
26
+
27
+ 2. If the wrapper reports that `schwab_rb` is missing:
28
+ - Report that the host machine does not have the CLI available in `PATH`
29
+ - Tell the user to install the gem globally and fix shell/shim configuration before retrying
30
+
31
+ 3. Before running auth or data commands, verify these exported variables exist in the shell environment:
32
+ - `SCHWAB_API_KEY`
33
+ - `SCHWAB_APP_SECRET`
34
+ - `SCHWAB_APP_CALLBACK_URL`
35
+
36
+ 4. For first-time auth, run:
37
+
38
+ ```bash
39
+ skills/schwab-rb-cli/scripts/run_schwab_rb.sh login
40
+ ```
41
+
42
+ 5. For data downloads, run:
43
+
44
+ ```bash
45
+ skills/schwab-rb-cli/scripts/run_schwab_rb.sh price-history --symbol AAPL --start-date 2026-03-01
46
+ ```
47
+
48
+ ## Operating guidance
49
+
50
+ - Do not ask users to paste secrets into chat when the task can be completed using already-exported environment variables.
51
+ - Assume the CLI token is shared at `~/.schwab_rb/token.json`.
52
+ - Assume downloaded data goes to `~/.schwab_rb/data` unless `--dir` is passed.
53
+ - Use `--format csv` only when the user specifically wants flat files; otherwise prefer JSON because it matches the API response shape.
54
+ - If the wrapper says the executable is missing, stop and report that the host install is unavailable. Do not substitute repo-local execution.
55
+ - If the CLI reports missing environment variables, check exported shell envs first. Do not rely on a repo-local `.env` unless the user explicitly wants that setup.
56
+
57
+ ## References
58
+
59
+ - For concrete commands and troubleshooting steps, read [references/cli_examples.md](references/cli_examples.md).
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "schwab_rb CLI"
3
+ short_description: "Run the installed schwab_rb CLI for auth and market data downloads"
@@ -0,0 +1,100 @@
1
+ # schwab_rb CLI Examples
2
+
3
+ ## Basic checks
4
+
5
+ ```bash
6
+ skills/schwab-rb-cli/scripts/run_schwab_rb.sh help
7
+ skills/schwab-rb-cli/scripts/run_schwab_rb.sh help price-history
8
+ ```
9
+
10
+ ## Authentication
11
+
12
+ Expected exported variables:
13
+
14
+ ```bash
15
+ env | rg '^SCHWAB_'
16
+ ```
17
+
18
+ Login flow:
19
+
20
+ ```bash
21
+ skills/schwab-rb-cli/scripts/run_schwab_rb.sh login
22
+ ```
23
+
24
+ ## Price history downloads
25
+
26
+ Daily JSON output:
27
+
28
+ ```bash
29
+ skills/schwab-rb-cli/scripts/run_schwab_rb.sh price-history --symbol AAPL --start-date 2026-03-01
30
+ ```
31
+
32
+ Minute CSV output:
33
+
34
+ ```bash
35
+ skills/schwab-rb-cli/scripts/run_schwab_rb.sh price-history \
36
+ --symbol VIX \
37
+ --start-date 2026-03-01 \
38
+ --end-date 2026-03-24 \
39
+ --freq 1min \
40
+ --format csv
41
+ ```
42
+
43
+ Custom output directory:
44
+
45
+ ```bash
46
+ skills/schwab-rb-cli/scripts/run_schwab_rb.sh price-history \
47
+ --symbol SPY \
48
+ --start-date 2026-03-01 \
49
+ --dir /tmp/schwab_rb_data
50
+ ```
51
+
52
+ ## Installation troubleshooting
53
+
54
+ Install the gem from the repo:
55
+
56
+ ```bash
57
+ bundle exec rake install
58
+ ```
59
+
60
+ If the host uses `asdf`, regenerate shims:
61
+
62
+ ```bash
63
+ asdf reshim ruby 3.2.2
64
+ ```
65
+
66
+ Verify the executable:
67
+
68
+ ```bash
69
+ which schwab_rb
70
+ ls -l ~/.asdf/shims/schwab_rb
71
+ ```
72
+
73
+ If `schwab_rb` is still not on `PATH`, the wrapper script will fail immediately and the agent should report that the host install is not usable yet.
74
+
75
+ ## Environment troubleshooting
76
+
77
+ Check that the variables are exported, not just set in the shell:
78
+
79
+ ```bash
80
+ env | rg '^SCHWAB_'
81
+ ```
82
+
83
+ Portable shell setup:
84
+
85
+ ```bash
86
+ # ~/.zshrc
87
+ if [ -f "$HOME/.env" ]; then
88
+ set -a
89
+ source "$HOME/.env"
90
+ set +a
91
+ fi
92
+ ```
93
+
94
+ Then in `~/.env`:
95
+
96
+ ```bash
97
+ SCHWAB_API_KEY=...
98
+ SCHWAB_APP_SECRET=...
99
+ SCHWAB_APP_CALLBACK_URL=https://127.0.0.1:8182
100
+ ```
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if command -v schwab_rb >/dev/null 2>&1; then
5
+ exec "$(command -v schwab_rb)" "$@"
6
+ fi
7
+
8
+ echo "schwab_rb executable not found in PATH. Install the gem globally on the host machine and ensure the executable is available before using this skill." >&2
9
+ exit 1
data/AGENTS.md ADDED
@@ -0,0 +1,137 @@
1
+ # AGENTS.md
2
+
3
+ This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
4
+
5
+ ## Development Commands
6
+
7
+ ### Setup and Installation
8
+ - `bin/setup` - Install dependencies and set up the development environment
9
+ - `bundle install` - Install gem dependencies
10
+ - `bin/console` - Start an interactive Ruby console with the gem loaded
11
+
12
+ ### Testing
13
+ - `bundle exec rake spec` - Run all RSpec tests
14
+ - `bundle exec rspec` - Run RSpec tests directly
15
+ - `bundle exec rspec spec/path/to/specific_spec.rb` - Run a specific test file
16
+ - `bundle exec rspec spec/path/to/specific_spec.rb:line_number` - Run a specific test
17
+
18
+ ### Linting and Code Quality
19
+ - `bundle exec rubocop` - Run RuboCop linter
20
+ - `bundle exec rubocop -a` - Auto-correct RuboCop violations where possible
21
+ - `bundle exec rake` - Run both tests and linting (default task)
22
+
23
+ ### Gem Management
24
+ - `bundle exec rake build` - Build the gem
25
+ - `bundle exec rake install` - Install the gem locally
26
+ - `bundle exec rake release` - Release a new version (creates git tag and pushes to RubyGems)
27
+
28
+ ## Architecture Overview
29
+
30
+ ### Core Structure
31
+ This is a Ruby gem (`schwab_rb`) that provides a client library for the Charles Schwab API. The architecture follows a modular design:
32
+
33
+ **Authentication Layer** (`lib/schwab_rb/auth/`):
34
+ - `init_client_easy.rb` - Recommended initialization method that handles token management automatically
35
+ - `init_client_login.rb` - Interactive browser-based authentication flow
36
+ - `init_client_token_file.rb` - Initialize from existing token file
37
+ - `token_manager.rb` - Handles token refresh and persistence
38
+ - `login_flow_server.rb` - Temporary server for OAuth callback handling
39
+
40
+ **Client Layer** (`lib/schwab_rb/clients/`):
41
+ - `client.rb` - Synchronous HTTP client for API calls
42
+ - `async_client.rb` - Asynchronous client using the `async` gem
43
+ - `base_client.rb` - Shared client functionality
44
+
45
+ **Data Objects** (`lib/schwab_rb/data_objects/`):
46
+ - Structured Ruby objects for API responses (Account, Quote, Order, etc.)
47
+ - Replaces raw JSON responses with typed objects for better developer experience
48
+ - All API methods support `return_data_objects: false` to get raw JSON if needed
49
+
50
+ **Order Management** (`lib/schwab_rb/orders/`):
51
+ - `builder.rb` - Fluent interface for constructing complex orders
52
+ - Various enum classes for order parameters (duration, session, instructions, etc.)
53
+
54
+ ### Key Design Patterns
55
+
56
+ **Three-Tier Client Initialization**:
57
+ 1. `init_client_easy()` - Handles everything automatically (recommended)
58
+ 2. `init_client_token_file()` - For existing tokens
59
+ 3. `init_client_login()` - For interactive authentication
60
+
61
+ **Data Object Strategy**:
62
+ - All API methods return structured Ruby objects by default
63
+ - Use `return_data_objects: false` for raw JSON responses
64
+ - Data objects are built from JSON using factory patterns
65
+
66
+ **Async Support**:
67
+ - Both sync and async clients available
68
+ - Async operations use the `async` gem with promises
69
+ - Set `asyncio: true` during client initialization
70
+
71
+ ### Configuration
72
+
73
+ Environment variables (loaded via `dotenv`):
74
+ - `SCHWAB_API_KEY` - Your Schwab API key
75
+ - `SCHWAB_APP_SECRET` - Your Schwab application secret
76
+ - `APP_CALLBACK_URL` - OAuth callback URL
77
+ - `TOKEN_PATH` - Path for token storage
78
+ - `SCHWAB_ACCOUNT_NUMBER` - Your account number
79
+ - `SCHWAB_LOGFILE` - Log file path (optional, defaults to STDOUT)
80
+ - `SCHWAB_LOG_LEVEL` - Log level (DEBUG, INFO, WARN, ERROR, FATAL)
81
+ - `SCHWAB_SILENCE_OUTPUT` - Set to 'true' to disable logging
82
+
83
+ Programmatic configuration:
84
+ ```ruby
85
+ SchwabRb.configure do |config|
86
+ config.logger = Logger.new(STDOUT)
87
+ config.log_level = 'INFO'
88
+ config.silence_output = false
89
+ end
90
+ ```
91
+
92
+ ### Testing Infrastructure
93
+
94
+ **Fixtures and Factories**:
95
+ - JSON fixtures in `spec/fixtures/` for realistic API responses
96
+ - Factory classes in `spec/factories/` for creating test objects
97
+ - Extensive mocking of HTTP responses for unit tests
98
+
99
+ **Test Organization**:
100
+ - Unit tests mirror the `lib/` structure
101
+ - Separate specs for data objects, clients, auth, and orders
102
+ - Uses `async-rspec` for testing async functionality
103
+
104
+ ### Key Dependencies
105
+ - `oauth2` - OAuth2 authentication
106
+ - `async` + `async-http` - Asynchronous HTTP operations
107
+ - `sinatra` + `puma` - Temporary server for OAuth callbacks
108
+ - `dotenv` - Environment variable management
109
+ - `rspec` + `async-rspec` - Testing framework
110
+ - `rubocop` - Code linting
111
+
112
+ ## Common Patterns
113
+
114
+ ### Client Initialization
115
+ Always use `init_client_easy()` for new development:
116
+ ```ruby
117
+ client = SchwabRb::Auth.init_client_easy(
118
+ ENV['SCHWAB_API_KEY'],
119
+ ENV['SCHWAB_APP_SECRET'],
120
+ ENV['APP_CALLBACK_URL'],
121
+ ENV['TOKEN_PATH']
122
+ )
123
+ ```
124
+
125
+ ### Order Building
126
+ Use the fluent builder pattern for complex orders:
127
+ ```ruby
128
+ order = SchwabRb::Orders::Builder.new
129
+ .set_session(:normal)
130
+ .set_duration(:day)
131
+ .set_order_type(:market)
132
+ .add_equity_leg(:buy, 'AAPL', 100)
133
+ .build
134
+ ```
135
+
136
+ ### Error Handling
137
+ Token expiration is handled automatically by `init_client_easy()`. Custom error classes are defined in `lib/schwab_rb/orders/errors.rb`.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ### Added
4
+ - CLI `sample` command for saving single-expiration option chain snapshots as CSV or JSON
5
+ - `--root` option for filtering sampled contracts by option root and using that root in output filenames
6
+
7
+ ### Changed
8
+ - CLI history downloads now default to `~/.schwab_rb/data/history`
9
+ - CLI option samples now default to `~/.schwab_rb/data/options`
10
+
3
11
  ## [0.6.0] - 2025-12-11
4
12
 
5
13
  ### Breaking Changes
data/README.md CHANGED
@@ -18,6 +18,44 @@ gem install schwab_rb
18
18
 
19
19
  Note: The gem requires Ruby 3.0.0 or higher.
20
20
 
21
+ ## CLI
22
+
23
+ Installing the gem also installs a `schwab_rb` executable.
24
+
25
+ Set these environment variables before using the CLI:
26
+
27
+ ```bash
28
+ export SCHWAB_API_KEY=your_api_key_here
29
+ export SCHWAB_APP_SECRET=your_app_secret_here
30
+ export SCHWAB_APP_CALLBACK_URL=https://127.0.0.1:8182
31
+ ```
32
+
33
+ Authenticate once to create the shared token in `~/.schwab_rb/token.json`:
34
+
35
+ ```bash
36
+ schwab_rb login
37
+ ```
38
+
39
+ Download price history into `~/.schwab_rb/data` as JSON. Files are stored per symbol and frequency, for example `AAPL_day.json` or `SPX_5min.csv`, and the CLI reuses and reconciles existing candle files when possible:
40
+
41
+ ```bash
42
+ schwab_rb price-history --symbol AAPL --start-date 2026-03-01
43
+ ```
44
+
45
+ Write CSV output to a custom directory:
46
+
47
+ ```bash
48
+ schwab_rb price-history \
49
+ --symbol VIX \
50
+ --start-date 2026-03-01 \
51
+ --end-date 2026-03-24 \
52
+ --freq 1min \
53
+ --format csv \
54
+ --dir ./tmp/price_history
55
+ ```
56
+
57
+ That command writes `./tmp/price_history/VIX_1min.csv`.
58
+
21
59
  ## Prerequisites
22
60
 
23
61
  Before using this gem, you'll need:
@@ -268,4 +306,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jwplat
268
306
 
269
307
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
270
308
 
271
- The gem is inspired by [schwab-py](https://pypi.org/project/schwab-py). The original implementation can be found [here](https://github.com/alexgolec/schwab-py).
309
+ The gem is inspired by [schwab-py](https://pypi.org/project/schwab-py). The original implementation can be found [here](https://github.com/alexgolec/schwab-py).
data/Rakefile CHANGED
@@ -3,6 +3,32 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
5
 
6
+ gem_helper = Bundler::GemHelper.instance
7
+
8
+ def install_built_gem(gem_helper, local: false)
9
+ gem_path = File.join(
10
+ gem_helper.base,
11
+ "pkg",
12
+ "#{gem_helper.send(:name)}-#{gem_helper.send(:version)}.gem"
13
+ )
14
+ command = [*gem_helper.send(:gem_command), "install", gem_path, "--no-document"]
15
+ command << "--local" if local
16
+ sh(*command)
17
+ Bundler.ui.confirm "#{gem_helper.send(:name)} (#{gem_helper.send(:version)}) installed."
18
+ end
19
+
20
+ Rake::Task["install"].clear
21
+ desc "Build and install the gem into system gems without generating documentation."
22
+ task "install" => "build" do
23
+ install_built_gem(gem_helper)
24
+ end
25
+
26
+ Rake::Task["install:local"].clear
27
+ desc "Build and install the gem into system gems without network access or documentation."
28
+ task "install:local" => "build" do
29
+ install_built_gem(gem_helper, local: true)
30
+ end
31
+
6
32
  RSpec::Core::RakeTask.new(:spec)
7
33
 
8
34
  require "rubocop/rake_task"
data/doc/LOGGING.md ADDED
@@ -0,0 +1,141 @@
1
+ # Logging Configuration
2
+
3
+ The `schwab_rb` gem provides flexible logging configuration options to suit different development and production needs.
4
+
5
+ ## Overview
6
+
7
+ The gem's logging system is built around two main components:
8
+ - `SchwabRb::Configuration` - Manages configuration settings
9
+ - `SchwabRb::Logger` - Creates and manages the logger instance
10
+
11
+ ## Configuration Methods
12
+
13
+ ### 1. Environment Variables
14
+
15
+ Set logging behavior using environment variables:
16
+
17
+ ```bash
18
+ export SCHWAB_LOGFILE="/path/to/logfile.log" # File path or "STDOUT"
19
+ export SCHWAB_LOG_LEVEL="INFO" # DEBUG, INFO, WARN, ERROR, FATAL
20
+ ```
21
+
22
+ ### 2. Programmatic Configuration
23
+
24
+ Configure logging in your Ruby code:
25
+
26
+ ```ruby
27
+ SchwabRb.configure do |config|
28
+ config.log_file = "/path/to/logfile.log"
29
+ config.log_level = "DEBUG"
30
+ config.silence_output = false
31
+ end
32
+ ```
33
+
34
+ ## Using the Gem's Built-in Logger
35
+
36
+ ### Default Behavior
37
+ By default, the gem creates its own logger that:
38
+ - Logs to STDOUT
39
+ - Uses WARN level
40
+ - Rotates log files weekly (when logging to file)
41
+ - Uses custom formatting: `[HH:MM:SS] SCHWAB_RB LEVEL: message`
42
+
43
+ ### Example Configuration
44
+ ```ruby
45
+ SchwabRb.configure do |config|
46
+ config.log_file = "/var/log/schwab_rb.log"
47
+ config.log_level = "INFO"
48
+ end
49
+ ```
50
+
51
+ ## Using an External Logger
52
+
53
+ You can provide your own logger instance instead of using the gem's built-in logger:
54
+
55
+ ### Rails Logger Example
56
+ ```ruby
57
+ # In Rails application
58
+ SchwabRb.configure do |config|
59
+ config.logger = Rails.logger
60
+ end
61
+ ```
62
+
63
+ ### Custom Logger Example
64
+ ```ruby
65
+ # Custom logger with specific formatting
66
+ my_logger = Logger.new(STDOUT)
67
+ my_logger.level = Logger::DEBUG
68
+ my_logger.formatter = proc do |severity, datetime, progname, msg|
69
+ "#{datetime} [SCHWAB] #{severity}: #{msg}\n"
70
+ end
71
+
72
+ SchwabRb.configure do |config|
73
+ config.logger = my_logger
74
+ end
75
+ ```
76
+
77
+ ### Structured Logging Example
78
+ ```ruby
79
+ # Using a structured logging library
80
+ require 'semantic_logger'
81
+
82
+ SchwabRb.configure do |config|
83
+ config.logger = SemanticLogger['SchwabRb']
84
+ end
85
+ ```
86
+
87
+ ## Logger Behavior
88
+
89
+ ### External Logger Priority
90
+ When an external logger is provided via `config.logger`, it takes precedence over all other settings. The gem will:
91
+ - Use your logger instance directly
92
+ - Ignore `log_file` and `log_level` settings
93
+ - Not apply custom formatting
94
+
95
+ ### Silence Output
96
+ Setting `silence_output = true` creates a null logger that discards all messages:
97
+
98
+ ```ruby
99
+ SchwabRb.configure do |config|
100
+ config.silence_output = true
101
+ end
102
+ ```
103
+
104
+ ### Log File Handling
105
+ When logging to a file, the gem:
106
+ - Creates the directory if it doesn't exist
107
+ - Creates the log file if it doesn't exist
108
+ - Rotates logs weekly
109
+ - Returns a null logger if the path is `:null` or `"/dev/null"`
110
+
111
+ ## Log Levels
112
+
113
+ Available log levels (case-insensitive):
114
+ - `DEBUG` - Detailed information for debugging
115
+ - `INFO` - General information messages
116
+ - `WARN` - Warning messages (default)
117
+ - `ERROR` - Error conditions
118
+ - `FATAL` - Critical errors
119
+
120
+ ## Accessing the Logger
121
+
122
+ Get the configured logger instance:
123
+
124
+ ```ruby
125
+ logger = SchwabRb::Logger.logger
126
+ logger.info("Custom log message")
127
+ ```
128
+
129
+ ## Configuration Reset
130
+
131
+ Reset the logger and configuration:
132
+
133
+ ```ruby
134
+ # Reset configuration to defaults
135
+ SchwabRb.reset_configuration!
136
+
137
+ # Reset logger instance (forces recreation)
138
+ SchwabRb::Logger.reset!
139
+ ```
140
+
141
+ Note: The logger is automatically reset when configuration changes are made via `SchwabRb.configure`.
data/doc/QUICK_START.md CHANGED
@@ -33,6 +33,12 @@ SCHWAB_APP_CALLBACK_URL=https://127.0.0.1:8182
33
33
  SCHWAB_TOKEN_PATH=~/.schwab_rb/token.json
34
34
  ```
35
35
 
36
+ Or authenticate with the CLI:
37
+
38
+ ```bash
39
+ schwab_rb login
40
+ ```
41
+
36
42
  ## 4. Initialize Client (First Time)
37
43
 
38
44
  ```ruby
@@ -70,6 +76,15 @@ accounts.each do |account|
70
76
  end
71
77
  ```
72
78
 
79
+ Or download price history directly from the CLI:
80
+
81
+ ```bash
82
+ schwab_rb price-history --symbol AAPL --start-date 2026-03-01
83
+ schwab_rb price-history --symbol VIX --start-date 2026-03-01 --end-date 2026-03-24 --freq 1min --format csv
84
+ ```
85
+
86
+ The CLI stores candle files using `SYMBOL_FREQ.format`, such as `AAPL_day.json` or `VIX_1min.csv`, and will reuse an existing file instead of re-downloading ranges it already has.
87
+
73
88
  ## 6. Set Up Account Names (Optional but Recommended)
74
89
 
75
90
  Create `~/.schwab_rb/account_names.json`:
data/exe/schwab_rb ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "dotenv/load"
5
+ require "schwab_rb"
6
+
7
+ exit SchwabRb::CLI::App.new.call(ARGV.dup)
@@ -3,6 +3,7 @@
3
3
  require "oauth2"
4
4
  require_relative "init_client_token_file"
5
5
  require_relative "init_client_login"
6
+ require_relative "../path_support"
6
7
 
7
8
  module SchwabRb
8
9
  module Auth
@@ -17,6 +18,7 @@ module SchwabRb
17
18
  interactive: true,
18
19
  requested_browser: nil
19
20
  )
21
+ token_path = SchwabRb::PathSupport.expand_path(token_path)
20
22
 
21
23
  raise OAuth2::Error, "No token found" unless File.exist?(token_path)
22
24
 
@@ -5,6 +5,7 @@ require "uri"
5
5
  require "net/http"
6
6
  require "json"
7
7
  require "oauth2"
8
+ require_relative "../path_support"
8
9
  # require 'logger'
9
10
 
10
11
  module SchwabRb
@@ -71,6 +72,7 @@ module SchwabRb
71
72
  interactive: true,
72
73
  requested_browser: nil
73
74
  )
75
+ token_path = SchwabRb::PathSupport.expand_path(token_path)
74
76
 
75
77
  callback_timeout = if !callback_timeout
76
78
  0
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "oauth2"
4
+ require_relative "../path_support"
4
5
 
5
6
  module SchwabRb
6
7
  module Auth
7
8
  def self.init_client_token_file(api_key, app_secret, token_path, enforce_enums: true)
9
+ token_path = SchwabRb::PathSupport.expand_path(token_path)
10
+
8
11
  oauth = OAuth2::Client.new(
9
12
  api_key,
10
13
  app_secret,
@@ -2,12 +2,15 @@
2
2
 
3
3
  require "oauth2"
4
4
  require "json"
5
+ require_relative "../constants"
6
+ require_relative "../path_support"
5
7
 
6
8
  module SchwabRb
7
9
  module Auth
8
10
  class TokenManager
9
11
  class << self
10
12
  def from_file(token_path)
13
+ token_path = SchwabRb::PathSupport.expand_path(token_path)
11
14
  token_data = JSON.parse(File.read(token_path))
12
15
  token = SchwabRb::Auth::Token.new(
13
16
  token: token_data["token"]["access_token"],
@@ -40,7 +43,7 @@ module SchwabRb
40
43
  def initialize(token, timestamp, token_path: SchwabRb::Constants::DEFAULT_TOKEN_PATH)
41
44
  @token = token
42
45
  @timestamp = timestamp
43
- @token_path = token_path
46
+ @token_path = SchwabRb::PathSupport.expand_path(token_path)
44
47
  end
45
48
 
46
49
  attr_reader :token, :timestamp, :token_path
@@ -77,6 +80,7 @@ module SchwabRb
77
80
  end
78
81
 
79
82
  def to_file
83
+ SchwabRb::PathSupport.ensure_parent_directory(token_path)
80
84
  File.write(token_path, to_json)
81
85
  end
82
86