sigit-code 1.7.3__tar.gz → 1.7.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 (63) hide show
  1. {sigit_code-1.7.3 → sigit_code-1.7.4}/Cargo.lock +2 -2
  2. {sigit_code-1.7.3 → sigit_code-1.7.4}/Cargo.toml +2 -2
  3. {sigit_code-1.7.3 → sigit_code-1.7.4}/PKG-INFO +1 -1
  4. sigit_code-1.7.4/crates/sigit-agent/assets/sign-in-logo.png +0 -0
  5. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/sigit-agent/src/browser_auth.rs +88 -26
  6. {sigit_code-1.7.3 → sigit_code-1.7.4}/README.md +0 -0
  7. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/Cargo.toml +0 -0
  8. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/README.md +0 -0
  9. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/account.rs +0 -0
  10. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/backend.rs +0 -0
  11. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/chat.rs +0 -0
  12. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/client_fs.rs +0 -0
  13. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/client_terminal.rs +0 -0
  14. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/commands.rs +0 -0
  15. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/frontmatter.rs +0 -0
  16. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/harness_markup.rs +0 -0
  17. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/headless.rs +0 -0
  18. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/hooks.rs +0 -0
  19. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/inline_tool_calls.rs +0 -0
  20. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/instructions.rs +0 -0
  21. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/main.rs +0 -0
  22. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/mcp.rs +0 -0
  23. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/models.rs +0 -0
  24. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/permissions.rs +0 -0
  25. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/program_status.rs +0 -0
  26. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/provider.rs +0 -0
  27. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/repetition.rs +0 -0
  28. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/session_store.rs +0 -0
  29. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/settings.rs +0 -0
  30. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/setup.rs +0 -0
  31. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/skills.rs +0 -0
  32. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/subagents.rs +0 -0
  33. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/tools.rs +0 -0
  34. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/src/workspace.rs +0 -0
  35. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_audio.rs +0 -0
  36. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_client_fs.rs +0 -0
  37. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_client_terminal.rs +0 -0
  38. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_concurrent_sessions.rs +0 -0
  39. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_elicitation.rs +0 -0
  40. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_embedded_context.rs +0 -0
  41. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_endpoint_errors.rs +0 -0
  42. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_error_codes.rs +0 -0
  43. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_harness_markup.rs +0 -0
  44. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_images.rs +0 -0
  45. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_logout.rs +0 -0
  46. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_multi_root.rs +0 -0
  47. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_permissions.rs +0 -0
  48. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_session_list.rs +0 -0
  49. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_session_load.rs +0 -0
  50. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_stop_reasons.rs +0 -0
  51. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_tool_call_name.rs +0 -0
  52. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/acp_tool_stdin.rs +0 -0
  53. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/headless_mode.rs +0 -0
  54. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/cli/tests/version_flag.rs +0 -0
  55. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/sigit-agent/Cargo.toml +0 -0
  56. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/sigit-agent/README.md +0 -0
  57. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/sigit-agent/src/auth.rs +0 -0
  58. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/sigit-agent/src/credentials.rs +0 -0
  59. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/sigit-agent/src/lib.rs +0 -0
  60. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/sigit-agent/src/profile.rs +0 -0
  61. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/sigit-agent/src/prompt.rs +0 -0
  62. {sigit_code-1.7.3 → sigit_code-1.7.4}/crates/sigit-agent/tests/embedded.rs +0 -0
  63. {sigit_code-1.7.3 → sigit_code-1.7.4}/pyproject.toml +0 -0
@@ -5945,7 +5945,7 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
5945
5945
 
5946
5946
  [[package]]
5947
5947
  name = "sigit"
