@kolatts/pncli 5.3.0 → 6.0.0

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.
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ envOverriddenSecretPaths,
3
4
  getGlobalConfigPath,
4
5
  loadConfig,
5
6
  loadJsonFile,
@@ -9,9 +10,10 @@ import {
9
10
  setRepoConfigValue,
10
11
  writeGlobalConfig,
11
12
  writeRepoConfig
12
- } from "./chunk-QHJ2HM5J.js";
13
+ } from "./chunk-WDWAPTVC.js";
13
14
  import "./chunk-HRNOAQDN.js";
14
15
  export {
16
+ envOverriddenSecretPaths,
15
17
  getGlobalConfigPath,
16
18
  loadConfig,
17
19
  loadJsonFile,
@@ -22,4 +24,4 @@ export {
22
24
  writeGlobalConfig,
23
25
  writeRepoConfig
24
26
  };
25
- //# sourceMappingURL=config-Y6IE6GY7.js.map
27
+ //# sourceMappingURL=config-5LC7ZTEP.js.map
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  HttpClient,
4
4
  createHttpClient
5
- } from "./chunk-2J2HAFKO.js";
5
+ } from "./chunk-4FR4WGNR.js";
6
6
  import "./chunk-BOFSNQQ2.js";
7
7
  import "./chunk-HRNOAQDN.js";
8
8
  export {
9
9
  HttpClient,
10
10
  createHttpClient
11
11
  };
12
- //# sourceMappingURL=http-V7PZD7LE.js.map
12
+ //# sourceMappingURL=http-DJSGEGD6.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolatts/pncli",
3
- "version": "5.3.0",
3
+ "version": "6.0.0",
4
4
  "description": "The Paperwork Nightmare CLI — structured JSON access to Jira, Bitbucket, GitHub, Confluence, Azure DevOps, SonarQube, Jenkins, and more, built for AI coding agents. Connectivity without MCP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,7 @@ metadata:
9
9
  services: config
10
10
  ---
11
11
 
12
- pncli gives AI agents and humans unified CLI access to enterprise tools: Jira, Bitbucket, GitHub, Confluence, SonarQube, SDElements, Azure DevOps, Jenkins, Artifactory, Checkmarx, Contrast Security IAST, Sonatype IQ Server, OpenShift / Kubernetes, Dynatrace, LogScale, Split.IO, Figma, and Alation.
12
+ pncli gives AI agents and humans unified CLI access to enterprise tools: Jira, Bitbucket, GitHub, Confluence, SonarQube, SDElements, Azure DevOps, Jenkins, Artifactory, Checkmarx, Contrast Security IAST, Sonatype IQ Server, OpenShift / Kubernetes, Dynatrace, LogScale, Split.IO, Figma, Alation, and Sauce Labs.
13
13
 
14
14
  Every service authenticates with a long-lived credential you generate once in that tool's own UI and put in an env var or the config file — for almost all of them a personal access token that goes straight into a header. Alation is the exception: you configure its refresh token, and pncli exchanges it for short-lived API tokens on every run without any interaction. If a tool you need is missing from the table below, it is not out of scope by default — pncli covers enterprise tooling broadly, and the only hard requirement is a credential you can generate once with no browser or interactive step at use time.
15
15
 
@@ -74,9 +74,22 @@ network stay direct while SaaS ones route out through the proxy. If a proxy
74
74
  variable is set but the proxy cannot be configured, pncli warns on stderr rather
75
75
  than silently bypassing it.
76
76
 
77
- TLS verification is **off** by default, because most self-hosted enterprise
78
- installs sit behind SSL-inspecting proxies that break the certificate chain.
79
- Set `PNCLI_VERIFY_TLS=1` to turn it back on.
77
+ TLS certificate verification is always **on**, and pncli trusts the OS
78
+ certificate store alongside Node's bundled CAs. If your IT department has
79
+ installed the SSL-inspecting proxy's root certificate on your machine (the usual
80
+ case: it is what makes your browser work), pncli trusts it too, with nothing to
81
+ set. If a certificate still fails, pncli's error says why and how to fix it:
82
+
83
+ - **Untrusted certificate** — install the proxy's or internal CA's root in the
84
+ OS store, or point `NODE_EXTRA_CA_CERTS=/path/to/ca.pem` at it. For a
85
+ self-signed server, export its certificate from the browser and point
86
+ `NODE_EXTRA_CA_CERTS` at that file.
87
+ - **Hostname mismatch** — use the host name the certificate was issued for in
88
+ `baseUrl`, usually the fully qualified name rather than a short alias or IP.
89
+
90
+ Set `NODE_USE_SYSTEM_CA=0` to trust only Node's bundled CAs. (Earlier versions
91
+ disabled verification by default and used `PNCLI_VERIFY_TLS=1` to opt back in;
92
+ that variable is no longer read.)
80
93
 
