Skip to content

feat: Merge post-v1.0.0 changes from sandbox (v1.1.0+)#67

Merged
ivyleavedtoadflax merged 75 commits intomainfrom
sandbox-post-v1.0.0
Jan 18, 2026
Merged

feat: Merge post-v1.0.0 changes from sandbox (v1.1.0+)#67
ivyleavedtoadflax merged 75 commits intomainfrom
sandbox-post-v1.0.0

Conversation

@ivyleavedtoadflax
Copy link
Owner

Summary

This PR merges all changes from the remote.py-sandbox repository made after v1.0.0, including the v1.1.0 release and subsequent improvements. These 75 commits bring:

New Features

  • Pydantic config validation with environment variable support
  • Built-in watch mode for instance status command
  • SSH key configuration - connect command uses ssh_key_path from config
  • Scheduled instance shutdown via SSH (instance shutdown command)
  • Instance cost tracking - new instance cost command and --cost flag on instance ls
  • Region fallback for AWS pricing API
  • Differentiated ls vs status commands for instances
  • Connect command enhancements - --start/--no-start flags to auto-start stopped instances
  • Standardized console output styles for ECS commands
  • Rich formatting enhancements throughout

Bug Fixes

  • Correct config key ssh_key_path in connect command
  • EU region location names in pricing API
  • Rich Panel width expansion issues
  • Console width consistency in config module

Refactoring

  • Centralized console initialization in utils.py
  • Extracted SSH command builder helper
  • Removed deprecated client shims and unused functions
  • Standardized Typer parameter styles and docstrings
  • Replaced silent/broad exception handlers with specific handling
  • Extracted hardcoded constants to named settings
  • Simplified config path assignments

Testing

  • Added AWS API contract validation tests
  • Comprehensive test coverage improvements (227+ tests)

Other

  • Added gitleaks to pre-commit hooks for secret detection
  • v1.1.0 release preparation

Test plan

  • Run full test suite: uv run pytest
  • Verify code quality: uv run ruff check .
  • Test core commands manually

claude and others added 30 commits January 18, 2026 22:03
- Add pydantic-settings dependency for config validation
- Create RemoteConfig Pydantic model with field validators:
  - instance_name: alphanumeric, hyphens, underscores, dots
  - ssh_user: alphanumeric, hyphens, underscores
  - ssh_key_path: expands ~ to home directory
  - aws_region: validates AWS region format (e.g., us-east-1)
- Support environment variable overrides (REMOTE_ prefix)
- Create ConfigValidationResult for structured validation output
- Update ConfigManager to use Pydantic validation internally
- Enhance 'remote config validate' command with Pydantic validation
- Add 25 new tests for Pydantic config features
- Fix specs/readme.md: mark issue-23 as COMPLETED, update mypy path

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 1f56cba)
Add --watch/-w and --interval/-i flags to the status command to enable
continuous monitoring of instance status using Rich's Live display.
This resolves garbled output issues when using the external `watch`
command with Rich formatting.

- Add _build_status_table helper function for reusable table generation
- Add _watch_status function using Rich Live display with screen mode
- Validate interval must be at least 1 second
- Handle Ctrl+C gracefully to exit watch mode
- Add comprehensive tests for watch mode functionality

Closes #35

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit d2ccacc)
- Remove unused `cfg: ConfigParser | None = None` parameter from get_instance_name()
- Remove corresponding docstring documentation for the parameter
- Remove now-unused `from configparser import ConfigParser` import

The parameter was marked as "for backward compatibility" but was never used
in the function body (it always used config_manager instead), and none of
the 8 call sites passed any arguments.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 24a886a)
The connect() function was using "ssh_key" as the config key when
retrieving the SSH key path, but the valid config key is "ssh_key_path".
This caused the SSH key configuration to fail silently.

- Fix get_value() call to use "ssh_key_path" instead of "ssh_key"
- Update help text to reference the correct config key name

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit d9b35f5)
Panel stretches beyond console width and has redundant messaging.

(cherry picked from commit 9f3116b)
The builtins module was imported only to use builtins.list for a type
annotation. In Python 3.9+, list can be used directly in type annotations
without the builtins prefix. This change removes the unnecessary import
and simplifies the type annotation.

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit 1fc7119)
The utils.py module defined `app = typer.Typer()` but this app instance
was never used - no commands were registered to it and no other modules
imported it. The typer import itself is still needed for other uses
(typer.Exit, typer.secho, typer.colors).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit f57850d)
The get_sts_client() function was defined as a cached client factory but
was never used. The get_account_id() function created a new STS client
directly with boto3.client("sts") instead.

This change makes the code consistent with the EC2 client pattern where
get_ec2_client() is used throughout the codebase, and utilizes the
caching provided by @lru_cache.

