insightfactory-cli 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. insightfactory_cli-1.0.0/.github/workflows/ci.yml +17 -0
  2. insightfactory_cli-1.0.0/.github/workflows/claude.yml +76 -0
  3. insightfactory_cli-1.0.0/.github/workflows/release.yml +43 -0
  4. insightfactory_cli-1.0.0/.gitignore +11 -0
  5. insightfactory_cli-1.0.0/.python-version +1 -0
  6. insightfactory_cli-1.0.0/AGENTS.md +1 -0
  7. insightfactory_cli-1.0.0/CLAUDE.md +28 -0
  8. insightfactory_cli-1.0.0/PKG-INFO +265 -0
  9. insightfactory_cli-1.0.0/README.md +249 -0
  10. insightfactory_cli-1.0.0/pyproject.toml +43 -0
  11. insightfactory_cli-1.0.0/src/if_cli/__init__.py +1 -0
  12. insightfactory_cli-1.0.0/src/if_cli/__main__.py +4 -0
  13. insightfactory_cli-1.0.0/src/if_cli/cache.py +206 -0
  14. insightfactory_cli-1.0.0/src/if_cli/cli.py +163 -0
  15. insightfactory_cli-1.0.0/src/if_cli/colour.py +24 -0
  16. insightfactory_cli-1.0.0/src/if_cli/commands/__init__.py +0 -0
  17. insightfactory_cli-1.0.0/src/if_cli/commands/api.py +177 -0
  18. insightfactory_cli-1.0.0/src/if_cli/commands/config.py +45 -0
  19. insightfactory_cli-1.0.0/src/if_cli/commands/login.py +75 -0
  20. insightfactory_cli-1.0.0/src/if_cli/commands/logout.py +20 -0
  21. insightfactory_cli-1.0.0/src/if_cli/commands/profiles.py +119 -0
  22. insightfactory_cli-1.0.0/src/if_cli/commands/set_token.py +62 -0
  23. insightfactory_cli-1.0.0/src/if_cli/commands/token.py +32 -0
  24. insightfactory_cli-1.0.0/src/if_cli/config.py +188 -0
  25. insightfactory_cli-1.0.0/src/if_cli/constants.py +5 -0
  26. insightfactory_cli-1.0.0/src/if_cli/http.py +153 -0
  27. insightfactory_cli-1.0.0/src/if_cli/main.py +76 -0
  28. insightfactory_cli-1.0.0/src/if_cli/oauth.py +389 -0
  29. insightfactory_cli-1.0.0/src/if_cli/runtime.py +94 -0
  30. insightfactory_cli-1.0.0/tests/__init__.py +0 -0
  31. insightfactory_cli-1.0.0/tests/cache_writer.py +14 -0
  32. insightfactory_cli-1.0.0/tests/conftest.py +11 -0
  33. insightfactory_cli-1.0.0/tests/helpers.py +210 -0
  34. insightfactory_cli-1.0.0/tests/servers.py +99 -0
  35. insightfactory_cli-1.0.0/tests/test_api.py +120 -0
  36. insightfactory_cli-1.0.0/tests/test_api_command.py +324 -0
  37. insightfactory_cli-1.0.0/tests/test_cache.py +146 -0
  38. insightfactory_cli-1.0.0/tests/test_cli.py +444 -0
  39. insightfactory_cli-1.0.0/tests/test_config.py +163 -0
  40. insightfactory_cli-1.0.0/tests/test_config_command.py +115 -0
  41. insightfactory_cli-1.0.0/tests/test_login.py +240 -0
  42. insightfactory_cli-1.0.0/tests/test_oauth.py +74 -0
  43. insightfactory_cli-1.0.0/tests/test_oauth_flow.py +535 -0
  44. insightfactory_cli-1.0.0/tests/test_profiles.py +259 -0
  45. insightfactory_cli-1.0.0/tests/test_programmatic_api.py +43 -0
  46. insightfactory_cli-1.0.0/tests/test_runtime.py +78 -0
  47. insightfactory_cli-1.0.0/tests/test_set_token.py +114 -0
  48. insightfactory_cli-1.0.0/tests/test_token.py +44 -0
  49. insightfactory_cli-1.0.0/uv.lock +212 -0
