cloudx-proxy 0.17.2__tar.gz → 0.17.4__tar.gz

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.
Files changed (54) hide show
  1. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.ai/context/development.md +26 -0
  2. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.ai/context/ssh-config.md +34 -3
  3. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/CHANGELOG.md +22 -0
  4. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/PKG-INFO +30 -8
  5. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/README.md +29 -7
  6. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy/_version.py +2 -2
  7. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy/cli.py +32 -13
  8. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy/setup.py +450 -98
  9. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy.egg-info/PKG-INFO +30 -8
  10. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/tests/test_backward_compat.py +54 -10
  11. cloudx_proxy-0.17.4/tests/test_cli_options.py +418 -0
  12. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/tests/test_runtime_safety.py +83 -1
  13. cloudx_proxy-0.17.4/tests/test_ssh_config.py +919 -0
  14. cloudx_proxy-0.17.2/tests/test_cli_options.py +0 -65
  15. cloudx_proxy-0.17.2/tests/test_ssh_config.py +0 -470
  16. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.ai/README.md +0 -0
  17. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.ai/context/README.md +0 -0
  18. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.ai/context/architecture.md +0 -0
  19. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.ai/context/commit-conventions.md +0 -0
  20. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.ai/context/project-overview.md +0 -0
  21. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.ai/roadmap/architecture-review.md +0 -0
  22. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.ai/roadmap/documentation-findings.md +0 -0
  23. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.ai/roadmap/improvement-roadmap.md +0 -0
  24. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.clinerules +0 -0
  25. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.envrc +0 -0
  26. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.github/dependabot.yml +0 -0
  27. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.github/workflows/claude.yml +0 -0
  28. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.github/workflows/codeql.yml +0 -0
  29. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.github/workflows/release.yml +0 -0
  30. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.github/workflows/test.yml +0 -0
  31. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.gitignore +0 -0
  32. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/.releaserc +0 -0
  33. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/CLAUDE.md +0 -0
  34. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/CONTRIBUTING.md +0 -0
  35. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/LICENSE +0 -0
  36. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/NOTICE +0 -0
  37. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy/__init__.py +0 -0
  38. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy/_op.py +0 -0
  39. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy/colors.py +0 -0
  40. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy/core.py +0 -0
  41. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy.egg-info/SOURCES.txt +0 -0
  42. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy.egg-info/dependency_links.txt +0 -0
  43. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy.egg-info/entry_points.txt +0 -0
  44. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy.egg-info/requires.txt +0 -0
  45. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/cloudx_proxy.egg-info/top_level.txt +0 -0
  46. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/pyproject.toml +0 -0
  47. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/setup.cfg +0 -0
  48. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/tests/__init__.py +0 -0
  49. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/tests/test_config_safety.py +0 -0
  50. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/tests/test_core.py +0 -0
  51. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/tests/test_non_interactive.py +0 -0
  52. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/tests/test_setup.py +0 -0
  53. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/tests/test_version.py +0 -0
  54. {cloudx_proxy-0.17.2 → cloudx_proxy-0.17.4}/uv.lock +0 -0
@@ -104,6 +104,32 @@ The project uses semantic-release with GitHub Actions:
104
104
  - `feat:` → minor version
105
105
  - `fix:`, `docs:`, `style:`, etc. → patch version
106
106
 
107
+ ## Console Output
108
+
109
+ Three indents and three symbols, used the same way everywhere, so a run can be
110
+ skimmed down the left edge. The rule lives next to `CloudXSetup.print_status`
111
+ in `cloudx_proxy/setup.py`; `tests/test_cli_options.py` enforces it.
112
+
113
+ ```
114
+ === Section === a phase of the command (print_header)
115
+ ○ Doing something... STEP: one operation within the section
116
+ ✓ It worked DETAIL: what that step found or did
117
+ ○ ... SUB: detail of a nested operation
118
+ ```
119
+
120
+ - `○` neutral - about to happen, in progress, informational, or a dry-run
121
+ preview
122
+ - `✓` true now - succeeded, exists, verified
123
+ - `✗` wrong - failed, missing, invalid
124
+
125
+ Indents are 0, 2 and 4; nothing else. A detail must have a step above it, and a
126
+ sub-detail a detail. A ✗ marks the *outcome*: a condition the code goes on to
127
+ recover from is `○`, so one failure shows as one ✗ rather than one per line on
128
+ the way there.
129
+
130
+ `print_header` emits a single blank line before the header, and callers printing
131
+ a banner above the first section add none of their own - the two used to stack.
132
+
107
133
  ## Publishing to PyPI