Also updates tests to mock get_sts_client instead of boto3.client.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 8daac71)
- Issue 37: Fallback to us-east-1 for pricing API in other regions
- Issue 38: Add command to show instance cost based on uptime

(cherry picked from commit 55af72a)
- Remove width=200 from Console initialization in config.py to use
  terminal's actual width instead of stretching beyond it
- Simplify validation output to show single status message instead
  of redundant "All checks passed" + "Status: Valid"
- Update test assertion to match new output message

Closes #36

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit acd655a)
Add spec for feature to schedule instance stops after a duration,
e.g., `remote instance stop --in 3h`

(cherry picked from commit 01adf6b)
Use SSH to run `shutdown -h +N` on the instance instead of a
local subprocess. This is simpler and survives local disconnects.

(cherry picked from commit f474c02)
When a user's region is not in the region-to-location mapping, pricing
lookups now fall back to us-east-1 instead of returning None. This
provides users in less common regions with an estimated price rather
than no price at all.

- Add get_instance_price_with_fallback() function that returns (price, fallback_used)
- Update get_instance_pricing_info() to include fallback_used indicator
- Update instance list command to use fallback pricing
- Add comprehensive tests for fallback behavior

Closes #37

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit 875cc9d)
Add --in option to stop command to schedule shutdown after duration
Add --stop-in option to start command to auto-stop after duration
Add --cancel flag to cancel scheduled shutdowns

Features:
- Parse duration strings (e.g., 3h, 30m, 1h30m)
- SSH to instance and run 'shutdown -h +N'
- Uses ssh_user and ssh_key_path from config
- Handles SSH errors gracefully

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 32d8144)
Standardize all command output styles around `config show` format.

(cherry picked from commit a8bb264)
Update ECS list commands to use Rich Tables instead of plain line-by-line
output, matching the consistent style used across other modules.

Changes:
- list-clusters: Now displays clusters in a Rich Table with name and ARN
- list-services: Now displays services in a Rich Table with name and ARN
- prompt_for_cluster_name: Changed typer.echo to typer.secho for consistency
- prompt_for_services_name: Changed typer.echo to typer.secho for consistency

Closes #40

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 8adb22f)
#25)

Adds a new `remote instance cost` command that displays the estimated cost
of a running instance based on its uptime and hourly pricing rate.

Features:
- Shows instance ID, type, status, launch time, and uptime
- Calculates estimated cost using hourly rate × uptime hours
- Uses fallback to us-east-1 pricing for unsupported regions
- Handles non-running instances and unavailable pricing gracefully

Closes #38

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit ab6e4f9)
Issue 41: Fix instance cost (not displaying, panel too wide, integrate into ls)
Issue 42: Evaluate overlap between instance ls and status commands

(cherry picked from commit 7c6af04)
Emphasize need for Typer CLI tests to catch cost display issues.

(cherry picked from commit 88852c7)
- Add --cost/-c flag to instance ls to show cost columns
- Add Uptime, $/hr, and Est. Cost columns when --cost is used
- Remove separate instance cost command (functionality now in ls)
- Remove _get_instance_details helper (no longer needed)
- Add _get_raw_launch_times helper for extracting launch times
- Add comprehensive tests for cost flag functionality
- Update plan.md and spec status to COMPLETED

The cost information is now opt-in rather than shown by default,
which improves performance since pricing API calls are skipped
unless explicitly requested.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 3980be0)
PR #26 did not fix the pricing lookup issue. Cost shows "-" in
real usage despite tests passing. Need to investigate mismatch
between mocked tests and actual AWS pricing API behavior.

(cherry picked from commit fccab83)
Clarify the distinct purposes of instance ls (summary view of all instances)
vs instance status (detailed view of one instance).

Changes:
- Enhanced instance status to show comprehensive details including network
  config, security groups, key pair, tags, and health status
- Updated help text for both commands to clearly indicate when to use each
- instance status now uses a Rich Panel instead of Table for better formatting
- Health status section only shown for running instances

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit d3093e7)
The loop index was unused, making enumerate() unnecessary.
Simplified to a plain for loop over reservations.

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit b37effb)
The drop_nameless parameter was defined but never used - the function
always skips instances without a Name tag regardless of its value.
This was misleading since the default value of False implied nameless
instances would be included.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 0d14e97)
The module-level `ec2_client` attribute was deprecated and scheduled
for removal in v0.5.0. After scanning the codebase, no code uses this
deprecated attribute - all modules use `get_ec2_client()` directly.

Remove:
- Deprecation comment block
- `__getattr__` function providing lazy access to `ec2_client`

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit c8885ac)
The __getattr__ function providing lazy access to ecs_client as a
module-level attribute was dead code. All ECS functions use
get_ecs_client() directly, and no imports of ecs_client exist in
the codebase.

