insightfactory-cli 1.0.1.dev6__tar.gz → 1.0.1.dev9__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 (49) hide show
  1. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/CLAUDE.md +1 -0
  2. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/PKG-INFO +7 -1
  3. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/README.md +6 -0
  4. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/pyproject.toml +1 -1
  5. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/commands/api.py +22 -5
  6. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_api.py +40 -0
  7. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/.github/workflows/ci.yml +0 -0
  8. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/.github/workflows/claude.yml +0 -0
  9. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/.github/workflows/release.yml +0 -0
  10. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/.gitignore +0 -0
  11. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/.python-version +0 -0
  12. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/AGENTS.md +0 -0
  13. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/__init__.py +0 -0
  14. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/__main__.py +0 -0
  15. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/cache.py +0 -0
  16. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/cli.py +0 -0
  17. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/colour.py +0 -0
  18. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/commands/__init__.py +0 -0
  19. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/commands/config.py +0 -0
  20. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/commands/login.py +0 -0
  21. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/commands/logout.py +0 -0
  22. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/commands/profiles.py +0 -0
  23. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/commands/set_token.py +0 -0
  24. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/commands/token.py +0 -0
  25. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/config.py +0 -0
  26. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/constants.py +0 -0
  27. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/http.py +0 -0
  28. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/main.py +0 -0
  29. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/oauth.py +0 -0
  30. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/src/if_cli/runtime.py +0 -0
  31. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/__init__.py +0 -0
  32. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/cache_writer.py +0 -0
  33. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/conftest.py +0 -0
  34. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/helpers.py +0 -0
  35. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/servers.py +0 -0
  36. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_api_command.py +0 -0
  37. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_cache.py +0 -0
  38. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_cli.py +0 -0
  39. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_config.py +0 -0
  40. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_config_command.py +0 -0
  41. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_login.py +0 -0
  42. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_oauth.py +0 -0
  43. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_oauth_flow.py +0 -0
  44. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_profiles.py +0 -0
  45. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_programmatic_api.py +0 -0
  46. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_runtime.py +0 -0
  47. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_set_token.py +0 -0
  48. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/tests/test_token.py +0 -0
  49. {insightfactory_cli-1.0.1.dev6 → insightfactory_cli-1.0.1.dev9}/uv.lock +0 -0
@@ -9,6 +9,7 @@ the `if-cli` executable. It uses Python 3.10+, uv, pytest, ruff, and ty.
9
9
  uv sync
10
10
  uv run pytest
11
11
  uv run ruff check .
12
+ uv run ruff format --check .
12
13
  uv run ty check