108
134
 
109
135
  The package is automatically published to PyPI via GitHub Actions when a new release is created. Setup:
@@ -66,7 +66,14 @@ Host cloudX-Prod-*
66
66
 
67
67
  **Settings explained**:
68
68
  - `IdentityFile`: Path to the SSH private key for this environment
69
- - `IdentitiesOnly yes`: Only use the specified key, don't try others from ssh-agent
69
+ - `IdentitiesOnly yes`: Only use the specified key, don't try others from ssh-agent.
70
+ With 1Password this is why `IdentityFile` names the *public* key: ssh offers only
71
+ identities named by `IdentityFile`, even when the agent holds more, so the `.pub`
72
+ is what lets the agent's copy of the key be used at all.
73
+ - `IdentityAgent`: Only written with `--1password`, and only where the agent has to be
74
+ named: `~/.1password/agent.sock` on macOS and Linux (or the snap path on snap
75
+ installs). Nothing is written on Windows, where 1Password serves the standard
76
+ OpenSSH named pipe `\\.\pipe\openssh-ssh-agent` that ssh already uses by default.
70
77
  - `ProxyCommand`: The cloudX-proxy connect command that:
71
78
  - Checks if the instance is running (starts it if needed)
72
79
  - Pushes the SSH public key to the instance
@@ -101,13 +108,37 @@ Host cloudX-Prod-foobar
101
108
  - The `Environment` tag determines the environment part
102
109
  - The `Name` tag (or user-specified hostname) determines the hostname part
103
110
 
111
+ ## Prefix Case
112
+
113
+ ssh matches `Host` **patterns** case-sensitively, and the pattern syntax supports
114
+ only `*` and `?` - `cloud[xX]-*` is a literal hostname, not a character class. A
115
+ block written as `Host cloudX-*` therefore does not apply to a host entry spelled
116
+ `cloudx-dev-web1`, and both spellings exist in the wild (two command names, older
117
+ releases, hand edits).
118
+
119
+ Wildcard blocks are consequently written with one pattern per prefix spelling -
120
+ `Host cloudX-* cloudx-*` - since a `Host` line takes any number of patterns and
121
+ matches if any one of them does. The configured spelling always comes first.
122
+
123
+ Only the prefix varies between the patterns. The environment part is set by
124
+ whoever rolled out the environment stack and the host part is chosen by the user;
125
+ both are written exactly as given.
126
+
127
+ Host entries are never rewritten. `cloudX` is the product's name - the X is ten,
128
+ after Cloud9 - but people who would rather not reach for shift call their instance
129
+ `cloudx-dev-something`, and that name is theirs: it is what they type, what `list`
130
+ reports and what VSCode offers. The widened wildcard blocks match it either way, so
131
+ it does not need renaming to inherit its settings. Re-running `setup` for an
132
+ existing host updates its `HostName` and keeps its name; only a brand new entry is
133
+ written in the configured case.
134
+
104
135
  ## Configuration Inheritance
105
136
 
106
137
  SSH applies configurations from most specific to least specific. When connecting to `cloudX-Prod-foobar`:
107
138
 
108
139
  1. **Host cloudX-Prod-foobar** matches first → sets `HostName`
109
- 2. **Host cloudX-Prod-*** matches next → sets `IdentityFile`, `IdentitiesOnly`, `ProxyCommand`
110
- 3. **Host cloudX-*** matches last → sets `User`, `TCPKeepAlive`, `Control*`
140
+ 2. **Host cloudX-Prod-* cloudx-Prod-*** matches next → sets `IdentityFile`, `IdentitiesOnly`, `ProxyCommand`
141
+ 3. **Host cloudX-* cloudx-*** matches last → sets `User`, `TCPKeepAlive`, `Control*`
111
142
 