Also removes the unused Any type import.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit c361dcb)
The ENV_PREFIX constant was defined but never used. The actual env
prefix is hardcoded in RemoteConfig.model_config as env_prefix="REMOTE_".

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit aae94bd)
The stop() function used parameter name 'in_duration' while the start()
function used 'stop_in' for the same purpose (scheduling automatic
shutdown). This inconsistency created cognitive overhead.

Renamed the parameter to 'stop_in' to match the pattern used in start().
The CLI flag '--in' remains unchanged for backwards compatibility.

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit 5e34587)
Recurring issue: Panels expand to full terminal width.
Audit all Panel usage and set expand=False globally.

(cherry picked from commit 209b796)
…-in (#34)

Rename the `type()` command function to `instance_type()` with
`@app.command("type")` decorator to preserve CLI command name.
Rename the `type` parameter to `new_type` to avoid shadowing
Python's built-in type() function.

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit 89b9939)
claude and others added 28 commits January 18, 2026 22:04
The instance_type() function called get_instance_type() twice to fetch
the same value - once at function start (stored in current_type) and
again in the else branch (stored in current_instance_type). This made
an unnecessary AWS API call since current_type was already available.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 470c984)
…lates() (#48)

The exception handler `except (ResourceNotFoundError, Exception)` was
catching all exceptions silently, which could mask bugs. Changed to
`except (ResourceNotFoundError, AWSServiceError)` to match the documented
exceptions that get_launch_template_versions() can raise.

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit c3074d4)
…Typer command (#49)

The function returned a list value that was never consumed by callers since
it's a Typer CLI command. Changed return type to None and removed unused
Any import.

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit 9816412)
Replace `except Exception` with `except ValueError` in three locations:
- ConfigValidationResult.validate_config()
- ConfigManager.get_instance_name()
- ConfigManager.get_value()

Pydantic's ValidationError inherits from ValueError, so this catches
validation errors while avoiding the antipattern of catching all
exceptions indiscriminately.

Updated test to use ValueError instead of generic Exception.

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit c32ab4f)
Changed exception handling in ami.py's list_launch_templates() to display
a warning message instead of silently ignoring errors when fetching
launch template version details.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit bc7a4b5)
…ion (#52)

The launch() function was duplicated nearly identically (~130 lines) in both
ami.py and instance.py. This refactor:

- Adds launch_instance_from_template() to utils.py with all common logic
- Simplifies both launch() commands to thin wrappers calling shared function
- Updates test mocks to patch correct modules (remote.utils, remote.config)

Net change: -58 lines of code, single source of truth for launch logic.

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit 1cff43a)
Two typer.Exit() calls lacked explicit exit codes:
- prompt_for_cluster_name(): when no clusters found
- prompt_for_services_name(): when no services found

While typer.Exit() defaults to exit code 0, being explicit about
exit codes is best practice and consistent with other exit calls
in the codebase. Exit code 0 is correct here as these are
informational cases, not errors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 4b564d6)
Fix inconsistent docstring formatting to match the style used in
utils.py and other modules:

- Move descriptions to same line as opening triple quotes
- Add proper 4-space indentation to Args/Returns sections
- Remove redundant type annotations from docstrings (types are in
  function signatures)
- Remove type prefixes from Returns sections

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 27cd751)
The status() command in instance.py used the Annotated[] style for
parameter type hints while all other commands used the simpler inline
style. This inconsistency made the codebase harder to read and created
confusion about which style to follow.

Changes:
- Convert status() parameters from Annotated[] to inline style
- Remove unused Annotated import from typing

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit 15b75b6)
The function was dead code - only exercised by tests, never used in
application code. The instance list command uses get_instance_price_with_fallback()
directly rather than this higher-level wrapper.

- Remove get_instance_pricing_info() from remote/pricing.py
- Remove TestGetInstancePricingInfo from tests/test_pricing.py
- Update specs to reflect actual implementation

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit a50adcf)
Renamed `cfg` to `config` throughout the file for consistency.
The codebase mixed both names for ConfigParser objects - now
standardized on `config` which is more descriptive and matches
the pattern used in ConfigManager class methods.

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit 252f41d)
Remove dead code from five modules that contained unused main blocks:
- remote/ami.py
- remote/config.py
- remote/instance.py
- remote/snapshot.py
- remote/volume.py

