insightfactory-cli 1.0.2.dev13__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.
@@ -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.
@@ -17,8 +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.dev13.dist-info/METADATA,sha256=yIRqFsG5CL-1DG6fh5v8dsZ_AlM8qAGvCb-vNJ9lJEE,13330
21
- insightfactory_cli-1.0.2.dev13.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
22
- insightfactory_cli-1.0.2.dev13.dist-info/entry_points.txt,sha256=UlX854D7f-7dBj5F0wGdakVy6vopvP9hA1BN-gv739w,44
23
- insightfactory_cli-1.0.2.dev13.dist-info/licenses/LICENSE,sha256=8eZ1YAABL398qESVJc_FlK1voYQ0GAh-R_rNBQK6QH8,234
24
- insightfactory_cli-1.0.2.dev13.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,,
@@ -1,317 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: insightfactory-cli
3
- Version: 1.0.2.dev13
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: browser OAuth login (authorization-code + PKCE),
23
- token refresh, and authenticated API calls, with profiles stored in
24
- `~/.insightfactory` in the same layout as the Node `@insightfactory-ai/if-cli`
25
- so the two implementations can share a config directory.
26
-
27
- This is the production Python CLI, published to public PyPI. Python 3.10 or
28
- newer is required.
29
-
30
- ## Install
31
-
32
- ```bash
33
- uv tool install insightfactory-cli # persistent install, on PATH
34
- uvx insightfactory-cli profiles # or zero-install, run-once
35
- uv tool upgrade insightfactory-cli # updates
36
- ```
37
-
38
- `uv` fetches a managed Python automatically if the machine lacks one. Fallbacks:
39
-
40
- ```bash
41
- pipx install insightfactory-cli
42
- pip install insightfactory-cli
43
- ```
44
-
45
- Develop-branch builds are published as PEP 440 `.devN` pre-releases. Install the
46
- bleeding edge with:
47
-
48
- ```bash
49
- pip install --pre insightfactory-cli
50
- uv tool install --prerelease allow insightfactory-cli
51
- ```
52
-
53
- PEP 440 orders `1.0.0.devN` *before* `1.0.0`, so a dependency spec of
54
- `insightfactory-cli>=1.0.0` does **not** match a develop-channel dev build.
55
- An in-process consumer that wants those builds must use a pre-release-bearing
56
- spec such as `insightfactory-cli>=1.0.0.dev0`, which matches both `.devN`
57
- builds and the final `1.0.0` release.
58
-
59
- ## Usage
60
-
61
- ```bash
62
- # Create a profile and authenticate through the browser.
63
- if-cli login -p example-dev --host https://factory.example
64
-
65
- # Inspect profiles and cached-token status. Statuses are colour-coded on a TTY
66
- # (green valid, yellow refreshable-but-expired, red missing or broken); set
67
- # NO_COLOR=1 to disable or FORCE_COLOR=1 to keep colour when piping.
68
- if-cli profiles
69
- if-cli profiles --json
70
-
71
- # Read a single non-secret profile value for scripting.
72
- if-cli config get host -p example-dev
73
-
74
- # Print a valid token, refreshing it when a refresh token is available.
75
- if-cli token -p example-dev
76
- if-cli token -p example-dev --env
77
- if-cli token -p example-dev --env-name INSIGHTFACTORY_ACCESS_TOKEN_DEV
78
-
79
- # Make an authenticated API request.
80
- if-cli api -p example-dev /api/agent-projects
81
- if-cli api -p example-dev -X POST -d '{"name":"example"}' /api/example
82
-
83
- # Wait longer than the 30-second default for a long-running endpoint.
84
- if-cli api -p example-dev --timeout 300 -X PUT -d '{"productionLineCodes":["PL001"]}' \
85
- /api/orchestration/run
86
-
87
- # Discover API routes from the factory's OpenAPI document.
88
- if-cli api routes -p example-dev
89
- if-cli api routes -p example-dev agent-projects
90
- if-cli api describe -p example-dev GET /api/agent-projects/{id}
91
-
92
- # Remove the cached credential for a profile.
93
- if-cli logout -p example-dev
94
-
95
- # Fallback for factories where browser OAuth is not available.
96
- if-cli set-token -p example-dev
97
-
98
- if-cli --version
99
- ```
100
-
101
- Profile selection uses `-p`, then `INSIGHTFACTORY_CONFIG_PROFILE`, then
102
- `[DEFAULT]`. Route discovery reads `{host}/swagger/v1/swagger.json` and does
103
- not require a login. Authenticated API requests are restricted to the selected
104
- factory origin so a profile token cannot be forwarded to another host. Profile
105
- hosts must be bare origins rather than URLs containing application path
106
- prefixes.
107
-
108
- Requests time out after 30 seconds by default. Raise the deadline per
109
- invocation with `--timeout <seconds>`, or for a whole session with
110
- `INSIGHTFACTORY_REQUEST_TIMEOUT`; the flag wins over the environment variable,
111
- and both are validated before the command touches the network. There is no
112
- upper bound, because the right ceiling depends on the endpoint.
113
-
114
- Both apply to the API request itself. The OAuth discovery and token-refresh
115
- round trips keep the 30-second default, so `if-cli token` and in-process callers
116
- of `if_cli.oauth.get_valid_token` always have a bounded wait that no ambient
117
- environment value can redefine.
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. Re-issuing a
121
- non-idempotent call (`POST`, `PUT`, `PATCH`, `DELETE`) can therefore apply it
122
- twice, so the timeout message for those methods says to confirm the current
123
- state before retrying rather than repeating the call.
124
-
125
- Override the config directory with `INSIGHTFACTORY_CONFIG_DIR` (useful in tests
126
- 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. A `curl`-shaped `if-cli api GET /api/schedules`
130
- therefore parses `GET` as the path — write `if-cli api -X GET /api/schedules`
131
- instead. The CLI names the misplaced verb rather than reporting a problem with
132
- `/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 — one entry per configured profile, in
149
- file order — of `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` are present only when a cached token exists. `valid` means
164
- `if-cli token` will hand back the cached token as-is, so a token inside the
165
- 60-second slack window that command refreshes within is reported as `expired` —
166
- use the raw `expires_at` if you need the literal expiry instant instead. A
167
- profile that cannot be resolved has a `host` of `null` (or its raw configured
168
- value) and a top-level `error` describing the problem, and 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
- within it. A profile with no host cannot be resolved at all, so every key on it
175
- fails, not just `host`. A key present but empty (`client_id =`) counts as not
176
- 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 non-secret, and resolved the same way the CLI itself
185
- resolves them, so `audience` and `callback_port` return their defaults when the
186
- profile omits them. Tokens are deliberately not readable this way; use
187
- `if-cli token`.
188
-
189
- ## Programmatic API
190
-
191
- `insightfactory-cli` may be imported in-process as a library to resolve a
192
- profile to a factory host and a fresh access token. These four symbols are a
193
- supported programmatic surface for in-process consumers:
194
-
195
- - `if_cli.config.load_config`
196
- - `if_cli.config.get_profile`
197
- - `if_cli.oauth.get_valid_token`
198
- - `if_cli.runtime.CliError`
199
-
200
- They raise `CliError` on failure (never `SystemExit` or `sys.exit`), never
201
- write to stdout, never launch a browser or block on interactive input, and use
202
- only bounded waits. Changing or removing any of them is a breaking change and
203
- requires a major version bump.
204
-
205
- ## How authentication works
206
-
207
- - Profiles are stored in `~/.insightfactory/config`, one per customer/environment.
208
- - The profile file is managed by the CLI and is regenerated on updates; comments
209
- and empty sections are not preserved.
210
- - Tokens are cached by host in `~/.insightfactory/token-cache.json` with file
211
- mode `0600`.
212
- - The configuration directory is `0700`; configuration and cache updates are
213
- atomic.
214
- - Remote factory, authorization, and token endpoints must use HTTPS. HTTP is
215
- accepted only for loopback development.
216
- - Login discovers OAuth metadata from
217
- `{host}/.well-known/oauth-authorization-server`.
218
- - Authorization uses PKCE through the factory `/authorize` proxy with a
219
- loopback-only (`127.0.0.1`) callback. The CLI is a public OAuth client: the
220
- client ID is read from the factory's discovery document at runtime (no client
221
- secret).
222
-
223
- ## Development
224
-
225
- ```bash
226
- uv sync
227
- uv run pytest
228
- uv run ruff check .
229
- uv run ty check
230
- ```
231
-
232
- Smoke-test against a throwaway config directory, never `~/.insightfactory`:
233
-
234
- ```bash
235
- uv run if-cli --help
236
- uv run if-cli --version
237
- INSIGHTFACTORY_CONFIG_DIR=$(mktemp -d) uv run if-cli profiles
238
- ```
239
-
240
- Python 3.10 or newer is required. Runtime code is stdlib-only.
241
-
242
- ## Release
243
-
244
- **A release is cut by pushing a tag, not by merging.** Merging to `main` runs CI and
245
- publishes nothing.
246
-
247
- ```bash
248
- git tag v1.0.1 && git push origin v1.0.1
249
- ```
250
-
251
- Verify and build live in the reusable workflow
252
- [`if_s_insightfactory_cli.yml`](https://github.com/insightfactory-ai/if_sre_github_actions/blob/main/.github/workflows/if_s_insightfactory_cli.yml)
253
- in `insightfactory-ai/if_sre_github_actions`. This repository's
254
- `.github/workflows/release.yml` keeps only the OIDC publish job, because PyPI
255
- Trusted Publishing is registered for **this** repo, workflow `release.yml`, and
256
- environment `pypi` — a reusable workflow cannot be registered.
257
-
258
- A one-time trusted-publisher registration is required on the PyPI project for
259
- that triple. The publish job downloads the `dist-release` artifact staged by
260
- the reusable workflow and uploads it with `pypa/gh-action-pypi-publish`.
261
-
262
- | Trigger | Published version | PyPI role |
263
- |---|---|---|
264
- | Push / merge to `develop` | `1.0.1.dev{N}` | pre-release (`pip install --pre`) |
265
- | Push of a `v*` tag | the version in `pyproject.toml`, which the tag must match | latest |
266
- | Merge to `main` | nothing — CI only | — |
267
-
268
- `N` is `github.run_number` of the reusable workflow: a per-workflow integer
269
- that goes up on every run and stays the same across re-runs of that run (so a
270
- failed publish can be retried under the same version). It does not depend on
271
- git history or clone depth. After a successful release, bump `version` in
272
- `pyproject.toml` on `develop` so later `.devN` builds sort *after* what just
273
- landed; PEP 440 puts `1.0.1.devN` before `1.0.1`. Because of that ordering, a
274
- dependency spec of `insightfactory-cli>=1.0.0` does not match develop-channel
275
- `.devN` builds; an in-process consumer that wants them must specify a
276
- pre-release-bearing spec such as `insightfactory-cli>=1.0.0.dev0` (which also
277
- matches the final `1.0.0` release).
278
-
279
- A `v*` tag whose name does not match `pyproject.toml` fails before publishing.
280
-
281
- The two channels treat an already-published version differently, on purpose:
282
-
283
- - **Release channel (a tag):** publishing fails, naming the fix. A tag is a request to
284
- release that version; if it cannot be honoured, the run must say so rather than report
285
- success having published nothing. (git also refuses to push a tag that already exists,
286
- so this is hard to reach.)
287
- - **Dev channel (`develop`):** publishing is skipped quietly. `.dev{N}` uses
288
- `github.run_number`, which is stable across re-runs of one run, so an already-present
289
- version there can only mean a re-run of a run that already published — real idempotency,
290
- not a swallowed mistake.
291
-
292
- This split is why releasing moved off `main`. While a merge was the trigger, an
293
- already-published version *had* to be tolerated as a no-op for merges to stay green — so a
294
- merge that released nothing looked exactly like one that released, and a forgotten version
295
- bump shipped nothing silently.
296
-
297
- ## Differences from the Node CLI
298
-
299
- Behaviour matches `@insightfactory-ai/if-cli` except where the platforms
300
- genuinely diverge:
301
-
302
- - **Unknown-option wording** is aligned (`Unknown option '--flag'`). Other
303
- argparse messages (for example a missing option value) still differ from
304
- Node's `util.parseArgs`.
305
- - **Expiry timestamps** in human-readable output use the C-locale `%c` format
306
- rather than JavaScript `Date#toLocaleString()`.
307
- - **`--version`** reads the installed package metadata (`1.0.0` and later)
308
- rather than an npm `package.json`.
309
-
310
- On-disk profile and token-cache paths, JSON shapes, and file permissions stay
311
- compatible with the Node CLI, so you can switch implementations without
312
- re-authenticating.
313
-
314
- ## Licence
315
-
316
- Copyright 2026 insightfactory.ai. All rights reserved. This package is proprietary
317
- and may be used only under a separate written agreement with insightfactory.ai.