secshare-cli 0.2.1__tar.gz → 0.2.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: secshare-cli
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: CLI for SecShare — zero-knowledge secret sharing
5
5
  Project-URL: Homepage, https://secshare.link
6
6
  Project-URL: Source, https://github.com/rdney/secshare
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "secshare-cli"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "CLI for SecShare — zero-knowledge secret sharing"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,4 +1,6 @@
1
1
  import json
2
+ import subprocess
3
+ import sys
2
4
  import time
3
5
  import webbrowser
4
6
  from urllib.parse import urlparse
@@ -125,22 +127,63 @@ def whoami():
125
127
  raise click.ClickException(str(e))
126
128
 
127
129
 
130
+ def _copy_to_clipboard(text: str) -> None:
131
+ if sys.platform == "darwin":
132
+ subprocess.run(["pbcopy"], input=text.encode(), check=True)
133
+ elif sys.platform == "win32":
134
+ subprocess.run(["clip"], input=text.encode(), check=True)
135
+ else:
136
+ try:
137
+ subprocess.run(["xclip", "-selection", "clipboard"], input=text.encode(), check=True)
138
+ except FileNotFoundError:
139
+ subprocess.run(["xsel", "--clipboard", "--input"], input=text.encode(), check=True)
140
+
141
+
142
+ def _schedule_clipboard_clear(seconds: int) -> None:
143
+ """Launch a detached process that clears the clipboard after `seconds`. Returns immediately."""
144
+ if sys.platform == "darwin":
145
+ cmd = f"sleep {seconds} && echo -n '' | pbcopy"
146
+ elif sys.platform == "win32":
147
+ cmd = None # handled separately
148
+ else:
149
+ cmd = f"sleep {seconds} && echo -n '' | xclip -selection clipboard 2>/dev/null || echo -n '' | xsel --clipboard --input 2>/dev/null"
150
+
151
+ try:
152
+ if sys.platform == "win32":
153
+ subprocess.Popen(
154
+ ["powershell", "-Command", f"Start-Sleep {seconds}; Set-Clipboard ''"],
155
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
156
+ creationflags=subprocess.DETACHED_PROCESS, # type: ignore[attr-defined]
157
+ )
158
+ else:
159
+ subprocess.Popen(
160
+ ["bash", "-c", cmd],
161
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
162
+ start_new_session=True,
163
+ )
164
+ except Exception:
165
+ pass # best-effort
166
+
167
+
128
168
  @cli.command()
129
169
  @click.argument("link")
130
- @click.option("--show", is_flag=True, help="Print plaintext to terminal (use only when not piping).")
131
- def get(link, show):
170
+ @click.option("--show", is_flag=True, help="Print plaintext to terminal.")
171
+ @click.option("--clip", is_flag=True, help="Copy to clipboard. Clears after 30s.")
172
+ def get(link, show, clip):
132
173
  """Fetch and decrypt a secret.
133
174
 
134
175
  \b
135
- Inject .env secrets into your shell (recommended):
176
+ Inject a .env file into your shell (recommended):
136
177
  eval "$(secshare get https://secshare.link/s/abc123#KEY)"
137
178
 
138
179
  \b
139
- Explicitly print to terminal:
180
+ Copy a secret to clipboard:
181
+ secshare get https://secshare.link/s/abc123#KEY --clip
182
+
183
+ \b
184
+ Print explicitly:
140
185
  secshare get https://secshare.link/s/abc123#KEY --show
141
186
  """
142
- import sys
143
-
144
187
  try:
145
188
  secret_id, key_fragment = _parse_link(link)
146
189
  data = fetch_secret(secret_id)
@@ -150,23 +193,43 @@ def get(link, show):
150
193
  except Exception as e:
151
194
  raise click.ClickException(str(e))
152
195
 
153
- if sys.stdout.isatty() and not show:
154
- raise click.ClickException(
155
- "Refusing to print secret to terminal.\n"
156
- " To inject into your shell: eval \"$(secshare get <link>)\"\n"
157
- " To print explicitly: secshare get <link> --show"
158
- )
159
-
160
- # .env payload — output KEY=VALUE lines
196
+ # Detect payload type
197
+ secret_type = "secret"
198
+ content = plaintext
161
199
  try:
162
200
  parsed = json.loads(plaintext)
163
201
  if isinstance(parsed, dict) and parsed.get("__secshare_type") == "env":
164
- click.echo(parsed["content"], nl=False)
165
- return
202
+ secret_type = "env"
203
+ content = parsed["content"]
166
204
  except (json.JSONDecodeError, AttributeError):
167
205
  pass
168
206
 
169
- click.echo(plaintext, nl=False)
207
+ # --clip: copy silently regardless of TTY
208
+ if clip:
209
+ try:
210
+ _copy_to_clipboard(content)
211
+ except Exception as e:
212
+ raise click.ClickException(f"Clipboard unavailable: {e}")
213
+ _schedule_clipboard_clear(30)
214
+ click.echo("Copied to clipboard. Clears in 30s.", err=True)
215
+ return
216
+
217
+ # TTY guard
218
+ if sys.stdout.isatty() and not show:
219
+ if secret_type == "env":
220
+ raise click.ClickException(
221
+ "Refusing to print secret to terminal.\n"
222
+ " To inject into your shell: eval \"$(secshare get <link>)\"\n"
223
+ " To print explicitly: secshare get <link> --show"
224
+ )
225
+ else:
226
+ raise click.ClickException(
227
+ "Refusing to print secret to terminal.\n"
228
+ " To copy to clipboard: secshare get <link> --clip\n"
229
+ " To print explicitly: secshare get <link> --show"
230
+ )
231
+
232
+ click.echo(content, nl=False)
170
233
 
171
234
 
172
235
  @cli.command()
File without changes
File without changes