13
14
  ```
14
15
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: insightfactory-cli
3
- Version: 1.0.1.dev6
3
+ Version: 1.0.1.dev9
4
4
  Summary: Profile-based authentication CLI for the InsightFactory Interfaces API
5
5
  Project-URL: Homepage, https://github.com/insightfactory-ai/insightfactory-cli
6
6
  Project-URL: Repository, https://github.com/insightfactory-ai/insightfactory-cli
@@ -102,6 +102,12 @@ prefixes.
102
102
  Override the config directory with `INSIGHTFACTORY_CONFIG_DIR` (useful in tests
103
103
  and CI). The default is `~/.insightfactory`.
104
104
 
105
+ The request method goes in `-X`; `if-cli api` takes exactly one positional
106
+ argument and reads it as the path. A `curl`-shaped `if-cli api GET /api/schedules`
107
+ therefore parses `GET` as the path — write `if-cli api -X GET /api/schedules`
108
+ instead. The CLI names the misplaced verb rather than reporting a problem with
109
+ `/api/schedules`.
110
+
105
111
  On Git Bash (MSYS2) on Windows, path conversion rewrites a leading-slash
106
112
  argument such as `/api/agent-projects` into a Windows path. Prefix the command
107
113
  with `MSYS_NO_PATHCONV=1`:
@@ -86,6 +86,12 @@ prefixes.
86
86
  Override the config directory with `INSIGHTFACTORY_CONFIG_DIR` (useful in tests
87
87
  and CI). The default is `~/.insightfactory`.
88
88
 
89
+ The request method goes in `-X`; `if-cli api` takes exactly one positional
90
+ argument and reads it as the path. A `curl`-shaped `if-cli api GET /api/schedules`
91
+ therefore parses `GET` as the path — write `if-cli api -X GET /api/schedules`
92
+ instead. The CLI names the misplaced verb rather than reporting a problem with
93
+ `/api/schedules`.
94
+
89
95
  On Git Bash (MSYS2) on Windows, path conversion rewrites a leading-slash
90
96
  argument such as `/api/agent-projects` into a Windows path. Prefix the command
91
97
  with `MSYS_NO_PATHCONV=1`:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "insightfactory-cli"
3
- version = "1.0.1.dev6"
3
+ version = "1.0.1.dev9"
4
4
  description = "Profile-based authentication CLI for the InsightFactory Interfaces API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -61,6 +61,27 @@ def describe_route(document: dict[str, Any], method: str, path: str) -> dict[str
61
61
  return operation if isinstance(operation, dict) else None
62
62
 
63
63
 
64
+ def _is_absolute_http_url(api_path: str) -> bool:
65
+ parsed = parse_url(api_path)
66
+ return parsed.scheme in {"http", "https"} and bool(parsed.hostname)
67
+
68
+
69
+ def _die_on_misplaced_http_method(api_path: str, positionals: list[str]) -> None:
70
+ """Explain a `curl`-shaped `if-cli api GET /path`, where the verb is read as the path.
71
+
72
+ Recognition spans every verb a user might reach for, not just the ones -X accepts:
73
+ naming the misplaced verb is more useful than a path error even for TRACE, which
74
+ -X then rejects on its own terms.
75
+ """
76
+ method = api_path.lower()
77
+ if method not in HTTP_METHODS:
78
+ return
79
+ targets = [value for value in positionals[1:] if value.startswith("/") or _is_absolute_http_url(value)]
80
+ target = targets[0] if targets else "/api/path"
81
+ suggestion = f"if-cli api -X {method.upper()} {target}"
82
+ die(f"'{api_path}' looks like an HTTP method, not a path — did you mean: {suggestion} ?")
83
+
84
+
64
85
  def _die_on_msys_mangled_path(api_path: str) -> None:
65
86
  if not WINDOWS_DRIVE_PATH.search(api_path):
66
87
  return
@@ -73,11 +94,6 @@ def _die_on_msys_mangled_path(api_path: str) -> None:
73
94
  )
74
95
 
75
96
 
76
- def _is_absolute_http_url(api_path: str) -> bool:
77
- parsed = parse_url(api_path)
78
- return parsed.scheme in {"http", "https"} and bool(parsed.hostname)
79
-
80
-
81
97
  def resolve_api_url(host: str, api_path: str) -> ParseResult:
82
98
  if not api_path.startswith("/") and not _is_absolute_http_url(api_path):
83
99
  _die_on_msys_mangled_path(api_path)
@@ -145,6 +161,7 @@ def api_command(argv: list[str]) -> None:
145
161
  api_path = positionals[0] if positionals else None
146
162
  if not api_path:
147
163
  die(API_USAGE)
164
+ _die_on_misplaced_http_method(api_path, positionals)
148
165
 
149
166
  if values["method"] is not None:
150
167
  method = values["method"].upper()
@@ -37,6 +37,46 @@ def test_api_rejects_unsupported_request_methods_with_a_cli_error():
37
37
  assert "unsupported request method 'TRACE'" in result.stderr
38
38
 
39
39
 
40
+ def test_api_names_a_misplaced_http_method_instead_of_blaming_the_path():
41
+ result = run_cli(["api", "GET", "/api/schedules"])
42
+ assert result.returncode == 1
43
+ assert "'GET' looks like an HTTP method, not a path" in result.stderr
44
+ assert "did you mean: if-cli api -X GET /api/schedules ?" in result.stderr
45
+ assert "API path must begin with" not in result.stderr
46
+
47
+
48
+ def test_api_detects_a_misplaced_http_method_in_any_case_and_position():
49
+ lowercase = run_cli(["api", "put", "/api/orchestration/run"])
50
+ assert lowercase.returncode == 1
51
+ assert "'put' looks like an HTTP method, not a path" in lowercase.stderr
52
+ assert "did you mean: if-cli api -X PUT /api/orchestration/run ?" in lowercase.stderr
53
+
54
+ before_profile = run_cli(["api", "DELETE", "/api/schedules/1", "-p", "example-dev"])
55
+ assert before_profile.returncode == 1
56
+ assert "did you mean: if-cli api -X DELETE /api/schedules/1 ?" in before_profile.stderr
57
+
58
+
59
+ def test_api_keeps_a_same_origin_absolute_url_in_the_correction():
60
+ result = run_cli(["api", "GET", "https://factory.example/api/schedules"])
61
+ assert result.returncode == 1
62
+ assert "did you mean: if-cli api -X GET https://factory.example/api/schedules ?" in result.stderr
63
+
64
+
65
+ def test_api_names_a_misplaced_verb_that_x_would_not_accept():
66
+ result = run_cli(["api", "TRACE", "/api/schedules"])
67
+ assert result.returncode == 1
68
+ assert "'TRACE' looks like an HTTP method, not a path" in result.stderr
69
+ assert "did you mean: if-cli api -X TRACE /api/schedules ?" in result.stderr
70
+ assert "API path must begin with" not in result.stderr
71
+
72
+
73
+ def test_api_suggests_a_placeholder_path_when_only_a_method_was_supplied():
74
+ result = run_cli(["api", "POST"])
75
+ assert result.returncode == 1
76
+ assert "'POST' looks like an HTTP method, not a path" in result.stderr
77
+ assert "did you mean: if-cli api -X POST /api/path ?" in result.stderr
78
+
79
+
40
80
  def test_discovers_and_sorts_all_openapi_operations():
41
81
  assert discover_routes(OPENAPI) == [
42
82
  "DELETE /api/agent-projects/{id} Delete an agent project",