insightfactory-cli 1.0.2.dev12__py3-none-any.whl → 1.0.2.dev16__py3-none-any.whl

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.
if_cli/http.py CHANGED
@@ -188,7 +188,11 @@ def fetch_with_timeout(
188
188
  request = urllib.request.Request(url, data=data, headers=headers or {}, method=method)
189
189
  effective_timeout = resolve_request_timeout(timeout)
190
190
  try:
191
- with urllib.request.urlopen(request, timeout=effective_timeout) as response:
191
+ # Callers pass endpoints validated by parse_http_url or derived from a
192
+ # validated factory origin. file: and other local schemes are rejected.
193
+ with urllib.request.urlopen( # nosec B310
194
+ request, timeout=effective_timeout
195
+ ) as response:
192
196
  return HttpResponse(response.status, response.headers, response.read())
193
197
  except urllib.error.HTTPError as error:
194
198
  return HttpResponse(error.code, error.headers, error.read())
if_cli/oauth.py CHANGED
@@ -4,7 +4,7 @@ import base64
4
4
  import hashlib
5
5
  import json
6
6
  import secrets
7
- import subprocess
7
+ import subprocess # nosec B404
8
8
  import sys
9
9
  import time
10
10
  from http.server import BaseHTTPRequestHandler, HTTPServer
@@ -51,7 +51,8 @@ def open_browser(url: str, launcher: str | None = None) -> None:
51
51
  else:
52
52
  command, args = "xdg-open", [url]
53
53
  try:
54
- subprocess.Popen(
54
+ # The executable is selected by platform and shell remains false.
55
+ subprocess.Popen( # nosec B603
55
56
  [command, *args],
56
57
  stdout=subprocess.DEVNULL,
57
58
  stderr=subprocess.DEVNULL,
@@ -0,0 +1,314 @@
1
+ Metadata-Version: 2.5
2
+ Name: insightfactory-cli
3
+ Version: 1.0.2.dev16
4
+ Summary: Profile-based authentication CLI for the InsightFactory Interfaces API
5
+ Project-URL: Homepage, https://insightfactory.ai
6
+ Author-email: "insightfactory.ai Support" <support@insightfactory.ai>
7
+ License-Expression: LicenseRef-Proprietary
8
+ License-File: LICENSE
9
+ Keywords: cli,insightfactory,oauth,pkce
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: Other/Proprietary License
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+
19
+ # insightfactory-cli
20
+
21
+ Profile-based authentication CLI for the InsightFactory Interfaces API. It
22
+ installs the `if-cli` command, which logs in through the browser with OAuth
23
+ authorization code and PKCE, refreshes tokens, and makes authenticated API
24
+ calls. Profiles live in `~/.insightfactory` in the same layout the Node
25
+ `@insightfactory-ai/if-cli` uses, so both implementations can share one config
26
+ directory.
27
+
28
+ This is the production Python CLI, published to public PyPI. It needs Python
29
+ 3.10 or newer.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ uv tool install insightfactory-cli # persistent install, on PATH
35
+ uvx insightfactory-cli profiles # or zero-install, run-once
36
+ uv tool upgrade insightfactory-cli # updates
37
+ ```
38
+
39
+ `uv` fetches a managed Python if the machine lacks one. Fallbacks:
40
+
41
+ ```bash
42
+ pipx install insightfactory-cli
43
+ pip install insightfactory-cli
44
+ ```
45
+
46
+ Develop-branch builds are published as PEP 440 `.devN` pre-releases. Install
47
+ the latest one with:
48
+
49
+ ```bash
50
+ pip install --pre insightfactory-cli
51
+ uv tool install --prerelease allow insightfactory-cli
52
+ ```
53
+
54
+ PEP 440 sorts `1.0.0.devN` before `1.0.0`, so a dependency spec of
55
+ `insightfactory-cli>=1.0.0` does not match a develop-channel dev build. An
56
+ in-process consumer that wants those builds needs a spec that admits
57
+ pre-releases, such as `insightfactory-cli>=1.0.0.dev0`. That spec matches both
58
+ the `.devN` builds and the final `1.0.0` release.
59
+
60
+ ## Usage
61
+
62
+ ```bash
63
+ # Create a profile and authenticate through the browser.
64
+ if-cli login -p example-dev --host https://factory.example
65
+
66
+ # Inspect profiles and cached-token status. Statuses are colour-coded on a TTY
67
+ # (green valid, yellow refreshable-but-expired, red missing or broken); set
68
+ # NO_COLOR=1 to disable or FORCE_COLOR=1 to keep colour when piping.
69
+ if-cli profiles
70
+ if-cli profiles --json
71
+
72
+ # Read a single non-secret profile value for scripting.
73
+ if-cli config get host -p example-dev
74
+
75
+ # Print a valid token, refreshing it when a refresh token is available.
76
+ if-cli token -p example-dev
77
+ if-cli token -p example-dev --env
78
+ if-cli token -p example-dev --env-name INSIGHTFACTORY_ACCESS_TOKEN_DEV
79
+
80
+ # Make an authenticated API request.
81
+ if-cli api -p example-dev /api/agent-projects
82
+ if-cli api -p example-dev -X POST -d '{"name":"example"}' /api/example
83
+
84
+ # Wait longer than the 30-second default for a long-running endpoint.
85
+ if-cli api -p example-dev --timeout 300 -X PUT -d '{"productionLineCodes":["PL001"]}' \
86
+ /api/orchestration/run
87
+
88
+ # Discover API routes from the factory's OpenAPI document.
89
+ if-cli api routes -p example-dev
90
+ if-cli api routes -p example-dev agent-projects
91
+ if-cli api describe -p example-dev GET /api/agent-projects/{id}
92
+
93
+ # Remove the cached credential for a profile.
94
+ if-cli logout -p example-dev
95
+
96
+ # Fallback for factories where browser OAuth is not available.
97
+ if-cli set-token -p example-dev
98
+
99
+ if-cli --version
100
+ ```
101
+
102
+ Profile selection tries `-p` first, then `INSIGHTFACTORY_CONFIG_PROFILE`, then
103
+ `[DEFAULT]`. Route discovery reads `{host}/swagger/v1/swagger.json` and does
104
+ not need a login. Authenticated requests only go to the selected factory's
105
+ origin, so a profile token cannot be forwarded to another host. A profile host
106
+ must be a bare origin, not a URL with an application path prefix.
107
+
108
+ Requests time out after 30 seconds by default. Raise the deadline for one
109
+ invocation with `--timeout <seconds>`, or for a whole session with
110
+ `INSIGHTFACTORY_REQUEST_TIMEOUT`. The flag wins over the environment variable,
111
+ and the CLI validates both before it touches the network. There is no upper
112
+ bound, because the right ceiling depends on the endpoint.
113
+
114
+ Both settings apply to the API request itself. OAuth discovery and token
115
+ refresh keep the 30-second default, so `if-cli token` and in-process callers of
116
+ `if_cli.oauth.get_valid_token` always wait a bounded time that no ambient
117
+ environment value can change.
118
+
119
+ A timeout is a client-side deadline, not a rejection. The factory may have
120
+ accepted and completed the request after `if-cli` gave up, so re-issuing a
121
+ `POST`, `PUT`, `PATCH`, or `DELETE` can apply it twice. For those methods the
122
+ timeout message tells you to confirm the current state before retrying rather
123
+ than repeat the call.
124
+
125
+ Override the config directory with `INSIGHTFACTORY_CONFIG_DIR`, which is useful
126
+ in tests and CI. The default is `~/.insightfactory`.
127
+
128
+ The request method goes in `-X`. `if-cli api` takes exactly one positional
129
+ argument and reads it as the path, so the `curl`-shaped
130
+ `if-cli api GET /api/schedules` parses `GET` as the path. Write
131
+ `if-cli api -X GET /api/schedules` instead. The error names the misplaced verb
132
+ rather than reporting a problem with `/api/schedules`.
133
+
134
+ On Git Bash (MSYS2) on Windows, path conversion rewrites a leading-slash
135
+ argument such as `/api/agent-projects` into a Windows path. Prefix the command
136
+ with `MSYS_NO_PATHCONV=1`:
137
+
138
+ ```bash
139
+ MSYS_NO_PATHCONV=1 if-cli api -p example-dev /api/agent-projects
140
+ ```
141
+
142
+ ### Scripting against profiles
143
+
144
+ `profiles --json` and `config get` are the supported machine-readable
145
+ interfaces. The padded `profiles` columns are for humans and are not a
146
+ contract, and neither is the layout of `~/.insightfactory/config`.
147
+
148
+ `if-cli profiles --json` prints an array with one entry per configured profile,
149
+ in file order, each holding `name`, `host`, and `token`:
150
+
151
+ ```json
152
+ [
153
+ {
154
+ "name": "example-dev",
155
+ "host": "https://factory.example",
156
+ "token": { "status": "valid", "expires_at": 1785918336, "refreshable": true }
157
+ },
158
+ { "name": "other", "host": "https://other.factory.example", "token": { "status": "none" } }
159
+ ]
160
+ ```
161
+
162
+ `token.status` is `valid`, `expired`, `none`, or `unknown`. `expires_at` and
163
+ `refreshable` appear only when a cached token exists. `valid` means
164
+ `if-cli token` will hand back the cached token as-is. A token inside the
165
+ 60-second slack window that command refreshes within is therefore reported as
166
+ `expired`, so read the raw `expires_at` if you need the literal expiry instant.
167
+ A profile that cannot be resolved has a `host` of `null`, or its raw configured
168
+ value, plus a top-level `error` describing the problem. It does not stop the
169
+ other profiles from being listed. `token.error` is set only when the token
170
+ cache itself could not be read.
171
+
172
+ `if-cli config get <key> -p <profile>` prints one newline-terminated value and
173
+ exits non-zero if the profile cannot be resolved or the key is not configured
174
+ in it. A profile with no host cannot be resolved at all, so every key on it
175
+ fails, not just `host`. A key that is present but empty, such as `client_id =`,
176
+ counts as not configured:
177
+
178
+ ```bash
179
+ curl -H "Authorization: Bearer $(if-cli token -p example-dev)" \
180
+ "$(if-cli config get host -p example-dev)/api/agent-projects"
181
+ ```
182
+
183
+ The readable keys are `host`, `audience`, `callback_port`, `client_id`, and
184
+ `organization`. All are non-secret and resolved the same way the CLI resolves
185
+ them, so `audience` and `callback_port` return their defaults when the profile
186
+ omits them. Tokens are deliberately not readable this way. Use `if-cli token`.
187
+
188
+ ## Programmatic API
189
+
190
+ You can import `insightfactory-cli` in-process to resolve a profile to a
191
+ factory host and a fresh access token. These four symbols are supported for
192
+ in-process consumers:
193
+
194
+ - `if_cli.config.load_config`
195
+ - `if_cli.config.get_profile`
196
+ - `if_cli.oauth.get_valid_token`
197
+ - `if_cli.runtime.CliError`
198
+
199
+ They raise `CliError` on failure, never `SystemExit` or `sys.exit`. They never
200
+ write to stdout, never launch a browser or block on interactive input, and only
201
+ use bounded waits. Changing or removing any of them is a breaking change and
202
+ needs a major version bump.
203
+
204
+ ## How authentication works
205
+
206
+ - Profiles live in `~/.insightfactory/config`, one per customer and environment.
207
+ - The CLI manages the profile file and regenerates it on updates. Comments and
208
+ empty sections are not preserved.
209
+ - Tokens are cached by host in `~/.insightfactory/token-cache.json` with file
210
+ mode `0600`.
211
+ - The configuration directory is `0700`. Configuration and cache updates are
212
+ atomic.
213
+ - Remote factory, authorization, and token endpoints must use HTTPS. HTTP is
214
+ accepted only for loopback development.
215
+ - Login discovers OAuth metadata from
216
+ `{host}/.well-known/oauth-authorization-server`.
217
+ - Authorization uses PKCE through the factory `/authorize` proxy with a
218
+ loopback-only `127.0.0.1` callback. The CLI is a public OAuth client. It reads
219
+ the client ID from the factory's discovery document at runtime and holds no
220
+ client secret.
221
+
222
+ ## Development
223
+
224
+ ```bash
225
+ uv sync
226
+ uv run pytest
227
+ uv run ruff check .
228
+ uv run ty check
229
+ ```
230
+
231
+ Smoke-test against a throwaway config directory, never `~/.insightfactory`:
232
+
233
+ ```bash
234
+ uv run if-cli --help
235
+ uv run if-cli --version
236
+ INSIGHTFACTORY_CONFIG_DIR=$(mktemp -d) uv run if-cli profiles
237
+ ```
238
+
239
+ Python 3.10 or newer is required. Runtime code is stdlib-only.
240
+
241
+ ## Release
242
+
243
+ A release is cut by pushing a tag, not by merging. Merging to `main` runs CI
244
+ and publishes nothing.
245
+
246
+ ```bash
247
+ git tag v1.0.1 && git push origin v1.0.1
248
+ ```
249
+
250
+ Verify and build live in the reusable workflow
251
+ [`if_s_insightfactory_cli.yml`](https://github.com/insightfactory-ai/if_sre_github_actions/blob/main/.github/workflows/if_s_insightfactory_cli.yml)
252
+ in `insightfactory-ai/if_sre_github_actions`. This repository's
253
+ `.github/workflows/release.yml` keeps only the OIDC publish job, because PyPI
254
+ Trusted Publishing is registered for this repo, workflow `release.yml`, and
255
+ environment `pypi`. A reusable workflow cannot be registered.
256
+
257
+ The PyPI project needs a one-time trusted-publisher registration for that
258
+ triple. The publish job downloads the `dist-release` artifact staged by the
259
+ reusable workflow and uploads it with `pypa/gh-action-pypi-publish`.
260
+
261
+ | Trigger | Published version | PyPI role |
262
+ |---|---|---|
263
+ | Push / merge to `develop` | `1.0.1.dev{N}` | pre-release (`pip install --pre`) |
264
+ | Push of a `v*` tag | the version in `pyproject.toml`, which the tag must match | latest |
265
+ | Merge to `main` | nothing, CI only | none |
266
+
267
+ `N` is `github.run_number` of the reusable workflow. It is a per-workflow
268
+ integer that goes up on every run and stays the same across re-runs of that
269
+ run, so a failed publish can be retried under the same version. It does not
270
+ depend on git history or clone depth. After a release, bump `version` in
271
+ `pyproject.toml` on `develop` so later `.devN` builds sort after what just
272
+ landed. PEP 440 puts `1.0.1.devN` before `1.0.1`, which is also why a spec of
273
+ `insightfactory-cli>=1.0.0` skips develop-channel builds and an in-process
274
+ consumer that wants them must ask for `insightfactory-cli>=1.0.0.dev0`.
275
+
276
+ A `v*` tag whose name does not match `pyproject.toml` fails before publishing.
277
+
278
+ The two channels treat an already-published version differently, on purpose:
279
+
280
+ - **Release channel, a tag.** Publishing fails and names the fix. A tag is a
281
+ request to release that version. If it cannot be honoured, the run must say
282
+ so rather than report success having published nothing. Git also refuses to
283
+ push a tag that already exists, so this is hard to reach.
284
+ - **Dev channel, `develop`.** Publishing is skipped quietly. `.dev{N}` uses
285
+ `github.run_number`, which is stable across re-runs of one run, so an
286
+ already-present version there can only mean a re-run of a run that already
287
+ published. That is idempotency, not a swallowed mistake.
288
+
289
+ This split is why releasing moved off `main`. While a merge was the trigger, an
290
+ already-published version had to be tolerated as a no-op for merges to stay
291
+ green. A merge that released nothing then looked the same as one that
292
+ released, and a forgotten version bump shipped nothing, silently.
293
+
294
+ ## Differences from the Node CLI
295
+
296
+ Behaviour matches `@insightfactory-ai/if-cli` except where the platforms
297
+ differ:
298
+
299
+ - **Unknown-option wording** is aligned to `Unknown option '--flag'`. Other
300
+ argparse messages, such as a missing option value, still differ from Node's
301
+ `util.parseArgs`.
302
+ - **Expiry timestamps** in human-readable output use the C-locale `%c` format
303
+ rather than JavaScript `Date#toLocaleString()`.
304
+ - **`--version`** reads the installed package metadata from `1.0.0` onward
305
+ rather than an npm `package.json`.
306
+
307
+ On-disk profile and token-cache paths, JSON shapes, and file permissions stay
308
+ compatible with the Node CLI, so you can switch implementations without
309
+ re-authenticating.
310
+
311
+ ## Licence
312
+
313
+ Copyright 2026 insightfactory.ai. All rights reserved. This package is proprietary
314
+ and may be used only under a separate written agreement with insightfactory.ai.
@@ -5,9 +5,9 @@ if_cli/cli.py,sha256=1pdYz3bhsNO120aWs9Fqjchj4UvNByn5Otk62IiBvzY,5832
5
5
  if_cli/colour.py,sha256=2hbaM4ubTHGja6YZRD886eqnB7T2IaZyW5rvfOjxe4M,682
6
6
  if_cli/config.py,sha256=IZhuo0cMsP-L9rLIKYVB87YRhCF-tcuyBTWp0-um4vM,6338
7
7
  if_cli/constants.py,sha256=RW43KupAdhXfBsX6MxzZdJ-R57brX0q0TgIq8xXgvPs,269
8
- if_cli/http.py,sha256=8DDlpd7roDmMZaql4DOliQymqVoTCtXH9ZYDVX3PzIQ,7476
8
+ if_cli/http.py,sha256=W8HL3uZLeOu5MgmsOavM1Y6x4AcrInIQsAr5hx-j8AE,7671
9
9
  if_cli/main.py,sha256=kkJVfkRHvdcMLysmfsag7YVneDpJP02I0KRVdOaBh3k,2697
10
- if_cli/oauth.py,sha256=Z7M4b_tqB2VNBXeImQcZJx2nNdKFcNMuA8rqBJGO7ak,15974
10
+ if_cli/oauth.py,sha256=X0514sPBysjGwcN5_39KaQfQ62sbl72Xztixd9BqOE4,16076
11
11
  if_cli/runtime.py,sha256=Pz2iVclqy-uu1UMQWK2P_Hx7O4zAF30UpSFqQvGN0NY,2663
12
12
  if_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  if_cli/commands/api.py,sha256=XeVhhD39Oho4xNbMJIGh1I6vi53on5EGGsAtsb_5TAg,9016
@@ -17,7 +17,8 @@ if_cli/commands/logout.py,sha256=TW0P4_tXcTtiHHR73xVPHyhlbOWD1jBnkZsMImCiCBw,668
17
17
  if_cli/commands/profiles.py,sha256=JNBrYWdHQ9MHauwZxH33IAOYgRYRniX7bwd7KBT6Vyk,3869
18
18
  if_cli/commands/set_token.py,sha256=BVyv_QmByvllr04pE2sz8JFEscHbIgOC-7ZPg61PRoc,1886
19
19
  if_cli/commands/token.py,sha256=CyiXkyH7vxgH91-n-XJzeHnLI6mZJwO9Js5AHvfHaeM,1120
20
- insightfactory_cli-1.0.2.dev12.dist-info/METADATA,sha256=jeXfI9T5T7Ov06JhDNhtDrD7_E0cyYu8vrQ_ky6CsxA,13151
21
- insightfactory_cli-1.0.2.dev12.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
22
- insightfactory_cli-1.0.2.dev12.dist-info/entry_points.txt,sha256=UlX854D7f-7dBj5F0wGdakVy6vopvP9hA1BN-gv739w,44
23
- insightfactory_cli-1.0.2.dev12.dist-info/RECORD,,
20
+ insightfactory_cli-1.0.2.dev16.dist-info/METADATA,sha256=Iq2H3FJKYrsSgrIHMnaXt1Nl4FuJUGv53lXQ0iTPTb0,13101
21
+ insightfactory_cli-1.0.2.dev16.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
22
+ insightfactory_cli-1.0.2.dev16.dist-info/entry_points.txt,sha256=UlX854D7f-7dBj5F0wGdakVy6vopvP9hA1BN-gv739w,44
23
+ insightfactory_cli-1.0.2.dev16.dist-info/licenses/LICENSE,sha256=8eZ1YAABL398qESVJc_FlK1voYQ0GAh-R_rNBQK6QH8,234
24
+ insightfactory_cli-1.0.2.dev16.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Copyright 2026 insightfactory.ai. All rights reserved.
2
+
3
+ This software is proprietary. No permission is granted to use, copy, modify,
4
+ distribute, sublicense, or sell it except under a separate written agreement
5
+ with insightfactory.ai.
@@ -1,310 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: insightfactory-cli
3
- Version: 1.0.2.dev12
4
- Summary: Profile-based authentication CLI for the InsightFactory Interfaces API
5
- Project-URL: Homepage, https://github.com/insightfactory-ai/insightfactory-cli
6
- Project-URL: Repository, https://github.com/insightfactory-ai/insightfactory-cli
7
- Author-email: "insightfactory.ai Support" <support@insightfactory.ai>
8
- Keywords: cli,insightfactory,oauth,pkce
9
- Classifier: Development Status :: 5 - Production/Stable
10
- Classifier: Environment :: Console
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Programming Language :: Python :: 3 :: Only
13
- Classifier: Typing :: Typed
14
- Requires-Python: >=3.10
15
- Description-Content-Type: text/markdown
16
-
17
- # insightfactory-cli
18
-
19
- Profile-based authentication CLI for the InsightFactory Interfaces API. It
20
- installs the `if-cli` command: browser OAuth login (authorization-code + PKCE),
21
- token refresh, and authenticated API calls, with profiles stored in
22
- `~/.insightfactory` in the same layout as the Node `@insightfactory-ai/if-cli`
23
- so the two implementations can share a config directory.
24
-
25
- This is the production Python CLI, published to public PyPI. Python 3.10 or
26
- newer is required.
27
-
28
- ## Install
29
-
30
- ```bash
31
- uv tool install insightfactory-cli # persistent install, on PATH
32
- uvx insightfactory-cli profiles # or zero-install, run-once
33
- uv tool upgrade insightfactory-cli # updates
34
- ```
35
-
36
- `uv` fetches a managed Python automatically if the machine lacks one. Fallbacks:
37
-
38
- ```bash
39
- pipx install insightfactory-cli
40
- pip install insightfactory-cli
41
- ```
42
-
43
- Develop-branch builds are published as PEP 440 `.devN` pre-releases. Install the
44
- bleeding edge with:
45
-
46
- ```bash
47
- pip install --pre insightfactory-cli
48
- uv tool install --prerelease allow insightfactory-cli
49
- ```
50
-
51
- PEP 440 orders `1.0.0.devN` *before* `1.0.0`, so a dependency spec of
52
- `insightfactory-cli>=1.0.0` does **not** match a develop-channel dev build.
53
- An in-process consumer that wants those builds must use a pre-release-bearing
54
- spec such as `insightfactory-cli>=1.0.0.dev0`, which matches both `.devN`
55
- builds and the final `1.0.0` release.
56
-
57
- ## Usage
58
-
59
- ```bash
60
- # Create a profile and authenticate through the browser.
61
- if-cli login -p example-dev --host https://factory.example
62
-
63
- # Inspect profiles and cached-token status. Statuses are colour-coded on a TTY
64
- # (green valid, yellow refreshable-but-expired, red missing or broken); set
65
- # NO_COLOR=1 to disable or FORCE_COLOR=1 to keep colour when piping.
66
- if-cli profiles
67
- if-cli profiles --json
68
-
69
- # Read a single non-secret profile value for scripting.
70
- if-cli config get host -p example-dev
71
-
72
- # Print a valid token, refreshing it when a refresh token is available.
73
- if-cli token -p example-dev
74
- if-cli token -p example-dev --env
75
- if-cli token -p example-dev --env-name INSIGHTFACTORY_ACCESS_TOKEN_DEV
76
-
77
- # Make an authenticated API request.
78
- if-cli api -p example-dev /api/agent-projects
79
- if-cli api -p example-dev -X POST -d '{"name":"example"}' /api/example
80
-
81
- # Wait longer than the 30-second default for a long-running endpoint.
82
- if-cli api -p example-dev --timeout 300 -X PUT -d '{"productionLineCodes":["PL001"]}' \
83
- /api/orchestration/run
84
-
85
- # Discover API routes from the factory's OpenAPI document.
86
- if-cli api routes -p example-dev
87
- if-cli api routes -p example-dev agent-projects
88
- if-cli api describe -p example-dev GET /api/agent-projects/{id}
89
-
90
- # Remove the cached credential for a profile.
91
- if-cli logout -p example-dev
92
-
93
- # Fallback for factories where browser OAuth is not available.
94
- if-cli set-token -p example-dev
95
-
96
- if-cli --version
97
- ```
98
-
99
- Profile selection uses `-p`, then `INSIGHTFACTORY_CONFIG_PROFILE`, then
100
- `[DEFAULT]`. Route discovery reads `{host}/swagger/v1/swagger.json` and does
101
- not require a login. Authenticated API requests are restricted to the selected
102
- factory origin so a profile token cannot be forwarded to another host. Profile
103
- hosts must be bare origins rather than URLs containing application path
104
- prefixes.
105
-
106
- Requests time out after 30 seconds by default. Raise the deadline per
107
- invocation with `--timeout <seconds>`, or for a whole session with
108
- `INSIGHTFACTORY_REQUEST_TIMEOUT`; the flag wins over the environment variable,
109
- and both are validated before the command touches the network. There is no
110
- upper bound, because the right ceiling depends on the endpoint.
111
-
112
- Both apply to the API request itself. The OAuth discovery and token-refresh
113
- round trips keep the 30-second default, so `if-cli token` and in-process callers
114
- of `if_cli.oauth.get_valid_token` always have a bounded wait that no ambient
115
- environment value can redefine.
116
-
117
- A timeout is a client-side deadline, not a rejection: the factory may have
118
- accepted and completed the request after `if-cli` gave up. Re-issuing a
119
- non-idempotent call (`POST`, `PUT`, `PATCH`, `DELETE`) can therefore apply it
120
- twice, so the timeout message for those methods says to confirm the current
121
- state before retrying rather than repeating the call.
122
-
123
- Override the config directory with `INSIGHTFACTORY_CONFIG_DIR` (useful in tests
124
- and CI). The default is `~/.insightfactory`.
125
-
126
- The request method goes in `-X`; `if-cli api` takes exactly one positional
127
- argument and reads it as the path. A `curl`-shaped `if-cli api GET /api/schedules`
128
- therefore parses `GET` as the path — write `if-cli api -X GET /api/schedules`
129
- instead. The CLI names the misplaced verb rather than reporting a problem with
130
- `/api/schedules`.
131
-
132
- On Git Bash (MSYS2) on Windows, path conversion rewrites a leading-slash
133
- argument such as `/api/agent-projects` into a Windows path. Prefix the command
134
- with `MSYS_NO_PATHCONV=1`:
135
-
136
- ```bash
137
- MSYS_NO_PATHCONV=1 if-cli api -p example-dev /api/agent-projects
138
- ```
139
-
140
- ### Scripting against profiles
141
-
142
- `profiles --json` and `config get` are the supported machine-readable
143
- interfaces; the padded `profiles` columns are for humans and are not a
144
- contract, and neither is the layout of `~/.insightfactory/config`.
145
-
146
- `if-cli profiles --json` prints an array — one entry per configured profile, in
147
- file order — of `name`, `host`, and `token`:
148
-
149
- ```json
150
- [
151
- {
152
- "name": "example-dev",
153
- "host": "https://factory.example",
154
- "token": { "status": "valid", "expires_at": 1785918336, "refreshable": true }
155
- },
156
- { "name": "other", "host": "https://other.factory.example", "token": { "status": "none" } }
157
- ]
158
- ```
159
-
160
- `token.status` is `valid`, `expired`, `none`, or `unknown`; `expires_at` and
161
- `refreshable` are present only when a cached token exists. `valid` means
162
- `if-cli token` will hand back the cached token as-is, so a token inside the
163
- 60-second slack window that command refreshes within is reported as `expired` —
164
- use the raw `expires_at` if you need the literal expiry instant instead. A
165
- profile that cannot be resolved has a `host` of `null` (or its raw configured
166
- value) and a top-level `error` describing the problem, and does not stop the
167
- other profiles from being listed; `token.error` is set only when the token
168
- cache itself could not be read.
169
-
170
- `if-cli config get <key> -p <profile>` prints one newline-terminated value and
171
- exits non-zero if the profile cannot be resolved, or the key is not configured
172
- within it. A profile with no host cannot be resolved at all, so every key on it
173
- fails, not just `host`. A key present but empty (`client_id =`) counts as not
174
- configured:
175
-
176
- ```bash
177
- curl -H "Authorization: Bearer $(if-cli token -p example-dev)" \
178
- "$(if-cli config get host -p example-dev)/api/agent-projects"
179
- ```
180
-
181
- The readable keys are `host`, `audience`, `callback_port`, `client_id`, and
182
- `organization` — all non-secret, and resolved the same way the CLI itself
183
- resolves them, so `audience` and `callback_port` return their defaults when the
184
- profile omits them. Tokens are deliberately not readable this way; use
185
- `if-cli token`.
186
-
187
- ## Programmatic API
188
-
189
- `insightfactory-cli` may be imported in-process as a library to resolve a
190
- profile to a factory host and a fresh access token. These four symbols are a
191
- supported programmatic surface for in-process consumers:
192
-
193
- - `if_cli.config.load_config`
194
- - `if_cli.config.get_profile`
195
- - `if_cli.oauth.get_valid_token`
196
- - `if_cli.runtime.CliError`
197
-
198
- They raise `CliError` on failure (never `SystemExit` or `sys.exit`), never
199
- write to stdout, never launch a browser or block on interactive input, and use
200
- only bounded waits. Changing or removing any of them is a breaking change and
201
- requires a major version bump.
202
-
203
- ## How authentication works
204
-
205
- - Profiles are stored in `~/.insightfactory/config`, one per customer/environment.
206
- - The profile file is managed by the CLI and is regenerated on updates; comments
207
- and empty sections are not preserved.
208
- - Tokens are cached by host in `~/.insightfactory/token-cache.json` with file
209
- mode `0600`.
210
- - The configuration directory is `0700`; configuration and cache updates are
211
- atomic.
212
- - Remote factory, authorization, and token endpoints must use HTTPS. HTTP is
213
- accepted only for loopback development.
214
- - Login discovers OAuth metadata from
215
- `{host}/.well-known/oauth-authorization-server`.
216
- - Authorization uses PKCE through the factory `/authorize` proxy with a
217
- loopback-only (`127.0.0.1`) callback. The CLI is a public OAuth client: the
218
- client ID is read from the factory's discovery document at runtime (no client
219
- secret).
220
-
221
- ## Development
222
-
223
- ```bash
224
- uv sync
225
- uv run pytest
226
- uv run ruff check .
227
- uv run ty check
228
- ```
229
-
230
- Smoke-test against a throwaway config directory, never `~/.insightfactory`:
231
-
232
- ```bash
233
- uv run if-cli --help
234
- uv run if-cli --version
235
- INSIGHTFACTORY_CONFIG_DIR=$(mktemp -d) uv run if-cli profiles
236
- ```
237
-
238
- Python 3.10 or newer is required. Runtime code is stdlib-only.
239
-
240
- ## Release
241
-
242
- **A release is cut by pushing a tag, not by merging.** Merging to `main` runs CI and
243
- publishes nothing.
244
-
245
- ```bash
246
- git tag v1.0.1 && git push origin v1.0.1
247
- ```
248
-
249
- Verify and build live in the reusable workflow
250
- [`if_s_insightfactory_cli.yml`](https://github.com/insightfactory-ai/if_sre_github_actions/blob/main/.github/workflows/if_s_insightfactory_cli.yml)
251
- in `insightfactory-ai/if_sre_github_actions`. This repository's
252
- `.github/workflows/release.yml` keeps only the OIDC publish job, because PyPI
253
- Trusted Publishing is registered for **this** repo, workflow `release.yml`, and
254
- environment `pypi` — a reusable workflow cannot be registered.
255
-
256
- A one-time trusted-publisher registration is required on the PyPI project for
257
- that triple. The publish job downloads the `dist-release` artifact staged by
258
- the reusable workflow and uploads it with `pypa/gh-action-pypi-publish`.
259
-
260
- | Trigger | Published version | PyPI role |
261
- |---|---|---|
262
- | Push / merge to `develop` | `1.0.1.dev{N}` | pre-release (`pip install --pre`) |
263
- | Push of a `v*` tag | the version in `pyproject.toml`, which the tag must match | latest |
264
- | Merge to `main` | nothing — CI only | — |
265
-
266
- `N` is `github.run_number` of the reusable workflow: a per-workflow integer
267
- that goes up on every run and stays the same across re-runs of that run (so a
268
- failed publish can be retried under the same version). It does not depend on
269
- git history or clone depth. After a successful release, bump `version` in
270
- `pyproject.toml` on `develop` so later `.devN` builds sort *after* what just
271
- landed; PEP 440 puts `1.0.1.devN` before `1.0.1`. Because of that ordering, a
272
- dependency spec of `insightfactory-cli>=1.0.0` does not match develop-channel
273
- `.devN` builds; an in-process consumer that wants them must specify a
274
- pre-release-bearing spec such as `insightfactory-cli>=1.0.0.dev0` (which also
275
- matches the final `1.0.0` release).
276
-
277
- A `v*` tag whose name does not match `pyproject.toml` fails before publishing.
278
-
279
- The two channels treat an already-published version differently, on purpose:
280
-
281
- - **Release channel (a tag):** publishing fails, naming the fix. A tag is a request to
282
- release that version; if it cannot be honoured, the run must say so rather than report
283
- success having published nothing. (git also refuses to push a tag that already exists,
284
- so this is hard to reach.)
285
- - **Dev channel (`develop`):** publishing is skipped quietly. `.dev{N}` uses
286
- `github.run_number`, which is stable across re-runs of one run, so an already-present
287
- version there can only mean a re-run of a run that already published — real idempotency,
288
- not a swallowed mistake.
289
-
290
- This split is why releasing moved off `main`. While a merge was the trigger, an
291
- already-published version *had* to be tolerated as a no-op for merges to stay green — so a
292
- merge that released nothing looked exactly like one that released, and a forgotten version
293
- bump shipped nothing silently.
294
-
295
- ## Differences from the Node CLI
296
-
297
- Behaviour matches `@insightfactory-ai/if-cli` except where the platforms
298
- genuinely diverge:
299
-
300
- - **Unknown-option wording** is aligned (`Unknown option '--flag'`). Other
301
- argparse messages (for example a missing option value) still differ from
302
- Node's `util.parseArgs`.
303
- - **Expiry timestamps** in human-readable output use the C-locale `%c` format
304
- rather than JavaScript `Date#toLocaleString()`.
305
- - **`--version`** reads the installed package metadata (`1.0.0` and later)
306
- rather than an npm `package.json`.
307
-
308
- On-disk profile and token-cache paths, JSON shapes, and file permissions stay
309
- compatible with the Node CLI, so you can switch implementations without
310
- re-authenticating.