insightfactory-cli 1.0.3.dev17__tar.gz → 1.0.3.dev20__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.
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/CLAUDE.md +5 -2
- insightfactory_cli-1.0.3.dev17/README.md → insightfactory_cli-1.0.3.dev20/PKG-INFO +106 -1
- insightfactory_cli-1.0.3.dev17/PKG-INFO → insightfactory_cli-1.0.3.dev20/README.md +82 -19
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/pyproject.toml +5 -2
- insightfactory_cli-1.0.3.dev20/src/if_cli/assets/__init__.py +1 -0
- insightfactory_cli-1.0.3.dev20/src/if_cli/assets/insightfactoryai-logo.svg +55 -0
- insightfactory_cli-1.0.3.dev20/src/if_cli/callback_page.py +215 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/cli.py +6 -0
- insightfactory_cli-1.0.3.dev20/src/if_cli/commands/mcp.py +164 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/commands/profiles.py +3 -1
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/config.py +86 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/main.py +10 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/oauth.py +17 -9
- insightfactory_cli-1.0.3.dev20/src/if_cli/router/catalog.py +132 -0
- insightfactory_cli-1.0.3.dev20/src/if_cli/router/policy.py +86 -0
- insightfactory_cli-1.0.3.dev20/src/if_cli/router/server.py +285 -0
- insightfactory_cli-1.0.3.dev20/src/if_cli/router/upstream.py +107 -0
- insightfactory_cli-1.0.3.dev20/tests/__init__.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_cli.py +19 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_config.py +118 -0
- insightfactory_cli-1.0.3.dev20/tests/test_mcp_command.py +838 -0
- insightfactory_cli-1.0.3.dev20/tests/test_oauth_force_refresh.py +81 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_profiles.py +24 -0
- insightfactory_cli-1.0.3.dev20/tests/test_router_catalog.py +53 -0
- insightfactory_cli-1.0.3.dev20/tests/test_router_policy.py +133 -0
- insightfactory_cli-1.0.3.dev20/tests/test_router_server.py +13 -0
- insightfactory_cli-1.0.3.dev20/tests/test_router_upstream.py +80 -0
- insightfactory_cli-1.0.3.dev20/uv.lock +1100 -0
- insightfactory_cli-1.0.3.dev17/uv.lock +0 -212
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/.github/workflows/ci.yml +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/.github/workflows/claude.yml +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/.github/workflows/release.yml +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/.gitignore +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/.python-version +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/AGENTS.md +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/LICENSE +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/__init__.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/__main__.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/cache.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/colour.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/commands/__init__.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/commands/api.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/commands/config.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/commands/login.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/commands/logout.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/commands/set_token.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/commands/token.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/constants.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/http.py +0 -0
- {insightfactory_cli-1.0.3.dev17/tests → insightfactory_cli-1.0.3.dev20/src/if_cli/router}/__init__.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/src/if_cli/runtime.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/cache_writer.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/conftest.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/helpers.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/servers.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_api.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_api_command.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_cache.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_config_command.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_login.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_oauth.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_oauth_flow.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_programmatic_api.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_runtime.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_set_token.py +0 -0
- {insightfactory_cli-1.0.3.dev17 → insightfactory_cli-1.0.3.dev20}/tests/test_token.py +0 -0
|
@@ -16,11 +16,14 @@ uv run ty check
|
|
|
16
16
|
## Conventions
|
|
17
17
|
|
|
18
18
|
- Keep one module per CLI command under `src/if_cli/commands/`.
|
|
19
|
+
- The base install has no runtime dependencies. Optional features live in
|
|
20
|
+
extras and are imported lazily inside their command module.
|
|
19
21
|
- Never send a profile bearer token outside that profile's factory origin.
|
|
20
22
|
- OAuth uses authorization-code flow with PKCE and a loopback-only callback.
|
|
21
23
|
- Store profiles and tokens through the atomic private-file helpers.
|
|
22
|
-
-
|
|
23
|
-
Node
|
|
24
|
+
- The Node CLI is retired; `[factory <name>]` sections are Python-only. Profile
|
|
25
|
+
sections and the token store keep the Node layout (`~/.insightfactory`,
|
|
26
|
+
`INSIGHTFACTORY_CONFIG_DIR` override).
|
|
24
27
|
- Do not commit credentials, access tokens, refresh tokens, or client secrets.
|
|
25
28
|
- Test fixtures use only neutral hostnames (`factory.example`).
|
|
26
29
|
- Do not publish to PyPI from a developer machine. Publishing happens in CI: the
|
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: insightfactory-cli
|
|
3
|
+
Version: 1.0.3.dev20
|
|
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
|
+
Provides-Extra: mcp
|
|
18
|
+
Requires-Dist: anyio; extra == 'mcp'
|
|
19
|
+
Requires-Dist: exceptiongroup; (python_version < '3.11') and extra == 'mcp'
|
|
20
|
+
Requires-Dist: httpx2; extra == 'mcp'
|
|
21
|
+
Requires-Dist: mcp-types; extra == 'mcp'
|
|
22
|
+
Requires-Dist: mcp<3,>=2.1; extra == 'mcp'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
1
25
|
# insightfactory-cli
|
|
2
26
|
|
|
3
27
|
Profile-based authentication CLI for the InsightFactory Interfaces API. It
|
|
@@ -183,6 +207,15 @@ write to stdout, never launch a browser or block on interactive input, and only
|
|
|
183
207
|
use bounded waits. Changing or removing any of them is a breaking change and
|
|
184
208
|
needs a major version bump.
|
|
185
209
|
|
|
210
|
+
`get_valid_token` takes a keyword-only `force_refresh` (default `False`). When
|
|
211
|
+
`True`, it refreshes the cached token even if it is still fresh, which a
|
|
212
|
+
caller that just received a 401 from the factory needs: the cached token was
|
|
213
|
+
fresh a moment ago but the factory has since rejected it. With a refresh
|
|
214
|
+
token cached, this re-mints regardless of freshness; without one — a token
|
|
215
|
+
pasted through `if-cli set-token` — it raises `CliError` with the same
|
|
216
|
+
`if-cli login -p <name>` hint rather than serving the token that was just
|
|
217
|
+
rejected.
|
|
218
|
+
|
|
186
219
|
## How authentication works
|
|
187
220
|
|
|
188
221
|
- Profiles live in `~/.insightfactory/config`, one per customer and environment.
|
|
@@ -201,6 +234,76 @@ needs a major version bump.
|
|
|
201
234
|
the client ID from the factory's discovery document at runtime and holds no
|
|
202
235
|
client secret.
|
|
203
236
|
|
|
237
|
+
## MCP router (`if-cli mcp`)
|
|
238
|
+
|
|
239
|
+
Runs one MCP server over stdio that fronts several factory environments as a
|
|
240
|
+
single tool catalogue, instead of one MCP connection per environment. Every
|
|
241
|
+
tool gets a required `environment` argument and each call is forwarded to
|
|
242
|
+
that environment's own factory `/mcp` endpoint, authenticated with that
|
|
243
|
+
environment's `if-cli` profile — a profile's bearer token never reaches a
|
|
244
|
+
different environment's host. A tool is only offered on the environments
|
|
245
|
+
whose input schema matches the published one, so a factory on an older
|
|
246
|
+
release cannot be handed argument shapes it does not accept.
|
|
247
|
+
|
|
248
|
+
It needs the `mcp` extra, which is not part of the base install. Name its environments once
|
|
249
|
+
in a `[factory <name>]` section of `~/.insightfactory/config`, alongside profiles:
|
|
250
|
+
|
|
251
|
+
```ini
|
|
252
|
+
[factory foundry]
|
|
253
|
+
dev = example-dev
|
|
254
|
+
tst = example-tst
|
|
255
|
+
writable = dev
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Every key except `writable` and `catalog` is `<environment code> = <profile name>`; file
|
|
259
|
+
order is the environment order. `writable` is a comma-separated list of codes (default
|
|
260
|
+
none); `catalog` is one code (default the first environment). Then:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
uv tool install 'insightfactory-cli[mcp]'
|
|
264
|
+
uvx --from 'insightfactory-cli[mcp]' if-cli mcp foundry
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Add it to `.mcp.json`:
|
|
268
|
+
|
|
269
|
+
```json
|
|
270
|
+
{
|
|
271
|
+
"mcpServers": {
|
|
272
|
+
"if-factory": {
|
|
273
|
+
"command": "uvx",
|
|
274
|
+
"args": ["--from", "insightfactory-cli[mcp]", "if-cli", "mcp", "foundry"]
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
`--env`, `--writable`, `--catalog` and `--allow-tool` still work without a factory section
|
|
281
|
+
(`--env` is required in that case), and each one overrides the section when both are given:
|
|
282
|
+
a repeated `--env CODE=PROFILE` replaces that code's profile, `--writable`/`--catalog`, when
|
|
283
|
+
passed at all, replace the section's value outright, and `--allow-tool` is always additive.
|
|
284
|
+
|
|
285
|
+
| Flag | Meaning |
|
|
286
|
+
|---|---|
|
|
287
|
+
| `FACTORY` (optional, first positional) | Name of a `[factory <name>]` config section supplying the flags below. |
|
|
288
|
+
| `--env CODE=PROFILE` (repeatable, required without `FACTORY`) | The environment code the model passes, mapped to the `if-cli` profile that reaches it. |
|
|
289
|
+
| `--writable CODE` (repeatable) | Environments where write tools are allowed. Everything else is read-only. |
|
|
290
|
+
| `--read-only` | Forces every environment read-only, overriding both the section's `writable` and any `--writable`. Cannot be combined with `--writable`. |
|
|
291
|
+
| `--catalog CODE` (default: the first environment) | Whose tool list is published; its schemas are the reference every environment is checked against. |
|
|
292
|
+
| `--allow-tool NAME` (repeatable) | Extra tool names treated as read-only, beyond the shipped list. |
|
|
293
|
+
| `--timeout SECONDS` (default `120`) | Per upstream request; higher than the `api` command's default because some tools are slow. |
|
|
294
|
+
|
|
295
|
+
`mcp`'s `--timeout` does not read `INSIGHTFACTORY_REQUEST_TIMEOUT`: it parses the flag with
|
|
296
|
+
`parse_timeout_seconds` rather than `resolve_request_timeout`, because a long-running stdio
|
|
297
|
+
server is not the one-shot request that environment variable is scoped to.
|
|
298
|
+
|
|
299
|
+
Only environments passed to `--writable` accept mutating tools; every other
|
|
300
|
+
environment allows only tools it knows to read (an allowlist, not a
|
|
301
|
+
blocklist — a tool the model reaches through `invoke_tool` is never in the
|
|
302
|
+
published catalogue, so a blocklist could never cover everything callable).
|
|
303
|
+
Passing dev-only writes to production means changing an argument value, not
|
|
304
|
+
connecting to a different server, so keep `--writable` scoped to the
|
|
305
|
+
environments meant to be written by hand.
|
|
306
|
+
|
|
204
307
|
## Development
|
|
205
308
|
|
|
206
309
|
```bash
|
|
@@ -218,7 +321,9 @@ uv run if-cli --version
|
|
|
218
321
|
INSIGHTFACTORY_CONFIG_DIR=$(mktemp -d) uv run if-cli profiles
|
|
219
322
|
```
|
|
220
323
|
|
|
221
|
-
Python 3.10 or newer is required.
|
|
324
|
+
Python 3.10 or newer is required. The base install has no runtime dependencies;
|
|
325
|
+
optional features, such as `if-cli mcp`, ship as extras and are imported lazily
|
|
326
|
+
inside their command module.
|
|
222
327
|
|
|
223
328
|
## Release
|
|
224
329
|
|
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: insightfactory-cli
|
|
3
|
-
Version: 1.0.3.dev17
|
|
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
1
|
# insightfactory-cli
|
|
20
2
|
|
|
21
3
|
Profile-based authentication CLI for the InsightFactory Interfaces API. It
|
|
@@ -201,6 +183,15 @@ write to stdout, never launch a browser or block on interactive input, and only
|
|
|
201
183
|
use bounded waits. Changing or removing any of them is a breaking change and
|
|
202
184
|
needs a major version bump.
|
|
203
185
|
|
|
186
|
+
`get_valid_token` takes a keyword-only `force_refresh` (default `False`). When
|
|
187
|
+
`True`, it refreshes the cached token even if it is still fresh, which a
|
|
188
|
+
caller that just received a 401 from the factory needs: the cached token was
|
|
189
|
+
fresh a moment ago but the factory has since rejected it. With a refresh
|
|
190
|
+
token cached, this re-mints regardless of freshness; without one — a token
|
|
191
|
+
pasted through `if-cli set-token` — it raises `CliError` with the same
|
|
192
|
+
`if-cli login -p <name>` hint rather than serving the token that was just
|
|
193
|
+
rejected.
|
|
194
|
+
|
|
204
195
|
## How authentication works
|
|
205
196
|
|
|
206
197
|
- Profiles live in `~/.insightfactory/config`, one per customer and environment.
|
|
@@ -219,6 +210,76 @@ needs a major version bump.
|
|
|
219
210
|
the client ID from the factory's discovery document at runtime and holds no
|
|
220
211
|
client secret.
|
|
221
212
|
|
|
213
|
+
## MCP router (`if-cli mcp`)
|
|
214
|
+
|
|
215
|
+
Runs one MCP server over stdio that fronts several factory environments as a
|
|
216
|
+
single tool catalogue, instead of one MCP connection per environment. Every
|
|
217
|
+
tool gets a required `environment` argument and each call is forwarded to
|
|
218
|
+
that environment's own factory `/mcp` endpoint, authenticated with that
|
|
219
|
+
environment's `if-cli` profile — a profile's bearer token never reaches a
|
|
220
|
+
different environment's host. A tool is only offered on the environments
|
|
221
|
+
whose input schema matches the published one, so a factory on an older
|
|
222
|
+
release cannot be handed argument shapes it does not accept.
|
|
223
|
+
|
|
224
|
+
It needs the `mcp` extra, which is not part of the base install. Name its environments once
|
|
225
|
+
in a `[factory <name>]` section of `~/.insightfactory/config`, alongside profiles:
|
|
226
|
+
|
|
227
|
+
```ini
|
|
228
|
+
[factory foundry]
|
|
229
|
+
dev = example-dev
|
|
230
|
+
tst = example-tst
|
|
231
|
+
writable = dev
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Every key except `writable` and `catalog` is `<environment code> = <profile name>`; file
|
|
235
|
+
order is the environment order. `writable` is a comma-separated list of codes (default
|
|
236
|
+
none); `catalog` is one code (default the first environment). Then:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
uv tool install 'insightfactory-cli[mcp]'
|
|
240
|
+
uvx --from 'insightfactory-cli[mcp]' if-cli mcp foundry
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Add it to `.mcp.json`:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"mcpServers": {
|
|
248
|
+
"if-factory": {
|
|
249
|
+
"command": "uvx",
|
|
250
|
+
"args": ["--from", "insightfactory-cli[mcp]", "if-cli", "mcp", "foundry"]
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`--env`, `--writable`, `--catalog` and `--allow-tool` still work without a factory section
|
|
257
|
+
(`--env` is required in that case), and each one overrides the section when both are given:
|
|
258
|
+
a repeated `--env CODE=PROFILE` replaces that code's profile, `--writable`/`--catalog`, when
|
|
259
|
+
passed at all, replace the section's value outright, and `--allow-tool` is always additive.
|
|
260
|
+
|
|
261
|
+
| Flag | Meaning |
|
|
262
|
+
|---|---|
|
|
263
|
+
| `FACTORY` (optional, first positional) | Name of a `[factory <name>]` config section supplying the flags below. |
|
|
264
|
+
| `--env CODE=PROFILE` (repeatable, required without `FACTORY`) | The environment code the model passes, mapped to the `if-cli` profile that reaches it. |
|
|
265
|
+
| `--writable CODE` (repeatable) | Environments where write tools are allowed. Everything else is read-only. |
|
|
266
|
+
| `--read-only` | Forces every environment read-only, overriding both the section's `writable` and any `--writable`. Cannot be combined with `--writable`. |
|
|
267
|
+
| `--catalog CODE` (default: the first environment) | Whose tool list is published; its schemas are the reference every environment is checked against. |
|
|
268
|
+
| `--allow-tool NAME` (repeatable) | Extra tool names treated as read-only, beyond the shipped list. |
|
|
269
|
+
| `--timeout SECONDS` (default `120`) | Per upstream request; higher than the `api` command's default because some tools are slow. |
|
|
270
|
+
|
|
271
|
+
`mcp`'s `--timeout` does not read `INSIGHTFACTORY_REQUEST_TIMEOUT`: it parses the flag with
|
|
272
|
+
`parse_timeout_seconds` rather than `resolve_request_timeout`, because a long-running stdio
|
|
273
|
+
server is not the one-shot request that environment variable is scoped to.
|
|
274
|
+
|
|
275
|
+
Only environments passed to `--writable` accept mutating tools; every other
|
|
276
|
+
environment allows only tools it knows to read (an allowlist, not a
|
|
277
|
+
blocklist — a tool the model reaches through `invoke_tool` is never in the
|
|
278
|
+
published catalogue, so a blocklist could never cover everything callable).
|
|
279
|
+
Passing dev-only writes to production means changing an argument value, not
|
|
280
|
+
connecting to a different server, so keep `--writable` scoped to the
|
|
281
|
+
environments meant to be written by hand.
|
|
282
|
+
|
|
222
283
|
## Development
|
|
223
284
|
|
|
224
285
|
```bash
|
|
@@ -236,7 +297,9 @@ uv run if-cli --version
|
|
|
236
297
|
INSIGHTFACTORY_CONFIG_DIR=$(mktemp -d) uv run if-cli profiles
|
|
237
298
|
```
|
|
238
299
|
|
|
239
|
-
Python 3.10 or newer is required.
|
|
300
|
+
Python 3.10 or newer is required. The base install has no runtime dependencies;
|
|
301
|
+
optional features, such as `if-cli mcp`, ship as extras and are imported lazily
|
|
302
|
+
inside their command module.
|
|
240
303
|
|
|
241
304
|
## Release
|
|
242
305
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "insightfactory-cli"
|
|
3
|
-
version = "1.0.3.
|
|
3
|
+
version = "1.0.3.dev20"
|
|
4
4
|
description = "Profile-based authentication CLI for the InsightFactory Interfaces API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -23,12 +23,15 @@ if-cli = "if_cli.main:main"
|
|
|
23
23
|
[project.urls]
|
|
24
24
|
Homepage = "https://insightfactory.ai"
|
|
25
25
|
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
mcp = ["mcp>=2.1,<3", "anyio", "httpx2", "mcp-types", "exceptiongroup; python_version < '3.11'"]
|
|
28
|
+
|
|
26
29
|
[build-system]
|
|
27
30
|
requires = ["hatchling"]
|
|
28
31
|
build-backend = "hatchling.build"
|
|
29
32
|
|
|
30
33
|
[dependency-groups]
|
|
31
|
-
dev = ["pytest", "ruff", "ty"]
|
|
34
|
+
dev = ["pytest", "ruff", "ty", "insightfactory-cli[mcp]"]
|
|
32
35
|
|
|
33
36
|
[tool.hatch.build.targets.wheel]
|
|
34
37
|
packages = ["src/if_cli"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Static files shipped with the CLI."""
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<svg class="logo" role="img" aria-label="insightfactory.ai" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 286.67 62.53">
|
|
2
|
+
<defs>
|
|
3
|
+
<style>
|
|
4
|
+
.iflogo-1 {
|
|
5
|
+
fill: #fab314;
|
|
6
|
+
}
|
|
7
|
+
.iflogo-2 {
|
|
8
|
+
fill: #f8e86c;
|
|
9
|
+
}
|
|
10
|
+
.iflogo-3 {
|
|
11
|
+
fill: #fad121;
|
|
12
|
+
}
|
|
13
|
+
.iflogo-4 {
|
|
14
|
+
fill: #1f252e;
|
|
15
|
+
}
|
|
16
|
+
</style>
|
|
17
|
+
</defs>
|
|
18
|
+
<g id="ARTWORK">
|
|
19
|
+
<g>
|
|
20
|
+
<g>
|
|
21
|
+
<polygon class="iflogo-4" points="32.49 37.52 32.5 37.53 32.49 37.52 32.49 37.52"/>
|
|
22
|
+
<polygon class="iflogo-4" points="21.66 18.76 0 31.27 21.66 43.78 21.66 18.76"/>
|
|
23
|
+
<g>
|
|
24
|
+
<polygon class="iflogo-4" points="43.32 31.27 43.32 43.78 54.15 37.52 43.32 31.27"/>
|
|
25
|
+
<polygon class="iflogo-4" points="43.32 43.78 43.32 56.28 54.15 62.53 64.98 56.28 43.32 43.78"/>
|
|
26
|
+
</g>
|
|
27
|
+
<polygon class="iflogo-2" points="21.66 6.26 32.49 12.51 43.32 6.26 32.48 0 21.66 6.26"/>
|
|
28
|
+
<polygon class="iflogo-3" points="32.49 12.51 32.49 12.51 21.66 6.26 21.66 18.76 21.66 18.76 21.66 43.78 32.49 50.03 32.49 37.52 32.49 12.51"/>
|
|
29
|
+
<g>
|
|
30
|
+
<polygon class="iflogo-1" points="32.49 12.51 32.5 25.02 43.32 18.76 43.32 6.26 32.49 12.51"/>
|
|
31
|
+
<polygon class="iflogo-1" points="43.32 56.28 43.32 43.78 43.32 31.27 32.49 37.52 32.49 50.03 43.32 56.28"/>
|
|
32
|
+
</g>
|
|
33
|
+
</g>
|
|
34
|
+
<g>
|
|
35
|
+
<path class="iflogo-4" d="m75.28,23.74c-.81,0-1.47-.27-1.99-.8-.52-.53-.77-1.17-.77-1.91s.26-1.41.77-1.93,1.18-.78,1.99-.78c.76,0,1.41.26,1.94.78.53.52.79,1.17.79,1.93s-.26,1.38-.79,1.91c-.53.53-1.17.8-1.94.8Zm-2.19,2.23h4.31v17.39h-4.31v-17.39Z"/>
|
|
36
|
+
<path class="iflogo-4" d="m79.46,25.97h3.91l.3,1.77c.54-.63,1.2-1.12,1.97-1.48.78-.36,1.61-.54,2.51-.54,1.33,0,2.46.29,3.39.87.93.58,1.65,1.42,2.16,2.52.51,1.1.76,2.46.76,4.09v10.16h-4.35v-9.57c0-1.34-.26-2.35-.78-3.03s-1.29-1.01-2.33-1.01-1.86.34-2.41,1.03c-.55.68-.83,1.7-.83,3.04v9.53h-4.31v-17.39Z"/>
|
|
37
|
+
<path class="iflogo-4" d="m100.08,37.69c.13.58.36,1.02.69,1.32.33.3.7.52,1.11.64.42.13.84.19,1.26.19.79,0,1.4-.16,1.85-.47.45-.31.67-.71.67-1.2,0-.42-.16-.75-.47-.99-.31-.24-.71-.43-1.2-.57-.48-.14-.99-.27-1.53-.38-.61-.16-1.25-.34-1.94-.54-.69-.2-1.33-.48-1.94-.85-.61-.37-1.11-.85-1.5-1.44-.39-.59-.59-1.34-.59-2.24,0-1.09.28-2.03.84-2.83.56-.8,1.35-1.43,2.38-1.9s2.21-.7,3.56-.7c1.59,0,2.94.35,4.04,1.04,1.1.7,1.9,1.69,2.39,2.99l-3.78,1.11c-.13-.32-.34-.59-.61-.8s-.59-.36-.96-.45c-.37-.09-.75-.14-1.13-.14-.65,0-1.19.13-1.62.4-.43.27-.64.63-.64,1.1,0,.28.08.5.24.68s.38.32.66.43c.28.12.61.22.98.31.37.09.78.2,1.23.31.7.19,1.4.4,2.11.64.71.24,1.36.55,1.96.92.59.37,1.07.87,1.43,1.5.36.63.56,1.42.61,2.37,0,1.02-.28,1.94-.84,2.77s-1.37,1.48-2.43,1.96c-1.06.49-2.34.73-3.84.73-1.75,0-3.24-.38-4.47-1.15s-2.03-1.96-2.41-3.58l3.88-1.18Z"/>
|
|
38
|
+
<path class="iflogo-4" d="m114.61,23.74c-.81,0-1.47-.27-1.99-.8-.52-.53-.77-1.17-.77-1.91s.26-1.41.77-1.93,1.18-.78,1.99-.78c.76,0,1.41.26,1.94.78.53.52.79,1.17.79,1.93s-.26,1.38-.79,1.91c-.53.53-1.17.8-1.94.8Zm-2.19,2.23h4.31v17.39h-4.31v-17.39Z"/>
|
|
39
|
+
<path class="iflogo-4" d="m131.9,25.97h3.91v14.82c0,1.97-.47,3.67-1.42,5.1-.94,1.43-2.31,2.52-4.09,3.29-1.79.76-3.93,1.15-6.42,1.15l-.67-3.76c2.72-.05,4.78-.54,6.18-1.5s2.11-2.32,2.11-4.1v-.07l.57-.21c-.54.81-1.28,1.5-2.22,2.07s-2.07.85-3.37.85c-1.48,0-2.79-.37-3.91-1.11-1.12-.74-2.01-1.78-2.65-3.11-.64-1.33-.96-2.91-.96-4.71s.32-3.39.96-4.73c.64-1.34,1.52-2.38,2.65-3.11,1.12-.73,2.43-1.1,3.91-1.1.76,0,1.48.11,2.14.33.66.22,1.25.51,1.75.87.5.36.93.76,1.26,1.2l.27-2.16Zm-8.32,8.7c0,1.46.37,2.64,1.09,3.53.73.89,1.7,1.34,2.92,1.34.7,0,1.34-.19,1.92-.57.58-.38,1.06-.94,1.43-1.69.37-.74.56-1.61.56-2.61s-.19-1.92-.56-2.64c-.37-.72-.85-1.27-1.45-1.65-.59-.38-1.23-.57-1.9-.57-1.21,0-2.19.45-2.92,1.34-.73.89-1.09,2.07-1.09,3.53Z"/>
|
|
40
|
+
<path class="iflogo-4" d="m138.5,18.32h4.31v9.36c.52-.58,1.16-1.05,1.94-1.41.78-.36,1.65-.54,2.61-.54,1.28,0,2.39.29,3.34.87.94.58,1.68,1.42,2.21,2.52.53,1.1.79,2.46.79,4.09v10.16h-4.55v-9.57c0-1.34-.26-2.35-.78-3.03s-1.29-1.01-2.33-1.01-1.86.37-2.41,1.1c-.55.73-.83,1.77-.83,3.11v9.39h-4.31v-25.04Z"/>
|
|
41
|
+
<path class="iflogo-4" d="m157.01,25.97v-4.87h4.35v4.87h3.74v3.93h-3.74v7.06c0,.9.19,1.54.57,1.91.38.37.86.57,1.43.59.57.02,1.15-.01,1.74-.1v3.83c-1.03.23-2.04.3-3.02.21-.98-.09-1.85-.37-2.61-.82-.76-.45-1.36-1.14-1.8-2.07-.44-.93-.66-2.11-.66-3.55v-7.06h-2.39v-3.93h2.39Z"/>
|
|
42
|
+
<path class="iflogo-4" d="m170.42,23.92c0-1.27.2-2.35.61-3.23s1.05-1.52,1.94-1.93c.89-.41,2.07-.55,3.56-.43v2.33c-1.35-.12-2.28.11-2.8.68s-.78,1.43-.78,2.59v2.05h3.71v2.43h-3.71v14.96h-2.53v-14.96h-2.36v-2.43h2.36v-2.05Z"/>
|
|
43
|
+
<path class="iflogo-4" d="m188.25,43.36l-.2-3.13c-.47,1.07-1.17,1.9-2.09,2.49-.92.59-2.09.89-3.5.89-1.21,0-2.25-.2-3.1-.59-.85-.39-1.51-.96-1.97-1.69-.46-.73-.69-1.59-.69-2.59,0-1.46.53-2.66,1.58-3.6,1.06-.94,2.58-1.51,4.58-1.72l5.19-.59v-1.39c0-.97-.35-1.76-1.04-2.35s-1.64-.89-2.83-.89-2.14.28-2.92.83c-.78.56-1.31,1.36-1.6,2.4l-2.29-.83c.45-1.53,1.28-2.72,2.48-3.58,1.2-.86,2.67-1.29,4.4-1.29,2,0,3.56.53,4.67,1.58,1.11,1.06,1.67,2.49,1.67,4.3v11.76h-2.33Zm-.2-8.31l-5.22.63c-1.12.14-1.98.46-2.56.96-.58.5-.88,1.15-.88,1.96s.29,1.46.86,1.95c.57.49,1.4.73,2.48.73,1.24,0,2.25-.25,3.03-.77.79-.51,1.37-1.2,1.74-2.07.37-.87.56-1.81.56-2.83v-.56Z"/>
|
|
44
|
+
<path class="iflogo-4" d="m201.02,43.61c-1.64,0-3.08-.37-4.31-1.11-1.24-.74-2.2-1.78-2.88-3.11-.69-1.33-1.03-2.88-1.03-4.64s.34-3.36,1.03-4.71,1.65-2.41,2.88-3.17c1.24-.75,2.67-1.13,4.31-1.13,1.73,0,3.25.41,4.55,1.23,1.3.82,2.25,1.97,2.83,3.43l-2.33.83c-.34-.93-.95-1.66-1.84-2.21-.89-.54-1.92-.82-3.08-.82s-2.14.27-2.98.8-1.49,1.29-1.96,2.26c-.46.97-.69,2.12-.69,3.44,0,1.95.51,3.51,1.52,4.68s2.36,1.76,4.04,1.76c1.15,0,2.17-.27,3.07-.8.9-.53,1.56-1.25,1.99-2.16l2.29.83c-.61,1.44-1.56,2.56-2.86,3.38-1.3.81-2.82,1.22-4.55,1.22Z"/>
|
|
45
|
+
<path class="iflogo-4" d="m212.07,25.97v-4.87h2.53v4.87h4.48v2.43h-4.48v9.36c0,1.07.22,1.84.66,2.33.44.49,1,.78,1.68.87.69.09,1.4.09,2.14,0v2.26c-.88.21-1.74.27-2.58.17-.84-.09-1.6-.35-2.26-.78-.66-.43-1.19-1.04-1.58-1.83-.39-.79-.59-1.8-.59-3.03v-9.36h-2.53v-2.43h2.53Z"/>
|
|
46
|
+
<path class="iflogo-4" d="m228.49,43.61c-1.6,0-3.02-.36-4.28-1.08-1.26-.72-2.25-1.74-2.97-3.08-.72-1.33-1.08-2.92-1.08-4.75s.37-3.46,1.1-4.8c.73-1.34,1.73-2.38,3-3.1,1.27-.72,2.7-1.08,4.3-1.08s3.03.36,4.3,1.08c1.27.72,2.27,1.75,3,3.08.73,1.33,1.1,2.92,1.1,4.75s-.37,3.46-1.11,4.8-1.75,2.38-3.03,3.1-2.72,1.08-4.31,1.08Zm-.03-2.47c.99,0,1.92-.23,2.8-.68.88-.45,1.59-1.16,2.14-2.12.55-.96.83-2.19.83-3.67s-.27-2.74-.81-3.69c-.54-.95-1.24-1.65-2.11-2.1s-1.79-.68-2.78-.68-1.91.23-2.76.68-1.54,1.16-2.07,2.12c-.53.96-.79,2.19-.79,3.67s.26,2.74.78,3.69c.52.95,1.2,1.65,2.04,2.1.84.45,1.76.68,2.75.68Z"/>
|
|
47
|
+
<path class="iflogo-4" d="m238.93,25.97h2.33l.13,2.54c.45-.63.99-1.16,1.63-1.6.64-.44,1.35-.75,2.12-.92.77-.17,1.6-.18,2.48-.02v2.61c-.94-.18-1.79-.18-2.54,0-.75.19-1.4.53-1.94,1.04-.54.51-.96,1.15-1.25,1.93-.29.78-.44,1.68-.44,2.7v9.11h-2.53v-17.39Z"/>
|
|
48
|
+
<path class="iflogo-4" d="m262.19,25.97h2.63l-9.64,24.35h-2.43l2.87-7.2-6.81-17.15h2.76l5.39,14.05,5.22-14.05Z"/>
|
|
49
|
+
<path class="iflogo-4" d="m263.09,43.52c-.46,0-.84-.16-1.15-.5-.31-.33-.46-.73-.46-1.21s.15-.88.46-1.21c.31-.33.69-.5,1.15-.5s.82.17,1.14.5c.32.33.48.73.48,1.21s-.16.88-.48,1.21c-.32.33-.7.5-1.14.5Z"/>
|
|
50
|
+
<path class="iflogo-4" d="m278.25,43.36l-.2-3.13c-.47,1.07-1.17,1.9-2.09,2.49-.92.59-2.09.89-3.5.89-1.21,0-2.25-.2-3.1-.59-.85-.39-1.51-.96-1.97-1.69-.46-.73-.69-1.59-.69-2.59,0-1.46.53-2.66,1.58-3.6,1.06-.94,2.58-1.51,4.58-1.72l5.19-.59v-1.39c0-.97-.35-1.76-1.04-2.35s-1.64-.89-2.83-.89-2.14.28-2.92.83c-.78.56-1.31,1.36-1.6,2.4l-2.29-.83c.45-1.53,1.28-2.72,2.48-3.58,1.2-.86,2.67-1.29,4.4-1.29,2,0,3.56.53,4.67,1.58,1.11,1.06,1.67,2.49,1.67,4.3v11.76h-2.33Zm-.2-8.31l-5.22.63c-1.12.14-1.98.46-2.56.96-.58.5-.88,1.15-.88,1.96s.29,1.46.86,1.95c.57.49,1.4.73,2.48.73,1.24,0,2.25-.25,3.03-.77.79-.51,1.37-1.2,1.74-2.07.37-.87.56-1.81.56-2.83v-.56Z"/>
|
|
51
|
+
<path class="iflogo-4" d="m284.92,22.49c-.52,0-.94-.17-1.28-.52-.34-.35-.51-.78-.51-1.29s.17-.97.51-1.32.76-.52,1.28-.52c.47,0,.88.17,1.23.52.35.35.52.79.52,1.32s-.17.94-.52,1.29c-.35.35-.76.52-1.23.52Zm-1.28,3.48h2.49v17.39h-2.49v-17.39Z"/>
|
|
52
|
+
</g>
|
|
53
|
+
</g>
|
|
54
|
+
</g>
|
|
55
|
+
</svg>
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Branded HTML for the loopback OAuth callback tab.
|
|
2
|
+
|
|
3
|
+
This tab is the frame straight after the Auth0 Universal Login widget, so it
|
|
4
|
+
copies that theme rather than the Interfaces app. Every value here mirrors
|
|
5
|
+
``auth0_branding_theme.insight_factory`` in
|
|
6
|
+
``terraform-Insightfactory-infrastructure/Auth0/modules/auth0/branding.tf``, and
|
|
7
|
+
the comments name the Terraform attribute each one came from. Change them together.
|
|
8
|
+
|
|
9
|
+
The page is self-contained: the logo is inline SVG and the CSS is inline, so it
|
|
10
|
+
renders from the loopback server with no network access beyond the webfont.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from functools import lru_cache
|
|
16
|
+
from importlib.resources import files
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@lru_cache(maxsize=1)
|
|
20
|
+
def logo_svg() -> str:
|
|
21
|
+
"""The wordmark Auth0 serves from the CDN, shipped with the package.
|
|
22
|
+
|
|
23
|
+
Auth0 loads it from cdn.insightfactory.ai. We inline the same file so the
|
|
24
|
+
callback page does not depend on the CDN being reachable.
|
|
25
|
+
"""
|
|
26
|
+
return files("if_cli.assets").joinpath("insightfactoryai-logo.svg").read_text(encoding="utf-8")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
STYLES = """\
|
|
30
|
+
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap");
|
|
31
|
+
|
|
32
|
+
:root {
|
|
33
|
+
--page-background: #20252e; /* page_background.background_color */
|
|
34
|
+
--widget-background: #ffffff; /* colors.widget_background */
|
|
35
|
+
--header: #20252e; /* colors.header */
|
|
36
|
+
--body-text: #20252e; /* colors.body_text */
|
|
37
|
+
--icons: #65676e; /* colors.icons */
|
|
38
|
+
--input-border: #c9cace; /* colors.input_border */
|
|
39
|
+
--success: #13a688; /* colors.success */
|
|
40
|
+
--error: #d03c38; /* colors.error */
|
|
41
|
+
--success-soft: rgba(19, 166, 136, 0.12);
|
|
42
|
+
--error-soft: rgba(208, 60, 56, 0.12);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
* {
|
|
46
|
+
box-sizing: border-box;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
body {
|
|
50
|
+
margin: 0;
|
|
51
|
+
min-height: 100vh;
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center; /* page_layout: center */
|
|
54
|
+
justify-content: center;
|
|
55
|
+
padding: 24px;
|
|
56
|
+
background: var(--page-background);
|
|
57
|
+
color: var(--body-text);
|
|
58
|
+
font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
59
|
+
font-size: 16px; /* fonts.reference_text_size */
|
|
60
|
+
font-weight: 300;
|
|
61
|
+
line-height: 1.5;
|
|
62
|
+
-webkit-font-smoothing: antialiased;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
main {
|
|
66
|
+
width: 100%;
|
|
67
|
+
max-width: 400px;
|
|
68
|
+
padding: 40px 32px 32px;
|
|
69
|
+
border-radius: 8px; /* borders.widget_corner_radius */
|
|
70
|
+
background: var(--widget-background);
|
|
71
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28); /* borders.show_widget_shadow */
|
|
72
|
+
text-align: center; /* widget.header_text_alignment */
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.logo {
|
|
76
|
+
height: 68px; /* widget.logo_height */
|
|
77
|
+
width: auto;
|
|
78
|
+
max-width: 100%;
|
|
79
|
+
display: block;
|
|
80
|
+
margin: 0 auto 24px; /* widget.logo_position: center */
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.pill {
|
|
84
|
+
display: inline-flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
gap: 7px;
|
|
87
|
+
margin-bottom: 16px;
|
|
88
|
+
padding: 4px 12px 4px 10px;
|
|
89
|
+
border-radius: 999px;
|
|
90
|
+
background: var(--pill-soft);
|
|
91
|
+
color: var(--pill);
|
|
92
|
+
font-size: 14px;
|
|
93
|
+
font-weight: 500;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.dot {
|
|
97
|
+
width: 7px;
|
|
98
|
+
height: 7px;
|
|
99
|
+
border-radius: 50%;
|
|
100
|
+
background: currentColor;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
h1 {
|
|
104
|
+
margin: 0 0 10px;
|
|
105
|
+
color: var(--header); /* colors.header */
|
|
106
|
+
font-size: 24px; /* fonts.title: 150% of 16px */
|
|
107
|
+
font-weight: 300; /* fonts.title.bold = false */
|
|
108
|
+
line-height: 1.25;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
p {
|
|
112
|
+
margin: 0;
|
|
113
|
+
color: var(--icons);
|
|
114
|
+
font-size: 14px; /* fonts.subtitle: 87.5% of 16px */
|
|
115
|
+
font-weight: 400;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
code {
|
|
119
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
120
|
+
font-size: 13px;
|
|
121
|
+
color: var(--body-text);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
pre {
|
|
125
|
+
margin: 20px 0 0;
|
|
126
|
+
padding: 12px 14px;
|
|
127
|
+
border: 1px solid var(--input-border); /* colors.input_border */
|
|
128
|
+
border-radius: 3px; /* borders.input_border_radius */
|
|
129
|
+
color: var(--body-text);
|
|
130
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
131
|
+
font-size: 13px;
|
|
132
|
+
text-align: left;
|
|
133
|
+
white-space: pre-wrap;
|
|
134
|
+
word-break: break-word;
|
|
135
|
+
}
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
_TEMPLATE = """\
|
|
139
|
+
<!DOCTYPE html>
|
|
140
|
+
<html lang="en">
|
|
141
|
+
<head>
|
|
142
|
+
<meta charset="utf-8">
|
|
143
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
144
|
+
<title>{title} | insightfactory.ai</title>
|
|
145
|
+
<style>
|
|
146
|
+
{styles}
|
|
147
|
+
</style>
|
|
148
|
+
</head>
|
|
149
|
+
<body>
|
|
150
|
+
<main style="--pill: var({pill_colour}); --pill-soft: var({pill_soft});">
|
|
151
|
+
{logo}
|
|
152
|
+
<span class="pill"><span class="dot"></span>{pill}</span>
|
|
153
|
+
<h1>{heading}</h1>
|
|
154
|
+
<p>{message}</p>
|
|
155
|
+
{detail}
|
|
156
|
+
</main>
|
|
157
|
+
</body>
|
|
158
|
+
</html>
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def render(
|
|
163
|
+
*,
|
|
164
|
+
title: str,
|
|
165
|
+
pill: str,
|
|
166
|
+
heading: str,
|
|
167
|
+
message: str,
|
|
168
|
+
detail: str = "",
|
|
169
|
+
ok: bool = True,
|
|
170
|
+
) -> bytes:
|
|
171
|
+
"""Render one callback page, ready to write to the socket."""
|
|
172
|
+
return _TEMPLATE.format(
|
|
173
|
+
title=title,
|
|
174
|
+
styles=STYLES,
|
|
175
|
+
logo=logo_svg(),
|
|
176
|
+
pill=pill,
|
|
177
|
+
pill_colour="--success" if ok else "--error",
|
|
178
|
+
pill_soft="--success-soft" if ok else "--error-soft",
|
|
179
|
+
heading=heading,
|
|
180
|
+
message=message,
|
|
181
|
+
detail=detail,
|
|
182
|
+
).encode("utf-8")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def success_page() -> bytes:
|
|
186
|
+
return render(
|
|
187
|
+
title="Login complete",
|
|
188
|
+
pill="Connected",
|
|
189
|
+
heading="Authentication successful",
|
|
190
|
+
message="You can close this tab and return to the terminal.",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def failure_page(error: str, description: str) -> bytes:
|
|
195
|
+
return render(
|
|
196
|
+
title="Login failed",
|
|
197
|
+
pill="Not connected",
|
|
198
|
+
heading="Authentication failed",
|
|
199
|
+
message="Close this tab and run <code>if-cli login</code> again.",
|
|
200
|
+
detail=f"<pre>{error}: {description}</pre>" if error or description else "",
|
|
201
|
+
ok=False,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def state_mismatch_page() -> bytes:
|
|
206
|
+
return render(
|
|
207
|
+
title="Login callback ignored",
|
|
208
|
+
pill="Not connected",
|
|
209
|
+
heading="Callback ignored",
|
|
210
|
+
message=(
|
|
211
|
+
"The callback state did not match the one this login started with, "
|
|
212
|
+
"so it was discarded. Close this tab and run <code>if-cli login</code> again."
|
|
213
|
+
),
|
|
214
|
+
ok=False,
|
|
215
|
+
)
|
|
@@ -53,6 +53,8 @@ def _parse_args_with_positionals(argv: list[str], *, options: Options) -> tuple[
|
|
|
53
53
|
"""Parse argv allowing options and positionals in any order (Node util.parseArgs parity)."""
|
|
54
54
|
values: dict[str, Any] = {}
|
|
55
55
|
for name, spec in options.items():
|
|
56
|
+
if spec.get("repeat"):
|
|
57
|
+
die(f"internal error: option '--{name}' has repeat=True, which allow_positionals parsing does not support")
|
|
56
58
|
if spec["type"] == "boolean":
|
|
57
59
|
values[name] = spec.get("default", False)
|
|
58
60
|
else:
|
|
@@ -136,6 +138,10 @@ def parse_args(
|
|
|
136
138
|
dest = name.replace("-", "_")
|
|
137
139
|
if spec["type"] == "boolean":
|
|
138
140
|
parser.add_argument(*flags, dest=dest, action="store_true", default=spec.get("default", False))
|
|
141
|
+
elif spec.get("repeat"):
|
|
142
|
+
# `action="append"` with `default=None` (rather than a spec-level `[]`) so
|
|
143
|
+
# unset stays None and a shared mutable default can't leak across calls.
|
|
144
|
+
parser.add_argument(*flags, dest=dest, action="append", default=None)
|
|
139
145
|
else:
|
|
140
146
|
parser.add_argument(*flags, dest=dest, default=spec.get("default"))
|
|
141
147
|
namespace = parser.parse_args(argv)
|