These modules are library code imported into __main__.py, not executed
directly. The blocks were never executed since users run `remote <cmd>`
not `python -m remote.instance` etc.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit a618fe7)
The write_config() function returned its input ConfigParser object,
but no caller ever used the return value. Changed return type to None
and updated the corresponding test.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 9800551)
The function was defined in utils.py but never called by any production
code. It was only exercised by tests. Removes dead code and associated
tests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit b06bf72)
The validate_snapshot_id() function was defined in validation.py but
never called anywhere in the application. Remove this dead code along
with its associated tests to keep the codebase clean.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit fe92d81)
Move the "# If the instance is managed by Terraform, warn user" comment
from line 963 to line 981, placing it directly above the terraform_managed
assignment it describes. The comment was previously orphaned from its
relevant code by 20 lines of unrelated confirmation prompts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 8a7eb0d)
The unset_value() CLI command was directly manipulating the config file
instead of using the ConfigManager.remove_value() method like other
similar commands (set_value, add). This violated encapsulation and
missed proper state management (cache invalidation).

Also fixed ConfigManager.remove_value() to read from the specified
config_path parameter instead of the default path.

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit 8c0f8ca)
Replace verbose if-else blocks with ternary operators in
RemoteConfig.from_ini_file() and ConfigValidationResult.validate_config()
for cleaner, more idiomatic code (SIM108).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 190b0f5)
…_MONTH constant (#65)

The function and constant were defined but never used in production code:
- No code called get_monthly_estimate()
- HOURS_PER_MONTH was only used by get_monthly_estimate()
- Similar to previous removal of get_instance_pricing_info()

The application displays hourly prices directly without converting
to monthly estimates.

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit b55caaf)
…nfigManager (#66)

The get_instance_name() and get_value() methods had identical exception
handling blocks that displayed warnings for various config-related errors.
This duplicated ~12 lines of code.

Changes:
- Add _handle_config_error() helper method to centralize error handling
- Update both methods to use a single except clause delegating to helper
- Use union type syntax (X | Y) in isinstance checks per UP038 lint rule

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit dc66c51)
The get_instance_ids() function had inconsistent filtering behavior
compared to get_instance_info():

- get_instance_info() iterates through ALL instances and filters out
  those without a Name tag
- get_instance_ids() only took the FIRST instance from each reservation
  and did NOT filter by Name tag

This could cause array length mismatches when used together. The code
worked around this with strict=False in zip() calls, silently truncating
to the shortest array.

Changes:
- Update get_instance_ids() to iterate all instances and filter by Name tag
- Change strict=False to strict=True in zip calls in instance.py and config.py
- Add test for the new filtering behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 16147a7)
…#68)

The get_value() CLI command bypassed ConfigManager.get_value() and read
directly from config file. This was inconsistent with other CLI commands
(set_value, add, unset_value) which properly use ConfigManager.

Changes:
- Renamed function to get_value_cmd to avoid shadowing
- Added key validation against VALID_KEYS (consistent with set_value)
- For default path: delegate to config_manager.get_value()
- For custom paths: continue reading directly from file

Benefits:
- Pydantic validation for config values
- REMOTE_* environment variable override support
- Key validation (rejects unknown keys)
- Consistent API with other config commands

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit 3bec8ed)
Replace magic numbers with named constants for better readability:
- SECONDS_PER_MINUTE, SECONDS_PER_HOUR, MINUTES_PER_DAY
- MAX_STARTUP_WAIT_SECONDS, STARTUP_POLL_INTERVAL_SECONDS
- CONNECTION_RETRY_SLEEP_SECONDS, MAX_CONNECTION_ATTEMPTS

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit f4640f9)
The _format_uptime() function used SECONDS_PER_MINUTE (60) to perform
arithmetic on values measured in minutes. While mathematically correct,
this was semantically misleading. Added MINUTES_PER_HOUR constant and
updated MINUTES_PER_DAY to use it for consistency.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 9144511)
Add SSH_READINESS_WAIT_SECONDS constant (10s) for the hardcoded
sleep times used when waiting for SSH to become ready after
instance startup. This follows the established pattern of
extracting time-related magic numbers to named constants.

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit ef46b1a)
Add TYPE_CHANGE_MAX_POLL_ATTEMPTS and TYPE_CHANGE_POLL_INTERVAL_SECONDS
constants to replace hardcoded `5` values in instance_type() function.
This follows the established pattern for time-related constants.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 468117d)
When connecting to a stopped instance:
- --start: Automatically starts the instance without prompting
- --no-start: Fails immediately with a helpful message
- Non-interactive mode (no TTY): Fails with helpful message

Also:
- Refactored start command to extract _start_instance() internal function
- Added comprehensive tests for new behavior
- Fixed typo: "trying to starting" -> "trying to start"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 48f9a1d)
@ivyleavedtoadflax ivyleavedtoadflax merged commit 7e0acad into main Jan 18, 2026
3 checks passed
@ivyleavedtoadflax ivyleavedtoadflax deleted the sandbox-post-v1.0.0 branch January 18, 2026 21:08
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