112
143
  The result is a fully configured connection with all necessary settings.
113
144
 
@@ -1,3 +1,25 @@
1
+ ## [0.17.4](https://github.com/easytocloud/cloudX-proxy/compare/v0.17.3...v0.17.4) (2026-09-06)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * cleanup no longer deletes ProxyCommand flags ([5958bba](https://github.com/easytocloud/cloudX-proxy/commit/5958bbaa4db313d0ec4cd5aa045f5a39f08a0ec3))
7
+ * detect the command name on Windows too ([3f537c4](https://github.com/easytocloud/cloudX-proxy/commit/3f537c44c080e5eb8b7d66f02458bd2874bc379e))
8
+ * dry run previews the patterns it will actually write ([d0911ac](https://github.com/easytocloud/cloudX-proxy/commit/d0911acdd24e80d3097654842516638ecf93382f))
9
+ * find the 1Password SSH agent per platform ([8ae4860](https://github.com/easytocloud/cloudX-proxy/commit/8ae48605643daded0f40850a5e53446940da2fe4))
10
+ * list shows patterns in the preferred spelling ([0c49ec5](https://github.com/easytocloud/cloudX-proxy/commit/0c49ec5202c4dda1c6e43a42c5cc15e5d10efb09))
11
+ * never rename a host entry to the command name's case ([1b19d32](https://github.com/easytocloud/cloudX-proxy/commit/1b19d329b73484dc0601b7393968184c22dab62e))
12
+ * stop stacking blank lines, and name the host that was written ([3e24b87](https://github.com/easytocloud/cloudX-proxy/commit/3e24b87e55fa67dc7587909180af210f12f25bc3))
13
+ * wildcard Host blocks match every spelling of the prefix ([4532ecf](https://github.com/easytocloud/cloudX-proxy/commit/4532ecf97d31c4cfa06181a1c19675b53bf56c31))
14
+ * write managed Host lines in one prefix case ([b551fe1](https://github.com/easytocloud/cloudX-proxy/commit/b551fe17325f4cbfb742eff980c833416d3208bc))
15
+
16
+ ## [0.17.3](https://github.com/easytocloud/cloudX-proxy/compare/v0.17.2...v0.17.3) (2026-09-04)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * connectivity check reports false failure on healthy connections ([29e3717](https://github.com/easytocloud/cloudX-proxy/commit/29e3717d4d3d85f0f37290d597d79a3e99b6e637))
22
+
1
23
  ## [0.17.2](https://github.com/easytocloud/cloudX-proxy/compare/v0.17.1...v0.17.2) (2026-09-04)
2
24
 
3
25
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudx-proxy
3
- Version: 0.17.2
3
+ Version: 0.17.4
4
4
  Summary: SSH proxy command to connect VSCode with cloudX instance using AWS Systems Manager
5
5
  Author-email: easytocloud <info@easytocloud.com>
6
6
  License: MIT License
@@ -304,7 +304,7 @@ Will create a three-tier configuration structure like this:
304
304
  # Generic configuration (shared by all environments)
305
305
  # Created by cloudX-proxy v1.0.0 on 2025-03-07 09:05:23
306
306
  # Configuration type: generic
307
- Host cloudX-*
307
+ Host cloudX-* cloudx-*
308
308
  User ec2-user
309
309
  TCPKeepAlive yes
310
310
  ControlMaster auto
@@ -314,7 +314,7 @@ Host cloudX-*
314
314
  # Environment configuration (specific to a single environment)
315
315
  # Created by cloudX-proxy v1.0.0 on 2025-03-07 09:05:23
316
316
  # Configuration type: environment
317
- Host cloudX-dev-*
317
+ Host cloudX-dev-* cloudx-dev-*
318
318
  IdentityFile ~/.ssh/cloudX/mykey
319
319
  IdentitiesOnly yes
320
320
  ProxyCommand uvx cloudX-proxy connect %h %p --profile myprofile --ssh-key mykey --ssh-dir ~/.ssh/cloudX
@@ -481,8 +481,23 @@ Understanding the connection flow helps with troubleshooting and explains why ce
481
481
  ### Command Line Options
482
482
 
483
483
  > **Note:** Both `cloudX-proxy` (preferred) and `cloudx-proxy` command names are available. The command name determines the prefix case used in SSH configurations:
484
- > - `cloudX-proxy` generates `Host cloudX-*` patterns (preferred)
485
- > - `cloudx-proxy` generates `Host cloudx-*` patterns
484
+ > - `cloudX-proxy` generates `Host cloudX-* cloudx-*` patterns (preferred)
485
+ > - `cloudx-proxy` generates `Host cloudx-* cloudX-*` patterns
486
+ >
487
+ > ssh matches `Host` patterns case-sensitively, and its pattern syntax has only
488
+ > `*` and `?` - there is no `[xX]` character class. Wildcard blocks therefore list
489
+ > both spellings, so a config that mixes them keeps working; the first pattern is
490
+ > the one the command name chose.
491
+ >
492
+ > Only the prefix varies. The environment part is fixed by whoever rolled out the
493
+ > environment stack, and the host part is chosen by the user - both are written
494
+ > exactly as given. An instance called `cloudx-dev-web1` keeps that name and still
495
+ > picks up its settings from `Host cloudX-dev-* cloudx-dev-*`.
496
+ >
497
+ > `list` reflects that split: hosts appear under the names their owners gave them,
498
+ > while patterns are shown as `cloudX-*` whichever command name you type, since
499
+ > `cloudX` is the preferred spelling. An environment with no hosts of its own is
500
+ > listed only as a pattern (under `--detailed`), not as an environment.
486
501
  >
487
502
  > Run `cleanup` with your preferred command to normalize existing configurations.
488
503
 
@@ -620,8 +635,13 @@ uvx cloudX-proxy cleanup --ssh-config ~/.ssh/custom/config
620
635
  ```
621
636
 
622
637
  **Prefix Normalization:** The cleanup command normalizes all host patterns and ProxyCommand references to match the command name used:
623
- - Running `cloudX-proxy cleanup` converts `Host cloudx-*` → `Host cloudX-*` and `uvx cloudx-proxy` → `uvx cloudX-proxy`
624
- - Running `cloudx-proxy cleanup` converts `Host cloudX-*` → `Host cloudx-*` and `uvx cloudX-proxy` → `uvx cloudx-proxy`
638
+ - Running `cloudX-proxy cleanup` converts `Host cloudx-*` → `Host cloudX-* cloudx-*` and `uvx cloudx-proxy` → `uvx cloudX-proxy`
639
+ - Running `cloudx-proxy cleanup` converts `Host cloudX-*` → `Host cloudx-* cloudX-*` and `uvx cloudX-proxy` → `uvx cloudx-proxy`
640
+
641
+ Wildcard blocks keep both spellings so that host entries in either case pick up
642
+ `User`, `IdentitiesOnly` and the `ProxyCommand`. Host entries are **not** renamed:
643
+ what an instance is called belongs to whoever created it. Re-running `setup` for a
644
+ host that already exists updates its instance id and leaves its name alone.
625
645
 
626
646
  This allows users to easily convert between naming conventions. The preferred convention is `cloudX` (uppercase X).
627
647
 
@@ -727,7 +747,9 @@ These permissions are required to bootstrap the instance, so that after creation
727
747
  - **Region mismatch** - Ensure AWS profile region matches instance location
728
748
 
729
749
  4. **SSH Key Issues**
730
- - If using 1Password SSH agent, verify agent is running (~/.1password/agent.sock exists)
750
+ - If using 1Password SSH agent, verify the agent is running. Where it lives depends on the platform:
751
+ * macOS/Linux: `~/.1password/agent.sock` exists (Linux snap installs: `~/snap/1password/current/.1password/agent.sock`)
752
+ * Windows: 1Password serves the standard OpenSSH named pipe `\\.\pipe\openssh-ssh-agent`, which ssh uses by default - there is no socket file and no `IdentityAgent` line is written
731
753
  - Check file permissions (600 for private key, 644 for public key)
732
754
  - Verify the public key is being successfully pushed to the instance
733
755
  - For 1Password-managed keys, make sure:
@@ -252,7 +252,7 @@ Will create a three-tier configuration structure like this:
252
252
  # Generic configuration (shared by all environments)
253
253
  # Created by cloudX-proxy v1.0.0 on 2025-03-07 09:05:23
254
254
  # Configuration type: generic
255
- Host cloudX-*
255
+ Host cloudX-* cloudx-*
256
256
  User ec2-user
257
257
  TCPKeepAlive yes
258
258
  ControlMaster auto
@@ -262,7 +262,7 @@ Host cloudX-*
262
262
  # Environment configuration (specific to a single environment)
263
263
  # Created by cloudX-proxy v1.0.0 on 2025-03-07 09:05:23
264
264
  # Configuration type: environment
265
- Host cloudX-dev-*
265
+ Host cloudX-dev-* cloudx-dev-*
266
266
  IdentityFile ~/.ssh/cloudX/mykey
267
267
  IdentitiesOnly yes
268
268
  ProxyCommand uvx cloudX-proxy connect %h %p --profile myprofile --ssh-key mykey --ssh-dir ~/.ssh/cloudX
@@ -429,8 +429,23 @@ Understanding the connection flow helps with troubleshooting and explains why ce
429
429
  ### Command Line Options
430
430
 
431
431
  > **Note:** Both `cloudX-proxy` (preferred) and `cloudx-proxy` command names are available. The command name determines the prefix case used in SSH configurations:
432
- > - `cloudX-proxy` generates `Host cloudX-*` patterns (preferred)
433
- > - `cloudx-proxy` generates `Host cloudx-*` patterns
432
+ > - `cloudX-proxy` generates `Host cloudX-* cloudx-*` patterns (preferred)
433
+ > - `cloudx-proxy` generates `Host cloudx-* cloudX-*` patterns
434
+ >
435
+ > ssh matches `Host` patterns case-sensitively, and its pattern syntax has only
436
+ > `*` and `?` - there is no `[xX]` character class. Wildcard blocks therefore list
437
+ > both spellings, so a config that mixes them keeps working; the first pattern is
438
+ > the one the command name chose.
439
+ >
440
+ > Only the prefix varies. The environment part is fixed by whoever rolled out the
441
+ > environment stack, and the host part is chosen by the user - both are written
442
+ > exactly as given. An instance called `cloudx-dev-web1` keeps that name and still
443
+ > picks up its settings from `Host cloudX-dev-* cloudx-dev-*`.
444
+ >
445
+ > `list` reflects that split: hosts appear under the names their owners gave them,
446
+ > while patterns are shown as `cloudX-*` whichever command name you type, since
447
+ > `cloudX` is the preferred spelling. An environment with no hosts of its own is
448
+ > listed only as a pattern (under `--detailed`), not as an environment.
434
449
  >
435
450
  > Run `cleanup` with your preferred command to normalize existing configurations.
436
451
 
@@ -568,8 +583,13 @@ uvx cloudX-proxy cleanup --ssh-config ~/.ssh/custom/config
568
583
  ```
569
584
 
570
585
  **Prefix Normalization:** The cleanup command normalizes all host patterns and ProxyCommand references to match the command name used:
571
- - Running `cloudX-proxy cleanup` converts `Host cloudx-*` → `Host cloudX-*` and `uvx cloudx-proxy` → `uvx cloudX-proxy`
572
- - Running `cloudx-proxy cleanup` converts `Host cloudX-*` → `Host cloudx-*` and `uvx cloudX-proxy` → `uvx cloudx-proxy`
586
+ - Running `cloudX-proxy cleanup` converts `Host cloudx-*` → `Host cloudX-* cloudx-*` and `uvx cloudx-proxy` → `uvx cloudX-proxy`
587
+ - Running `cloudx-proxy cleanup` converts `Host cloudX-*` → `Host cloudx-* cloudX-*` and `uvx cloudX-proxy` → `uvx cloudx-proxy`
588
+
589
+ Wildcard blocks keep both spellings so that host entries in either case pick up
590
+ `User`, `IdentitiesOnly` and the `ProxyCommand`. Host entries are **not** renamed:
591
+ what an instance is called belongs to whoever created it. Re-running `setup` for a
592
+ host that already exists updates its instance id and leaves its name alone.
573
593
 
574
594
  This allows users to easily convert between naming conventions. The preferred convention is `cloudX` (uppercase X).
575
595
 
@@ -675,7 +695,9 @@ These permissions are required to bootstrap the instance, so that after creation
675
695
  - **Region mismatch** - Ensure AWS profile region matches instance location
676
696
 
677
697
  4. **SSH Key Issues**
678
- - If using 1Password SSH agent, verify agent is running (~/.1password/agent.sock exists)
698
+ - If using 1Password SSH agent, verify the agent is running. Where it lives depends on the platform:
699
+ * macOS/Linux: `~/.1password/agent.sock` exists (Linux snap installs: `~/snap/1password/current/.1password/agent.sock`)
700
+ * Windows: 1Password serves the standard OpenSSH named pipe `\\.\pipe\openssh-ssh-agent`, which ssh uses by default - there is no socket file and no `IdentityAgent` line is written
679
701
  - Check file permissions (600 for private key, 644 for public key)
680
702
  - Verify the public key is being successfully pushed to the instance
681
703
  - For 1Password-managed keys, make sure:
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.17.2'
22
- __version_tuple__ = version_tuple = (0, 17, 2)
21
+ __version__ = version = '0.17.4'
22
+ __version_tuple__ = version_tuple = (0, 17, 4)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -31,6 +31,20 @@ def detect_ssh_defaults() -> tuple:
31
31
  return "cloudX", "cloudX", "~/.ssh/cloudX"
32
32
 
33
33
 
34
+ def prefix_from_command_name() -> str:
35
+ """The host prefix implied by the command name that was invoked.
36
+
37
+ On Windows a console script is an .exe, so sys.argv[0] ends in one and a
38
+ bare basename never equals 'cloudX-proxy' - every Windows user silently
39
+ got the lowercase prefix whichever command they typed. Compare the stem.
40
+
41
+ Returns:
42
+ str: 'cloudX' when invoked as cloudX-proxy, otherwise 'cloudx'
43
+ """
44
+ stem = os.path.splitext(os.path.basename(sys.argv[0]))[0]
45
+ return 'cloudX' if stem == 'cloudX-proxy' else 'cloudx'
46
+
47
+
34
48
  def short_host_name(host: str, host_prefix: str) -> str:
35
49
  """Strip '<prefix>-<env>-' off a host entry to get its short name.
36
50
 
@@ -188,8 +202,7 @@ def setup(profile: str, ssh_key: str, ssh_config: str, ssh_dir: str, aws_env: st
188
202
  try:
189
203
  # Determine default prefix based on command name if not provided
190
204
  if not ssh_host_prefix:
191
- cmd_name = os.path.basename(sys.argv[0])
192
- ssh_host_prefix = 'cloudX' if cmd_name == 'cloudX-proxy' else 'cloudx'
205
+ ssh_host_prefix = prefix_from_command_name()
193
206
 
194
207
  setup = CloudXSetup(
195
208
  profile=profile,
@@ -205,9 +218,9 @@ def setup(profile: str, ssh_key: str, ssh_config: str, ssh_dir: str, aws_env: st
205
218
  )
206
219
 
207
220
  if dry_run:
208
- print(f"\n{header(f'=== {ssh_host_prefix}-proxy Setup (DRY RUN) ===')}\n")
221
+ print(f"\n{header('=== cloudX-proxy Setup (DRY RUN) ===')}")
209
222
  else:
210
- print(f"\n{header(f'=== {ssh_host_prefix}-proxy Setup ===')}\n")
223
+ print(f"\n{header('=== cloudX-proxy Setup ===')}")
211
224
 
212
225
  # Report missing tooling up front rather than from inside a
213
226
  # ProxyCommand, where the error is easy to miss
@@ -340,7 +353,7 @@ def list(ssh_config: str, environment: str, detailed: bool, dry_run: bool):
340
353
  config_file = cloudx_config
341
354
 
342
355
  if dry_run:
343
- print(f"\n{header('=== cloudx-proxy List (DRY RUN) ===')}\n")
356
+ print(f"\n{header('=== cloudX-proxy List (DRY RUN) ===')}\n")
344
357
  print(f"[DRY RUN] Would read SSH config from: {config_file}")
345
358
  if environment:
346
359
  print(f"[DRY RUN] Would filter hosts by environment: {environment}")
@@ -355,8 +368,7 @@ def list(ssh_config: str, environment: str, detailed: bool, dry_run: bool):
355
368
  sys.exit(1)
356
369
 
357
370
  # Detect ssh_host_prefix from command name
358
- cmd_name = os.path.basename(sys.argv[0])
359
- ssh_host_prefix = 'cloudX' if cmd_name == 'cloudX-proxy' else 'cloudx'
371
+ ssh_host_prefix = prefix_from_command_name()
360
372
 
361
373
  # Use shared parser from CloudXSetup
362
374
  setup = CloudXSetup(ssh_config=str(config_file), ssh_host_prefix=ssh_host_prefix)
@@ -367,17 +379,25 @@ def list(ssh_config: str, environment: str, detailed: bool, dry_run: bool):
367
379
  environments = {}
368
380
  generic_hosts = []
369
381
 
382
+ # Patterns are shown in the preferred spelling whichever command name
383
+ # was typed. A configured host keeps whatever case its owner gave it
384
+ # and is listed under that name; a pattern has no owner, so there is
385
+ # no reason to show 'cloudx-dev-*' to someone who ran cloudX-proxy.
386
+ display_prefix = setup.preferred_host_prefix
387
+
370
388
  # Add global pattern if present
371
389
  if parsed['global']:
372
- generic_hosts.append((f"{ssh_host_prefix}-*", "N/A"))
390
+ generic_hosts.append((f"{display_prefix}-*", "N/A"))
373
391
 
374
392
  # Process each environment
375
393
  for env_key, env_data in parsed['environments'].items():
376
394
  # Use original case name for display, fallback to key if not present
377
395
  display_name = env_data.get('name', env_key)
378
396
 
379
- # Add environment pattern to generic hosts
380
- generic_hosts.append((env_data['pattern'], "N/A"))
397
+ # Add environment pattern to generic hosts. Environments with no
398
+ # hosts of their own appear here and nowhere else, since the
399
+ # listing below is built from host entries.
400
+ generic_hosts.append((f"{display_prefix}-{display_name}-*", "N/A"))
381
401
 
382
402
  # Filter by environment if specified
383
403
  if environment and env_key.lower() != environment.lower():
@@ -426,7 +446,7 @@ def list(ssh_config: str, environment: str, detailed: bool, dry_run: bool):
426
446
  return
427
447
 
428
448
  # Print header
429
- print(f"\n{header('=== cloudx-proxy Configured Hosts ===')}\n")
449
+ print(f"\n{header('=== cloudX-proxy Configured Hosts ===')}\n")
430
450
 
431
451
  # Print generic patterns if any and detailed mode
432
452
  if generic_hosts and detailed:
@@ -514,8 +534,7 @@ def cleanup(ssh_config: str, ssh_host_prefix: str, dry_run: bool):
514
534
 
515
535
  # Determine default prefix based on command name if not provided
516
536
  if not ssh_host_prefix:
517
- cmd_name = os.path.basename(sys.argv[0])
518
- ssh_host_prefix = 'cloudX' if cmd_name == 'cloudX-proxy' else 'cloudx'
537
+ ssh_host_prefix = prefix_from_command_name()
519
538
 
520
539
  setup = CloudXSetup(ssh_config=ssh_config, ssh_host_prefix=ssh_host_prefix, dry_run=dry_run)
521
540