5948
- version = "1.7.3"
5948
+ version = "1.7.4"
5949
5949
  dependencies = [
5950
5950
  "agent-client-protocol",
5951
5951
  "anyhow",
@@ -5976,7 +5976,7 @@ dependencies = [
5976
5976
 
5977
5977
  [[package]]
5978
5978
  name = "sigit-agent"
5979
- version = "1.7.3"
5979
+ version = "1.7.4"
5980
5980
  dependencies = [
5981
5981
  "agent-client-protocol",
5982
5982
  "anyhow",
@@ -3,7 +3,7 @@ resolver = "3"
3
3
  members = ["crates/cli", "crates/sigit-*"]
4
4
 
5
5
  [workspace.package]
6
- version = "1.7.3"
6
+ version = "1.7.4"
7
7
  edition = "2024"
8
8
  license = "Apache-2.0"
9
9
  repository = "https://github.com/getsigit/sigit"
@@ -16,7 +16,7 @@ authors = ["Seto Elkahfi <seto@sigit.si>", "paydii <193906237+paydii@users.norep
16
16
  # crates.io, so this resolves from the registry rather than the onde-ed checkout next door.
17
17
  ed-acp = { version = "1.3.0", default-features = false }
18
18
  ed-acp-testkit = "1.3.0"
19
- sigit-agent = { version = "1.7.3", default-features = false, path = "crates/sigit-agent" }
19
+ sigit-agent = { version = "1.7.4", default-features = false, path = "crates/sigit-agent" }
20
20
 
21
21
  # ACP protocol SDK
22
22
  agent-client-protocol = { version = "2.2", features = ["unstable_session_fork"] }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sigit-code
3
- Version: 1.7.3
3
+ Version: 1.7.4
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -180,11 +180,27 @@ pub async fn complete_loopback(flow: Flow) -> Result<String, String> {
180
180
  let listener = listener.ok_or_else(|| "this sign-in has no local listener".to_string())?;
181
181
 
182
182
  let expected_state = state.clone();
183
- let code = tokio::task::spawn_blocking(move || wait_for_code(listener, &expected_state))
184
- .await
185
- .map_err(|error| format!("sign-in was interrupted: {error}"))??;
186
-
187
- exchange(&code, &verifier, &redirect_uri).await
183
+ let (code, mut stream) =
184
+ tokio::task::spawn_blocking(move || wait_for_code(listener, &expected_state))
185
+ .await
186
+ .map_err(|error| format!("sign-in was interrupted: {error}"))??;
187
+
188
+ // The browser is still waiting on its request. Answer it only once the
189
+ // exchange has settled, so the page never reports a sign-in that failed.
190
+ let result = exchange(&code, &verifier, &redirect_uri).await;
191
+ match &result {
192
+ Ok(_) => respond(
193
+ &mut stream,
194
+ "Signed in to siGit Code",
195
+ "You can close this tab.",
196
+ ),
197
+ Err(_) => respond(
198
+ &mut stream,
199
+ "Sign-in did not complete",
200
+ "Return to siGit Code and try again.",
201
+ ),
202
+ }
203
+ result
188
204
  }
189
205
 
190
206
  /// Redeem a code the person pasted in. Returns the signed-in email.
@@ -198,8 +214,12 @@ pub async fn complete_paste(flow: &Flow, code: &str) -> Result<String, String> {
198
214
 
199
215
  /// Accept connections until one carries our callback. Anything else on the
200
216
  /// port (a browser probe, a stray scan) is answered and ignored rather than
201
- /// ending the wait.
202
- fn wait_for_code(listener: TcpListener, expected_state: &str) -> Result<String, String> {
217
+ /// ending the wait. On success the matching connection is returned unanswered,
218
+ /// for the caller to respond to once the code has been redeemed.
219
+ fn wait_for_code(
220
+ listener: TcpListener,
221
+ expected_state: &str,
222
+ ) -> Result<(String, std::net::TcpStream), String> {
203
223
  let deadline = std::time::Instant::now() + CALLBACK_TIMEOUT;
204
224
  listener
205
225
  .set_nonblocking(true)
@@ -243,7 +263,7 @@ fn wait_for_code(listener: TcpListener, expected_state: &str) -> Result<String,
243
263
  }
244
264
  }
245
265
  Err(error) => {
246
- respond(&mut stream, "Could not read the sign-in callback.");
266
+ respond(&mut stream, "Nothing to do here", "");
247
267
  log::debug!("could not read browser callback: {error}");
248
268
  continue 'connections;
249
269
  }
@@ -255,7 +275,7 @@ fn wait_for_code(listener: TcpListener, expected_state: &str) -> Result<String,
255
275
  .next()
256
276
  .and_then(|line| line.split_whitespace().nth(1))
257
277
  else {
258
- respond(&mut stream, "Bad request.");
278
+ respond(&mut stream, "Nothing to do here", "");
259
279
  continue;
260
280
  };
261
281
 
@@ -273,39 +293,68 @@ fn wait_for_code(listener: TcpListener, expected_state: &str) -> Result<String,
273
293
  }
274
294
 
275
295
  if let Some(error) = error {
276
- respond(
277
- &mut stream,
278
- "Sign-in was cancelled. You can close this tab.",
279
- );
296
+ respond(&mut stream, "Sign-in cancelled", "You can close this tab.");
280
297
  return Err(format!("sign-in was declined ({error})"));
281
298
  }
282
299
 
283
300
  let Some(code) = code else {
284
- respond(&mut stream, "Nothing to do here.");
301
+ respond(&mut stream, "Nothing to do here", "");
285
302
  continue;
286
303
  };
287
304
 
288
305
  // The state proves this callback belongs to the flow we started, not
289
306
  // to a link someone else sent to this browser.
290
307
  if state.as_deref() != Some(expected_state) {
291
- respond(&mut stream, "That sign-in did not come from siGit Code.");
308
+ respond(
309
+ &mut stream,
310
+ "Sign-in didn't match",
311
+ "That sign-in did not come from siGit Code.",
312
+ );
292
313
  return Err("the sign-in response did not match this request".to_string());
293
314
  }
294
315
 
295
- respond(
296
- &mut stream,
297
- "Signed in to siGit Code. You can close this tab.",
298
- );
299
- return Ok(code);
316
+ return Ok((code, stream));
300
317
  }
301
318
  }
302
319
 
303
- fn respond(stream: &mut std::net::TcpStream, message: &str) {
304
- let body = format!(
305
- "<!doctype html><meta charset=\"utf-8\"><title>siGit Code</title>\
306
- <body style=\"font:16px system-ui;display:grid;place-items:center;height:90vh\">\
307
- <p>{message}</p></body>"
308
- );
320
+ /// The 64x64 app icon, inlined into the page so it needs no second request.
321
+ const LOGO_PNG: &[u8] = include_bytes!("../assets/sign-in-logo.png");
322
+
323
+ /// Renders the page the browser lands on. Self-contained: inline CSS and an
324
+ /// inline logo, because the page is served from the loopback address and must
325
+ /// not fetch anything. Title and hint are constants, so nothing is escaped.
326
+ fn page(title: &str, hint: &str) -> String {
327
+ let logo = base64::engine::general_purpose::STANDARD.encode(LOGO_PNG);
328
+ let hint = if hint.is_empty() {
329
+ String::new()
330
+ } else {
331
+ format!("<p>{hint}</p>")
332
+ };
333
+ format!(
334
+ "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">\
335
+ <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\
336
+ <meta name=\"color-scheme\" content=\"dark\"><title>{title}</title><style>\
337
+ :root{{color-scheme:dark}}\
338
+ html,body{{margin:0;height:100%}}\
339
+ body{{display:flex;align-items:center;justify-content:center;background:#15171a;\
340
+ font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,sans-serif}}\
341
+ main{{display:flex;flex-direction:column;align-items:center;text-align:center;\
342
+ padding:0 24px;transform:translateY(-6%);animation:fade-in 240ms ease-out}}\
343
+ img{{width:40px;height:40px;border-radius:9px;opacity:.8;\
344
+ box-shadow:0 0 0 1px rgb(255 255 255/.06)}}\
345
+ h1{{margin:16px 0 0;font-size:15px;line-height:20px;font-weight:500;\
346
+ letter-spacing:-.01em;color:#f3f4f6}}\
347
+ p{{margin:4px 0 0;font-size:13px;line-height:18px;color:#6b7280}}\
348
+ @keyframes fade-in{{from{{opacity:0}}to{{opacity:1}}}}\
349
+ @media(prefers-reduced-motion:reduce){{main{{animation:none}}}}\
350
+ </style></head><body><main>\
351
+ <img src=\"data:image/png;base64,{logo}\" alt=\"\" width=\"40\" height=\"40\">\
352
+ <h1>{title}</h1>{hint}</main></body></html>"
353
+ )
354
+ }
355
+
356
+ fn respond(stream: &mut std::net::TcpStream, title: &str, hint: &str) {
357
+ let body = page(title, hint);
309
358
  let response = format!(
310
359
  "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
311
360
  body.len(),
@@ -464,6 +513,19 @@ fn percent_decode(value: &str) -> String {
464
513
  mod tests {
465
514
  use super::*;
466
515
 
516
+ #[test]
517
+ fn page_carries_the_title_hint_and_an_inline_logo() {
518
+ let body = page("Signed in to siGit Code", "You can close this tab.");
519
+ assert!(body.contains("<h1>Signed in to siGit Code</h1>"));
520
+ assert!(body.contains("<p>You can close this tab.</p>"));
521
+ assert!(body.contains("data:image/png;base64,"));
522
+ }
523
+
524
+ #[test]
525
+ fn page_omits_the_hint_when_empty() {
526
+ assert!(!page("Nothing to do here", "").contains("<p>"));
527
+ }
528
+
467
529
  #[test]
468
530
  fn code_challenge_matches_rfc_7636_example() {
469
531
  // RFC 7636 Appendix B.
File without changes
File without changes