@@ -0,0 +1,17 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ workflow_dispatch:
6
+
7
+ concurrency:
8
+ group: ci-${{ github.event.pull_request.number || github.ref }}
9
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ ci:
16
+ uses: insightfactory-ai/if_sre_github_actions/.github/workflows/if_s_insightfactory_cli.yml@main
17
+ secrets: inherit
@@ -0,0 +1,76 @@
1
+ # =======================================================
2
+ # GitHub Actions Workflow: Claude Code Assistant
3
+ #
4
+ # Purpose: AI-powered code review and assistance, on demand only.
5
+ # Triggered by mentioning @claude in a PR comment, an issue
6
+ # comment, or an inline PR review comment (e.g. `@claude review`).
7
+ # There is no automatic review on PR open.
8
+ #
9
+ # =======================================================
10
+
11
+ name: Claude Workflow
12
+
13
+ on:
14
+ issue_comment:
15
+ types: [created]
16
+ pull_request_review_comment:
17
+ types: [created]
18
+
19
+ # Least privilege at the workflow level; the reusable workflow requests what it
20
+ # needs for its own jobs.
21
+ permissions:
22
+ contents: read
23
+
24
+ jobs:
25
+ claude:
26
+ # Author gating. The comment body is attacker-controlled and the call below
27
+ # inherits every secret, so restrict the trigger to people with write-ish
28
+ # access rather than anyone who can leave a comment.
29
+ if: |
30
+ contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) &&
31
+ contains(github.event.comment.body, '@claude')
32
+ uses: insightfactory-ai/if_sre_github_actions/.github/workflows/if_s_claude_review.yml@main
33
+ secrets: inherit
34
+ with:
35
+ # CONSTRAINT: the value below is interpolated inside a double-quoted CLI argument with no
36
+ # escaping. Do NOT include ASCII double-quote characters. Use single quotes or backticks
37
+ # if you need to quote something. Newlines are fine.
38
+ append_system_prompt: |
39
+ This repository (insightfactory-cli) is the production Python `if-cli`, published to public
40
+ PyPI. Read CLAUDE.md and README.md before reviewing; the rules below are the ones that get
41
+ violated most often.
42
+
43
+ Runtime and layout:
44
+ - Runtime code is stdlib-only. Flag any new runtime dependency in pyproject.toml or any
45
+ import outside the standard library.
46
+ - Keep one module per CLI command under `src/if_cli/commands/`.
47
+ - Do not suggest third-party HTTP, OAuth, or CLI frameworks; the codebase uses urllib and
48
+ argparse deliberately.
49
+
50
+ Security and auth:
51
+ - A profile's bearer token must never be sent outside that profile's factory origin. Flag
52
+ any code path that could forward a token to another host.
53
+ - OAuth uses authorization-code flow with PKCE and a loopback-only (`127.0.0.1`) callback.
54
+ The CLI is a public client with no client secret.
55
+ - Do not commit credentials, access tokens, refresh tokens, or client secrets in code,
56
+ tests, or fixtures. Fixtures must use neutral hostnames such as `factory.example`.
57
+
58
+ On-disk compatibility:
59
+ - Profiles and tokens must be written only through the atomic private-file helpers
60
+ (`write_private_file` in config.py): `0o600` files, `0o700` directories, atomic replace
61
+ via temp file.
62
+ - On-disk profile and token-cache paths, JSON shapes, and file permissions must stay
63
+ byte-compatible with the Node `@insightfactory-ai/if-cli` so users can switch
64
+ implementations without re-authenticating.
65
+
66
+ Programmatic API (breaking-change surface):
67
+ - These four symbols are a supported in-process surface documented in README.md:
68
+ `if_cli.config.load_config`, `if_cli.config.get_profile`, `if_cli.oauth.get_valid_token`,
69
+ `if_cli.runtime.CliError`.
70
+ - They must raise `CliError` on failure (never `SystemExit`), never write to stdout, never
71
+ launch a browser or block on interactive input, and use only bounded waits.
72
+ - Changing signatures, behaviour, or removing any of them requires a major version bump.
73
+
74
+ Tests:
75
+ - `tests/` uses isolated config dirs via `INSIGHTFACTORY_CONFIG_DIR`; never smoke-test
76
+ against `~/.insightfactory`.
@@ -0,0 +1,43 @@
1
+ name: release
2
+
3
+ # CD: merge to develop/main (or a v* tag) publishes to public PyPI.
4
+ # Verify + build live in insightfactory-ai/if_sre_github_actions
5
+ # (if_s_insightfactory_cli.yml). The publish job stays here because PyPI
6
+ # Trusted Publishing is registered for this repository, workflow `release.yml`,
7
+ # and environment `pypi` — a reusable workflow cannot be registered.
8
+ on:
9
+ push:
10
+ branches: [develop, main]
11
+ tags:
12
+ - "v*"
13
+
14
+ concurrency:
15
+ group: release-${{ github.ref }}
16
+ cancel-in-progress: false
17
+
18
+ permissions:
19
+ contents: read
20
+
21
+ jobs:
22
+ ci:
23
+ uses: insightfactory-ai/if_sre_github_actions/.github/workflows/if_s_insightfactory_cli.yml@main
24
+ secrets: inherit
25
+
26
+ publish:
27
+ name: Publish to PyPI
28
+ needs: ci
29
+ if: needs.ci.outputs.should_publish == 'true'
30
+ runs-on: ${{ vars.RUN_LINUX }}
31
+ timeout-minutes: 10
32
+ environment:
33
+ name: pypi
34
+ url: https://pypi.org/p/insightfactory-cli
35
+ permissions:
36
+ id-token: write
37
+ steps:
38
+ - uses: actions/download-artifact@v6
39
+ with:
40
+ name: dist-release
41
+ path: dist/
42
+
43
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,11 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ .tycache/
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ .DS_Store
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1 @@
1
+ CLAUDE.md
@@ -0,0 +1,28 @@
1
+ # InsightFactory CLI contributor guide
2
+
3
+ This repository publishes `insightfactory-cli` to public PyPI, which installs
4
+ the `if-cli` executable. It uses Python 3.10+, uv, pytest, ruff, and ty.
5
+
6
+ ## Commands
7
+
8
+ ```bash
9
+ uv sync
10
+ uv run pytest
11
+ uv run ruff check .
12
+ uv run ty check
13
+ ```
14
+
15
+ ## Conventions
16
+
17
+ - Keep one module per CLI command under `src/if_cli/commands/`.
18
+ - Never send a profile bearer token outside that profile's factory origin.
19
+ - OAuth uses authorization-code flow with PKCE and a loopback-only callback.
20
+ - Store profiles and tokens through the atomic private-file helpers.
21
+ - On-disk profile/token-store paths and JSON shapes stay compatible with the
22
+ Node CLI (`~/.insightfactory`, `INSIGHTFACTORY_CONFIG_DIR` override).
23
+ - Do not commit credentials, access tokens, refresh tokens, or client secrets.
24
+ - Test fixtures use only neutral hostnames (`factory.example`).
25
+ - Do not publish to PyPI or create release tags from a developer machine;
26
+ merges to `develop` / `main` (and optional `v*` tags) publish via the
27
+ reusable workflow in `if_sre_github_actions` plus the OIDC job in
28
+ `.github/workflows/release.yml`.
@@ -0,0 +1,265 @@
1
+ Metadata-Version: 2.5
2
+ Name: insightfactory-cli
3
+ Version: 1.0.0
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
+ # Discover API routes from the factory's OpenAPI document.
82
+ if-cli api routes -p example-dev
83
+ if-cli api routes -p example-dev agent-projects
84
+ if-cli api describe -p example-dev GET /api/agent-projects/{id}
85
+
86
+ # Remove the cached credential for a profile.
87
+ if-cli logout -p example-dev
88
+
89
+ # Fallback for factories where browser OAuth is not available.
90
+ if-cli set-token -p example-dev
91
+
92
+ if-cli --version
93
+ ```
94
+
95
+ Profile selection uses `-p`, then `INSIGHTFACTORY_CONFIG_PROFILE`, then
96
+ `[DEFAULT]`. Route discovery reads `{host}/swagger/v1/swagger.json` and does
97
+ not require a login. Authenticated API requests are restricted to the selected
98
+ factory origin so a profile token cannot be forwarded to another host. Profile
99
+ hosts must be bare origins rather than URLs containing application path
100
+ prefixes.
101
+
102
+ Override the config directory with `INSIGHTFACTORY_CONFIG_DIR` (useful in tests
103
+ and CI). The default is `~/.insightfactory`.
104
+
105
+ On Git Bash (MSYS2) on Windows, path conversion rewrites a leading-slash
106
+ argument such as `/api/agent-projects` into a Windows path. Prefix the command
107
+ with `MSYS_NO_PATHCONV=1`:
108
+
109
+ ```bash
110
+ MSYS_NO_PATHCONV=1 if-cli api -p example-dev /api/agent-projects
111
+ ```
112
+
113
+ ### Scripting against profiles
114
+
115
+ `profiles --json` and `config get` are the supported machine-readable
116
+ interfaces; the padded `profiles` columns are for humans and are not a
117
+ contract, and neither is the layout of `~/.insightfactory/config`.
118
+
119
+ `if-cli profiles --json` prints an array — one entry per configured profile, in
120
+ file order — of `name`, `host`, and `token`:
121
+
122
+ ```json
123
+ [
124
+ {
125
+ "name": "example-dev",
126
+ "host": "https://factory.example",
127
+ "token": { "status": "valid", "expires_at": 1785918336, "refreshable": true }
128
+ },
129
+ { "name": "other", "host": "https://other.factory.example", "token": { "status": "none" } }
130
+ ]
131
+ ```
132
+
133
+ `token.status` is `valid`, `expired`, `none`, or `unknown`; `expires_at` and
134
+ `refreshable` are present only when a cached token exists. `valid` means
135
+ `if-cli token` will hand back the cached token as-is, so a token inside the
136
+ 60-second slack window that command refreshes within is reported as `expired` —
137
+ use the raw `expires_at` if you need the literal expiry instant instead. A
138
+ profile that cannot be resolved has a `host` of `null` (or its raw configured
139
+ value) and a top-level `error` describing the problem, and does not stop the
140
+ other profiles from being listed; `token.error` is set only when the token
141
+ cache itself could not be read.
142
+
143
+ `if-cli config get <key> -p <profile>` prints one newline-terminated value and
144
+ exits non-zero if the profile cannot be resolved, or the key is not configured
145
+ within it. A profile with no host cannot be resolved at all, so every key on it
146
+ fails, not just `host`. A key present but empty (`client_id =`) counts as not
147
+ configured:
148
+
149
+ ```bash
150
+ curl -H "Authorization: Bearer $(if-cli token -p example-dev)" \
151
+ "$(if-cli config get host -p example-dev)/api/agent-projects"
152
+ ```
153
+
154
+ The readable keys are `host`, `audience`, `callback_port`, `client_id`, and
155
+ `organization` — all non-secret, and resolved the same way the CLI itself
156
+ resolves them, so `audience` and `callback_port` return their defaults when the
157
+ profile omits them. Tokens are deliberately not readable this way; use
158
+ `if-cli token`.
159
+
160
+ ## Programmatic API
161
+
162
+ `insightfactory-cli` may be imported in-process as a library to resolve a
163
+ profile to a factory host and a fresh access token. These four symbols are a
164
+ supported programmatic surface for in-process consumers:
165
+
166
+ - `if_cli.config.load_config`
167
+ - `if_cli.config.get_profile`
168
+ - `if_cli.oauth.get_valid_token`
169
+ - `if_cli.runtime.CliError`
170
+
171
+ They raise `CliError` on failure (never `SystemExit` or `sys.exit`), never
172
+ write to stdout, never launch a browser or block on interactive input, and use
173
+ only bounded waits. Changing or removing any of them is a breaking change and
174
+ requires a major version bump.
175
+
176
+ ## How authentication works
177
+
178
+ - Profiles are stored in `~/.insightfactory/config`, one per customer/environment.
179
+ - The profile file is managed by the CLI and is regenerated on updates; comments
180
+ and empty sections are not preserved.
181
+ - Tokens are cached by host in `~/.insightfactory/token-cache.json` with file
182
+ mode `0600`.
183
+ - The configuration directory is `0700`; configuration and cache updates are
184
+ atomic.
185
+ - Remote factory, authorization, and token endpoints must use HTTPS. HTTP is
186
+ accepted only for loopback development.
187
+ - Login discovers OAuth metadata from
188
+ `{host}/.well-known/oauth-authorization-server`.
189
+ - Authorization uses PKCE through the factory `/authorize` proxy with a
190
+ loopback-only (`127.0.0.1`) callback. The CLI is a public OAuth client: the
191
+ client ID is read from the factory's discovery document at runtime (no client
192
+ secret).
193
+
194
+ ## Development
195
+
196
+ ```bash
197
+ uv sync
198
+ uv run pytest
199
+ uv run ruff check .
200
+ uv run ty check
201
+ ```
202
+
203
+ Smoke-test against a throwaway config directory, never `~/.insightfactory`:
204
+
205
+ ```bash
206
+ uv run if-cli --help
207
+ uv run if-cli --version
208
+ INSIGHTFACTORY_CONFIG_DIR=$(mktemp -d) uv run if-cli profiles
209
+ ```
210
+
211
+ Python 3.10 or newer is required. Runtime code is stdlib-only.
212
+
213
+ ## Release
214
+
215
+ Publishing is automatic from GitHub Actions on merge. Verify and build live in
216
+ the reusable workflow
217
+ [`if_s_insightfactory_cli.yml`](https://github.com/insightfactory-ai/if_sre_github_actions/blob/main/.github/workflows/if_s_insightfactory_cli.yml)
218
+ in `insightfactory-ai/if_sre_github_actions`
219
+ ([PR #300](https://github.com/insightfactory-ai/if_sre_github_actions/pull/300)
220
+ must merge before these stubs go green). This repository's
221
+ `.github/workflows/release.yml` keeps only the OIDC publish job, because PyPI
222
+ Trusted Publishing is registered for **this** repo, workflow `release.yml`, and
223
+ environment `pypi` — a reusable workflow cannot be registered.
224
+
225
+ A one-time trusted-publisher registration is required on the PyPI project for
226
+ that triple. The publish job downloads the `dist-release` artifact staged by
227
+ the reusable workflow and uploads it with `pypa/gh-action-pypi-publish`.
228
+
229
+ | Trigger | Published version | PyPI role |
230
+ |---|---|---|
231
+ | Push / merge to `develop` | `1.0.0.dev{N}` | pre-release (`pip install --pre`) |
232
+ | Push / merge to `main` | `1.0.0` (the version in `pyproject.toml`) | latest |
233
+ | Push of a `v*` tag | same as `main` | retry / explicit cut of that version |
234
+
235
+ `N` is `github.run_number` of the reusable workflow: a per-workflow integer
236
+ that goes up on every run and stays the same across re-runs of that run (so a
237
+ failed publish can be retried under the same version). It does not depend on
238
+ git history or clone depth. After a successful `main` publish, bump `version`
239
+ in `pyproject.toml` on `develop` so later `.devN` builds sort *after* what just
240
+ landed; PEP 440 puts `1.0.0.devN` before `1.0.0`. Because of that ordering, a
241
+ dependency spec of `insightfactory-cli>=1.0.0` does not match develop-channel
242
+ `.devN` builds; an in-process consumer that wants them must specify a
243
+ pre-release-bearing spec such as `insightfactory-cli>=1.0.0.dev0` (which also
244
+ matches the final `1.0.0` release).
245
+
246
+ A version that is already on PyPI is skipped rather than failing, so a second
247
+ push to `main` (or a `v*` tag for the same version) is a no-op. A `v*` tag
248
+ whose name does not match `pyproject.toml` fails before publishing.
249
+
250
+ ## Differences from the Node CLI
251
+
252
+ Behaviour matches `@insightfactory-ai/if-cli` except where the platforms
253
+ genuinely diverge:
254
+
255
+ - **Unknown-option wording** is aligned (`Unknown option '--flag'`). Other
256
+ argparse messages (for example a missing option value) still differ from
257
+ Node's `util.parseArgs`.
258
+ - **Expiry timestamps** in human-readable output use the C-locale `%c` format
259
+ rather than JavaScript `Date#toLocaleString()`.
260
+ - **`--version`** reads the installed package metadata (`1.0.0` and later)
261
+ rather than an npm `package.json`.
262
+
263
+ On-disk profile and token-cache paths, JSON shapes, and file permissions stay
264
+ compatible with the Node CLI, so you can switch implementations without
265
+ re-authenticating.