81
94
  ## Large text fields (descriptions, acceptance criteria)
82
95
 
@@ -106,8 +119,10 @@ Each service has its own file in this skill with the config keys and example val
106
119
  | LogScale | `logscale.md` | Log queries, repository listing |
107
120
  | Split.IO | `splitio.md` | Feature flag discovery, targeting updates, Change Requests |
108
121
  | Figma | `figma.md` | Design files, comments, version history |
122
+ | Sauce Labs | `saucelabs.md` | Test jobs and builds, real-device inventory and availability, device sessions, Sauce Connect tunnels |
109
123
  | Alation | `alation.md` | Data catalog metadata (data sources, schemas, tables, columns), search, Document Hubs |
110
124
  | Skills Marketplace | `marketplace.md` | Org plugins and shipped AGENTS.md / CLAUDE.md from git marketplaces |
125
+ | Skills guide | `skills-guide.md` | How skills management fits together: sources, agent hosts, scopes, sync, private-repo auth, OS keychain |
111
126
 
112
127
  ## Installing skills
113
128
 
@@ -115,7 +130,7 @@ The skills bundled with pncli install into a repo with `pncli skills install` (d
115
130
 
116
131
  Installed skills are a copy — after upgrading pncli, re-run `pncli skills install` to refresh them. `skills list` and `skills status` warn when the installed copy came from a different pncli version.
117
132
 
118
- Org-internal plugins come from a git-hosted marketplace: `pncli skills marketplace add <git-clone-url> --all-agents` registers one and installs every plugin into all three agent hosts, and `pncli skills marketplace sync --marketplace all --all-agents` keeps them current. A marketplace can also ship an `instructions/AGENTS.md` and `instructions/CLAUDE.md`; `add` and `sync` merge those into each agent's user-level instructions file (`~/.codex/AGENTS.md`, `~/.copilot/copilot-instructions.md`, `~/.claude/CLAUDE.md`) as a marked block that leaves your own content untouched — `pncli skills marketplace instructions list|install|remove` manages them. `pncli skills status`, `pncli skills locations`, and `pncli doctor` show what is installed and where. The full workflow is in the `marketplace.md` file that ships inside the installed skill, and `pncli skills marketplace --help` summarises it.
133
+ Org-internal plugins come from a git-hosted marketplace: `pncli skills marketplace add <git-clone-url> --all-agents` registers one and installs every plugin into all three agent hosts, and `pncli skills marketplace sync --marketplace all --all-agents` keeps them current. A marketplace can also ship an `instructions/AGENTS.md` and `instructions/CLAUDE.md`; `add` and `sync` merge those into each agent's user-level instructions file (`~/.codex/AGENTS.md`, `~/.copilot/copilot-instructions.md`, `~/.claude/CLAUDE.md`) as a marked block that leaves your own content untouched — `pncli skills marketplace instructions list|install|remove` manages them. `pncli skills status`, `pncli skills locations`, and `pncli doctor` show what is installed and where. The full workflow is in the `marketplace.md` file that ships inside the installed skill, and `pncli skills marketplace --help` summarises it. For the concepts behind all of this — where skills come from, how they stay current, and how private marketplaces authenticate — run `pncli skills guide` (or read `skills-guide.md`).
119
134
 
120
135
  ## Setup walkthrough
121
136
 
@@ -159,10 +174,14 @@ Review results. If any service shows `ok: false`, help troubleshoot the URL or c
159
174
  pncli config show
160
175
  ```
161
176
 
177
+ **Keeping tokens out of plaintext** — `pncli config keychain migrate` moves every secret in `~/.pncli/config.json` into the OS keychain (macOS Keychain, Windows Credential Manager, Secret Service) and leaves `keychain:` references in their place; `--to config` moves them back. `PNCLI_*` environment variables still take precedence.
178
+
162
179
  **Troubleshooting** — when any command fails unexpectedly, run:
163
180
 
164
181
  ```
165
182
  pncli doctor
166
183
  ```
167
184
 
168
- It reports config-file health, credential validity per service, and skill install state (including stale skills) in one JSON envelope, with a `problems` array listing suggested fixes. Add `--offline` to skip the network checks.
185
+ It reports config-file health, credential validity per service, keychain references that do not resolve, git authentication for marketplace hosts (GitHub token scope, expiry, and SSO authorization), and skill install state (including stale skills) in one JSON envelope, with a `problems` array listing suggested fixes. Add `--offline` to skip the network checks.
186
+
187
+ When a clone fails with "repository not found" or an auth error, `pncli git credentials inspect` shows which credential git actually uses for each remote (after `insteadOf` rewriting, or from Git Credential Manager / the `gh` CLI / the OS keychain), and whether it can read the repo; `--scan <dir>` checks every clone under a folder, and `pncli git credentials stored` lists what the credential stores hold. See `github.md`.
@@ -51,3 +51,7 @@ pncli ado work schema --example-only > wi.json
51
51
  pncli ado work create --type Bug --input-file wi.json
52
52
  pncli ado work update --id 123 --input-file wi.json --field Priority=1 # flag wins, and it's reported
53
53
  ```
54
+
55
+ ## Skills marketplaces on this host
56
+
57
+ A skills marketplace repository on this host (see `marketplace.md`) with no `--token` of its own uses `ado.pat` (`PNCLI_ADO_PAT`) for clone and pull, and for `pncli skills git-auth`. The PAT needs the Code (Read) scope.
@@ -38,3 +38,7 @@ pncli bitbucket --project MYPROJ create-repo --name my-new-repo
38
38
  # With a description and explicit project flag
39
39
  pncli bitbucket create-repo --project MYPROJ --name my-new-repo --description "My project"
40
40
  ```
41
+
42
+ ## Skills marketplaces on this host
43
+
44
+ A skills marketplace repository on this host (see `marketplace.md`) with no `--token` of its own uses `bitbucket.pat` (`PNCLI_BITBUCKET_PAT`) for clone and pull, and for `pncli skills git-auth`. Bitbucket Data Center personal access tokens are usually sent with your Bitbucket username: add the marketplace with `--username <you>`, or set it later with `pncli skills marketplace update <name> --username <you>`.
@@ -65,3 +65,40 @@ pncli github resolve-thread --thread-id PRT_kwDOHfWCIM4APCA
65
65
  `list-review-threads` returns the first 100 threads with their resolution status,
66
66
  file path/line, and first 10 comments. If a PR has more than 100 threads, a warning
67
67
  is written to stderr.
68
+
69
+ ## Which token does a clone use? (`pncli git credentials`)
70
+
71
+ Classic-PAT setups usually put the token in a gitconfig rewrite such as
72
+ `url."https://<classic-PAT>@github.com/acme/".insteadOf "https://github.com/acme/"`, while Git Credential
73
+ Manager, the `gh` CLI, or the Windows Credential Manager may hold other tokens for the same host. A repo outside
74
+ every mapping silently clones anonymously and fails as "repository not found"; a mapped token may be valid but
75
+ unable to see a particular repo; a stored token may be shadowed by another helper. These commands work on any
76
+ clone, not just skills marketplaces, and never print a token — only its type and a fingerprint (`ghp_…a1b2`):
77
+
78
+ ```
79
+ pncli git credentials inspect # every remote of the current clone
80
+ pncli git credentials inspect ~/src/tools --remote upstream
81
+ pncli git credentials inspect https://github.com/acme/tools.git
82
+ pncli git credentials inspect --scan ~/src --problems-only # clones that are unmapped or lack access
83
+ pncli git credentials mappings # every insteadOf rewrite and URL-scoped helper, tokens checked
84
+ pncli git credentials stored # what the credential stores hold, and what git actually sends
85
+ pncli git credentials forget --host github.com --username octo # drop a stale stored credential
86
+ ```
87
+
88
+ - **`inspect`** — per remote: the URL as configured, the URL git uses after `insteadOf` rewriting, the mapping
89
+ that applied, and where the credential comes from (`insteadOf`, the `url` itself, a `helper`, or `none`). For a
90
+ helper it names the kind — Git Credential Manager, wincred, osxkeychain, libsecret, store, `gh`, pncli — and
91
+ GCM's backing store (`wincredman`, `dpapi`, `keychain`, `secretservice`, …). Online it runs an authenticated
92
+ `git ls-remote`, and for GitHub reports the account, a classic token missing `repo`, SAML SSO authorization,
93
+ expiry, and whether the token can see **this** repository — each problem with a concrete fix, such as the exact
94
+ `insteadOf` line to add for an unmapped org.
95
+ - **`stored`** — the Windows Credential Manager's `git:` entries (Git Credential Manager and wincred), the plaintext
96
+ `~/.git-credentials` file, and the macOS Keychain's git entries (metadata only), plus what git actually sends per
97
+ host and per stored path. Flags stored tokens that GitHub rejects or that expire soon, several accounts for one
98
+ host, plaintext storage, and **shadowed** entries — a stored credential git never sends because another helper
99
+ (typically `gh auth setup-git`) answers first.
100
+ - **`forget`** — `git credential reject` for one host (and optionally account or path), which every helper
101
+ implements; it reports the fingerprint before and after. A shadowed Windows entry cannot be reached this way —
102
+ `stored` gives the exact `cmdkey /delete:` command instead.
103
+
104
+ `--offline` skips every network check.
@@ -1,6 +1,6 @@
1
1
  # Jira
2
2
 
3
- Enables: `pncli jira get-issue`, `create-issue`, `update-issue`, `search`, `list-boards`, `list-sprints`, `set-sprint`, and more — get, create, and update issues, transitions, comments, attachments, custom fields, and sprints.
3
+ Enables: `pncli jira get-issue`, `create-issue`, `update-issue`, `search`, `list-boards`, `list-sprints`, `set-sprint`, `log-work`, and more — get, create, and update issues, transitions, comments, attachments, custom fields, sprints, and worklogs.
4
4
 
5
5
  ## Required config
6
6
 
@@ -42,6 +42,17 @@ pncli jira set-sprint --key ACME-123 --sprint <sprint-id>
42
42
 
43
43
  `list-sprints` output includes `startDate`/`endDate`/`state`/`goal` for each sprint.
44
44
 
45
+ ## Worklogs
46
+
47
+ ```
48
+ pncli jira log-work --key ACME-123 --time-spent "2h 30m" --comment "Investigated the failing build"
49
+ pncli jira log-work --key ACME-123 --time-spent "1d" --started 2024-01-15T09:00:00.000+0000
50
+ ```
51
+
52
+ `--time-spent` uses Jira's own duration format (`1d`, `2h 30m`, `45m`, ...). `--started` takes
53
+ Jira's datetime format (`yyyy-MM-dd'T'HH:mm:ss.SSSZ`, e.g. `2024-01-15T09:00:00.000+0000`) and
54
+ defaults to now when omitted.
55
+
45
56
  ## Custom fields
46
57
 
47
58
  Register a custom field once so `--field <Name>=value` and `--input-file` can address it by
@@ -60,6 +71,36 @@ mangle nested double quotes inside a single-quoted argument, which silently stor
60
71
  value. `pncli jira fields` prints what's currently registered; `pncli jira fields --discover`
61
72
  fetches field metadata straight from the Jira API instead.
62
73
 
74
+ Registration is only needed to address a field by **friendly name**, or to get automatic
75
+ value shaping from `type`. Both `--field` and `--fields-file` accept an unregistered raw
76
+ Jira field id or name directly (e.g. `--field fixVersions=@versions.json`,
77
+ `{"fixVersions": [...]}` in a `--fields-file` JSON file) — nothing needs to be pre-registered
78
+ just to use a standard field like `fixVersions` or a custom field you already know the
79
+ `customfield_NNNNN` id for. Registration only fails for a key that still has whitespace in
80
+ it and isn't a registered friendly name — that's almost always a typo.
81
+
82
+ Some select-type fields (a "Crew" picker, for example) reject the display text you see in
83
+ Jira's UI and only accept the option's numeric key. Run `pncli jira fields --discover
84
+ --project <key>` to see each field's `allowedValues`; if a field's values are `{id, value}`
85
+ pairs rather than plain strings, register it with `"type":"option-id"` and pass the numeric
86
+ `id`, not the display text. **Sprint** is not a custom field at all — don't try to set it
87
+ via `--field` or `--fields-file`; use `pncli jira set-sprint --key <key> --sprint <id>` after
88
+ the issue exists, with the id from `pncli jira list-sprints`.
89
+
90
+ ### PowerShell-safe JSON values
91
+
92
+ Passing JSON inline in `--field Name={"a":1}` is unreliable in PowerShell — its argument
93
+ parser mangles embedded quotes before pncli ever sees them. Use the `@file` form instead,
94
+ which sidesteps shell quoting entirely:
95
+
96
+ ```powershell
97
+ '{"steps":[{"action":"click"}]}' | Out-File -Encoding utf8 steps.json
98
+ pncli jira create-issue --project ACME --summary "..." --field "Test Steps=@steps.json"
99
+ ```
100
+
101
+ This also applies to `--jql` and any other flag that would otherwise need inline JSON or
102
+ nested quotes on Windows.
103
+
63
104
  ## Large fields via --input-file
64
105
 
65
106
  `create-issue` and `update-issue` accept `--input-file <path>` (`-` for stdin) instead of, or alongside, individual flags — useful for a long description or many custom fields at once. Run `pncli jira schema` to print the JSON Schema plus a runnable example. Any string value in `fields` may be `@path/to/file` to pull that field's content from a file (e.g. a big HTML description) instead of inlining it. Custom fields resolve by friendly name (if registered — see **Custom fields** above) or by raw id (`customfield_10032`) with no registration required. Individual flags (`--summary`, `--description`, `--field`, ...) override matching keys from the file; overridden keys are printed to stderr and included in the output's `meta.overrides`.
@@ -79,3 +120,6 @@ pncli jira create-issue --input-file issue.json --priority Low # --priority wi
79
120
  - `--assignee` on `create-issue`, `update-issue`, and `assign` takes a **username**, as does
80
121
  any `user`-typed custom field passed via `--field`
81
122
  - Custom fields discovered with `pncli jira fields --discover`
123
+ - `create-issue` does not check for duplicates before submitting. If a request times out or
124
+ the response is otherwise ambiguous, run `pncli jira search` for the exact summary before
125
+ retrying — a timeout does not tell you whether the issue was actually created.
@@ -27,7 +27,59 @@ pncli skills marketplace add https://bitbucket.imagile.dev/scm/ai/skills.git --n
27
27
 
28
28
  You can register as many marketplaces as you like — just run `add` again with a different URL. `marketplace setup` is kept as an alias of `add` for backward compatibility.
29
29
 
30
- For a private repo, pass `--token <token>` (a Bitbucket or GitHub access token) — it's stored with that marketplace's entry and injected into the clone/pull URL. For a marketplace hosted on `github.com` (or the host configured as `github.baseUrl`), you can omit `--token` if you already have a working GitHub credential configured (`PNCLI_GITHUB_TOKEN`, `GITHUB_TOKEN`, or `pncli config set github.token`) — `marketplace add` and `marketplace sync` fall back to it automatically. An explicit `--token` on the marketplace always takes priority over that fallback. To rotate a marketplace's own token, re-run `marketplace add <url> --token <new-token>`; a rejected or expired token surfaces as a clear error naming the marketplace rather than raw git output.
30
+ For a private repo, pass `--token <token>` (a Bitbucket or GitHub access token) — it's stored with that marketplace's entry and supplied to git for each clone and pull; it is not left in the clone's `.git/config`. For a marketplace hosted on `github.com` (or the host configured as `github.baseUrl`), you can omit `--token` if you already have a working GitHub credential configured (`PNCLI_GITHUB_TOKEN`, `GITHUB_TOKEN`, or `pncli config set github.token`) — `marketplace add` and `marketplace sync` fall back to it automatically. An explicit `--token` on the marketplace always takes priority over that fallback. To rotate a marketplace's own token, re-run `marketplace add <url> --token <new-token>` (repeat `--keychain` if the old one was stored there, or use `pncli config keychain set marketplaces.<name>.token`); a rejected or expired token surfaces as a clear error naming the marketplace rather than raw git output.
31
+
32
+ Add `--keychain` to store the token in the OS keychain (macOS Keychain, Windows Credential Manager, Secret Service) instead of plaintext config; the marketplace entry then holds a `keychain:marketplaces.<name>.token` reference.
33
+
34
+ ### Providers: GitHub, Bitbucket, Azure DevOps
35
+
36
+ A marketplace can live on any HTTPS git host. pncli detects the provider from your configured service hosts and the URL shape (`/_git/` is Azure DevOps, `/scm/` is Bitbucket Data Center), and uses it to pick a fallback token when the marketplace has none of its own:
37
+
38
+ | Provider | Example clone URL | Fallback token (when no `--token`) |
39
+ |---|---|---|
40
+ | GitHub / GitHub Enterprise | `https://ghe.imagile.dev/ai/skills.git` | `PNCLI_GITHUB_TOKEN` / `GITHUB_TOKEN` / `github.token` (github.com or the host of `github.baseUrl`) |
41
+ | Bitbucket Data Center | `https://bitbucket.imagile.dev/scm/ai/skills.git` | `PNCLI_BITBUCKET_PAT` / `bitbucket.pat` (the host of `bitbucket.baseUrl`) |
42
+ | Azure DevOps Server | `https://ado.imagile.dev/DefaultCollection/Platform/_git/skills` | `PNCLI_ADO_PAT` / `SYSTEM_ACCESSTOKEN` / `ado.pat` (the host of `ado.baseUrl`) |
43
+ | anything else | `https://git.imagile.dev/ai/skills.git` | none; pass `--token` |
44
+
45
+ Each marketplace keeps its own `--token`, so marketplaces on the same host (or on different providers) can use different secrets. Pass `--provider github|bitbucket|ado|git` if detection guesses wrong.
46
+
47
+ The token is sent with a username: `x-access-token` on github.com, `x-token-auth` everywhere else (unchanged from earlier versions; Azure DevOps accepts any username with a PAT). **Bitbucket Data Center personal access tokens are usually sent with your Bitbucket username** — set it per marketplace:
48
+
49
+ ```
50
+ pncli skills marketplace add https://bitbucket.imagile.dev/scm/ai/skills.git --token <token> --username jdoe --all-agents
51
+ pncli skills marketplace update internal-ai --username jdoe # for one you already added
52
+ ```
53
+
54
+ ### Rotate a token or change credentials (`update`)
55
+
56
+ ```
57
+ pncli skills marketplace update internal-ai --token <new-token> [--keychain]
58
+ pncli skills marketplace update internal-ai --username jdoe
59
+ pncli skills marketplace update internal-ai --clear-token # use the provider fallback instead
60
+ pncli skills marketplace update internal-ai --provider bitbucket
61
+ ```
62
+
63
+ `update` changes the stored entry only — no re-clone, no reinstall. A keychain entry the marketplace no longer points at is deleted.
64
+
65
+ ### Let git itself authenticate (`git-auth`)
66
+
67
+ pncli's own clone and pull are covered above, but an agent host that clones a marketplace itself (e.g. Claude Code's `/plugin marketplace add`) runs plain `git`, which has no credential for it. Give it one:
68
+
69
+ ```
70
+ pncli skills git-auth enable # every marketplace that has a credential
71
+ pncli skills git-auth enable --marketplace internal-ai --mode keychain
72
+ pncli skills git-auth status # per marketplace: mode, username, token source, match
73
+ pncli skills git-auth disable [--marketplace internal-ai] [--forget-keychain]
74
+ ```
75
+
76
+ - Entries are **scoped to each marketplace's repository URL** (with and without `.git`), for example `credential.https://bitbucket.imagile.dev/scm/ai/skills.git.helper`. git consults them only for that repository, so every other repo on the same host keeps using your own login, untouched.
77
+ - `--mode helper` (default) writes an empty reset entry, `!pncli skills git-credential --marketplace '<name>'`, then whatever helpers git used for that repo before (the host's `gh auth setup-git` entry, Git Credential Manager, osxkeychain). pncli answers with that marketplace's own credential — the same username and token its clone and pull use — resolved from env → config → keychain, so nothing is copied into gitconfig and rotation needs no re-run. If pncli cannot answer, git falls through to your helpers as before.
78
+ - `--mode keychain` stores the credential in git's own credential store via `git credential approve`, keyed to the repository (a repo-scoped `useHttpPath`). It works where pncli is not on git's `PATH`, but must be re-run after rotating the token; `pncli doctor` detects the stale copy.
79
+ - `--host <host>` writes a whole-host entry instead, answered with the provider token pncli has for that host. Use it deliberately: it puts that token in front of every repo on the host.
80
+ - `disable` restores exactly what `enable` replaced; pncli records it under `gitAuth.scopes` in its global config. `enable` also rewrites any clone whose `origin` still carries an embedded token.
81
+
82
+ `pncli doctor` reports a `gitAuth` entry per marketplace: provider, mode, username, token source, whether git's credential matches pncli's, and clones with embedded tokens. Online, it runs an authenticated `git ls-remote` with exactly that username and token — which catches a wrong Bitbucket username as reliably as an expired token — and, for GitHub, reports the token kind (classic `ghp_` vs fine-grained), a missing `repo` scope, SSO authorization, and expiry within 14 days.
31
83
 
32
84
  If you upgrade pncli from a version that only supported a single marketplace, your existing config is migrated to the multi-marketplace format automatically the first time you run any `marketplace` command — no manual steps required.
33
85
 
@@ -0,0 +1,175 @@
1
+ # Sauce Labs
2
+
3
+ pncli calls the Sauce Labs REST API directly with your username and access key; no `saucectl`, Sauce Connect, or other CLI is required.
4
+
5
+ ## Configuration
6
+
7
+ | Key | Environment variable | CI fallback | Purpose |
8
+ |---|---|---|---|
9
+ | `saucelabs.baseUrl` | `PNCLI_SAUCELABS_BASE_URL` | — | API endpoint for your data center (see below) |
10
+ | `saucelabs.username` | `PNCLI_SAUCELABS_USERNAME` | `SAUCE_USERNAME` | Your Sauce Labs username |
11
+ | `saucelabs.accessKey` | `PNCLI_SAUCELABS_ACCESS_KEY` | `SAUCE_ACCESS_KEY` | Your access key |
12
+
13
+ Find both the username and the access key in Sauce Labs under **Account → User Settings**. The access key is long-lived; pncli sends it with the username as HTTP Basic auth.
14
+
15
+ `baseUrl` depends on the data center your account lives in:
16
+
17
+ | Data center | `baseUrl` |
18
+ |---|---|
19
+ | US West | `https://api.us-west-1.saucelabs.com` |
20
+ | US East | `https://api.us-east-4.saucelabs.com` (real devices only — no virtual-device jobs) |
21
+ | EU Central | `https://api.eu-central-1.saucelabs.com` |
22
+
23
+ ```bash
24
+ pncli config set saucelabs.baseUrl https://api.us-west-1.saucelabs.com
25
+ pncli config set saucelabs.username <your-username>
26
+ pncli config set saucelabs.accessKey <your-access-key>
27
+ pncli config test
28
+ ```
29
+
30
+ Or with environment variables:
31
+
32
+ ```bash
33
+ export PNCLI_SAUCELABS_BASE_URL=https://api.us-west-1.saucelabs.com
34
+ export PNCLI_SAUCELABS_USERNAME=<your-username>
35
+ export PNCLI_SAUCELABS_ACCESS_KEY=<your-access-key>
36
+ ```
37
+
38
+ **CI:** `SAUCE_USERNAME` and `SAUCE_ACCESS_KEY` are the names `saucectl`, Sauce Connect, and Sauce's CI integrations already use, so a pipeline that sets them needs only `PNCLI_SAUCELABS_BASE_URL`. Precedence for the username and access key is `PNCLI_SAUCELABS_*` → `SAUCE_*` → `.pncli.json` / `~/.pncli/config.json`.
39
+
40
+ ## Commands
41
+
42
+ ### Account and platform
43
+
44
+ ```bash
45
+ pncli saucelabs status # Is Sauce Labs operational? Current wait time
46
+ pncli saucelabs concurrency # Allowed vs. in-use VMs and real devices (also verifies credentials)
47
+ pncli saucelabs platforms --api appium # Supported platforms: all | appium | webdriver
48
+ pncli saucelabs appium-versions # Appium versions for real-device sessions, with EOL dates
49
+ pncli saucelabs users --phrase jane # Look up users in your organization
50
+ pncli saucelabs team list
51
+ pncli saucelabs team get <team-id>
52
+ pncli saucelabs team members <team-id>
53
+ ```
54
+
55
+ ### Jobs (virtual devices and desktop browsers)
56
+
57
+ ```bash
58
+ pncli saucelabs job list --limit 20 --from 2026-09-01T00:00:00Z
59
+ pncli saucelabs job get <job-id> # Status, platform, timings, log and video URLs
60
+ pncli saucelabs job assets <job-id> # Asset file names (logs, video, screenshots)
61
+ pncli saucelabs job update <job-id> --passed --build release-42 --tags smoke,email
62
+ pncli saucelabs job update <job-id> --public team # public | public restricted | share | team | private
63
+ pncli saucelabs job stop <job-id>
64
+ pncli saucelabs job delete <job-id>
65
+ ```
66
+
67
+ `--from` / `--to` accept Unix seconds or an ISO 8601 date. `--tags` replaces the job's existing tags.
68
+
69
+ ### Real-device jobs
70
+
71
+ ```bash
72
+ pncli saucelabs rdc-job list --limit 20
73
+ pncli saucelabs rdc-job list --live # Manual (live) tests only
74
+ pncli saucelabs rdc-job get <job-id> # Device, result, timings, device/network/crash log URLs
75
+ pncli saucelabs rdc-job update <job-id> --failed --name "Gmail rendering"
76
+ pncli saucelabs rdc-job stop <job-id>
77
+ pncli saucelabs rdc-job delete <job-id>
78
+ ```
79
+
80
+ ### Builds
81
+
82
+ `--source` is `vdc` (virtual devices, the default) or `rdc` (real devices).
83
+
84
+ ```bash
85
+ pncli saucelabs build list --source rdc --status failed,error --limit 10
86
+ pncli saucelabs build get <build-id> --source rdc
87
+ pncli saucelabs build jobs <build-id> --failed # also --errored --passed --running --queued --completed --finished --faulty
88
+ pncli saucelabs build for-job <job-id> # Which build a job belongs to
89
+ ```
90
+
91
+ ### Real devices
92
+
93
+ ```bash
94
+ pncli saucelabs device list --os android --type phone
95
+ pncli saucelabs device list --name "iPhone 1[56].*" --os-version 17
96
+ pncli saucelabs device get iPhone_15_real # Full hardware and OS descriptor
97
+ pncli saucelabs device status --state available # Which devices are free right now
98
+ pncli saucelabs device status --private-only
99
+ ```
100
+
101
+ `--name` and `--os-version` are regular expressions on the Sauce side.
102
+
103
+ ### Device sessions (Real Device Access API)
104
+
105
+ A session reserves a real device and lets you drive it through the API. Close it when you are done — an open session holds a device and counts against your concurrency.
106
+
107
+ ```bash
108
+ # Reserve a device; --wait polls until it is ACTIVE (default timeout 300s)
109
+ pncli saucelabs session create --device-name "Samsung Galaxy S2[34].*" --os android --wait
110
+ pncli saucelabs session create --os ios --duration PT30M --tunnel-name <tunnel-name> --wait
111
+
112
+ pncli saucelabs session list --state active
113
+ pncli saucelabs session get <session-id> # State, expiry, Appium URL, live-view link
114
+
115
+ # Drive the device
116
+ pncli saucelabs session open-url <session-id> https://preview.imagile.dev/welcome-email
117
+ pncli saucelabs session shell <session-id> "getprop ro.build.version.release" # Android only
118
+ pncli saucelabs session install-app <session-id> storage:filename=app.apk --launch
119
+ pncli saucelabs session installations <session-id>
120
+ pncli saucelabs session launch-app <session-id> --package-name com.google.android.gm
121
+ pncli saucelabs session launch-app <session-id> --bundle-id com.apple.mobilesafari
122
+ pncli saucelabs session uninstall-app <session-id> --package-name <package>
123
+ pncli saucelabs session settings <session-id> --orientation landscape --locale de_DE
124
+
125
+ # Record a test (a job) inside the session
126
+ pncli saucelabs session start-test <session-id> --name "Welcome email" --build release-42 --video --device-logs
127
+ pncli saucelabs session end-test <session-id> --passed
128
+ pncli saucelabs session tests <session-id>
129
+
130
+ # Network throttling
131
+ pncli saucelabs session network-profiles <session-id>
132
+ pncli saucelabs session network <session-id> --profile 4G-fast
133
+ pncli saucelabs session network <session-id> --download 1500 --upload 750 --latency 300 --loss 1
134
+ pncli saucelabs session network <session-id> --reset
135
+
136
+ # Appium
137
+ pncli saucelabs session appium <session-id>
138
+ pncli saucelabs session appium <session-id> --start --appium-version <version>
139
+
140
+ # Release the device
141
+ pncli saucelabs session delete <session-id>
142
+ ```
143
+
144
+ Limits Sauce Labs enforces, not pncli:
145
+
146
+ - Sessions default to 6 hours. Public devices cap at 1 hour, private devices at 24 hours; longer `--duration` values are capped rather than rejected.
147
+ - `session shell` works on Android only, and public devices accept only an allowlisted set of commands.
148
+ - `session settings --locale` and `--animations` are Android only; `--orientation` works on both.
149
+ - A `409` on `session create` means your concurrency limit is reached — check `pncli saucelabs concurrency`.
150
+
151
+ ### Sauce Connect tunnels
152
+
153
+ ```bash
154
+ pncli saucelabs tunnel list # Full details for your tunnels
155
+ pncli saucelabs tunnel list --all # Include tunnels shared with you
156
+ pncli saucelabs tunnel get <tunnel-id>
157
+ pncli saucelabs tunnel jobs <tunnel-id> # Jobs currently running through it
158
+ pncli saucelabs tunnel stop <tunnel-id>
159
+ ```
160
+
161
+ ### App storage
162
+
163
+ ```bash
164
+ pncli saucelabs storage files --kind android --query gmail
165
+ pncli saucelabs storage groups --kind ios
166
+ ```
167
+
168
+ ## Not supported
169
+
170
+ pncli returns JSON from API calls; it does not handle binary content or visual comparison.
171
+
172
+ - Downloading job assets (videos, logs, screenshots), device screenshots, and pulling or pushing files on a device. `job assets` and the `*_url` fields on jobs give you the links.
173
+ - Uploading apps to app storage.
174
+ - Judging how something renders. To check an email on real devices, reserve a session, `open-url` the email's web view or `launch-app` the mail client, record it with `start-test --video --screenshots`, and review the recording through the job's `video_url` or the session's `liveViewUrl`.
175
+ - Starting a Sauce Connect tunnel. Run Sauce Connect itself, then reference the tunnel with `--tunnel-name`.