@harnessforce/cli 0.1.0 → 0.2.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.
- package/README.md +43 -34
- package/dist/bin.js +1386 -835
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -11,8 +11,8 @@ then, install it from the [local marketplace build](https://github.com/lambda-sc
|
|
|
11
11
|
## Requirements
|
|
12
12
|
|
|
13
13
|
- Node.js 18 or later (the same Node.js the plugin hook uses).
|
|
14
|
-
- An OS keychain: macOS Keychain, the
|
|
15
|
-
Keys and tokens are never written to a plain file.
|
|
14
|
+
- An OS keychain: macOS Keychain, the Windows Credential Manager, or on Linux the Secret Service or the
|
|
15
|
+
kernel keyring (keyutils). Keys and tokens are never written to a plain file.
|
|
16
16
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
@@ -23,18 +23,18 @@ harnessforce init
|
|
|
23
23
|
|
|
24
24
|
## Commands
|
|
25
25
|
|
|
26
|
+
`harnessforce --help` lists the commands. `harnessforce <command> --help` explains when to use a command, what it sends and does not send, its exit codes, and examples.
|
|
27
|
+
|
|
26
28
|
| Command | What it does |
|
|
27
29
|
| --- | --- |
|
|
28
|
-
| `harnessforce init [--
|
|
29
|
-
| `harnessforce otel-headers` | Prints the `Authorization` header for Claude Code's `otelHeadersHelper
|
|
30
|
+
| `harnessforce init [--port <port>] [--send-content] [--no-plugin-auto-update]` | Logs in with the browser, stores a user ingest key and an API token in the keychain, and configures Claude Code and Codex |
|
|
31
|
+
| `harnessforce otel-headers` | Prints the `Authorization` header for Claude Code's `otelHeadersHelper`. Exits 3 when there is no key, 4 when the destination differs from the pinned origin, 5 while the CLI version is unsupported, and 1 on other failures. On Linux, after printing a key read from the kernel keyring, it moves that Workspace's items to an unlocked Secret Service (not when the hook starts it), so a reboot does not lose them |
|
|
30
32
|
| `harnessforce run --issue <identifier> -- <agent> [args]` | Launches an agent with its session linked to an Issue |
|
|
31
33
|
| `harnessforce import` | Sends the metadata of past Claude Code sessions in connected repositories |
|
|
32
34
|
| `harnessforce tune [--all] [--no-send] [--show-report] [--json]` | Analyzes past sessions on this machine by public rules and sends the counts (analysis reports) |
|
|
33
35
|
| `harnessforce tune record` / `harnessforce tune --purge` | Records a proposal from stdin / deletes everything under `~/.harnessforce/tune/` |
|
|
34
36
|
| `harnessforce top [--once] [--json] [--ascii] [--theme <auto\|light\|dark\|ansi>]` | Shows the Claude Code and Codex sessions on this machine in the terminal: state, tokens, context and tool failures. Reads only local files and never connects to Harnessforce |
|
|
35
37
|
| `harnessforce hook session-start` | The entry of Codex's `SessionStart` hook: reads `session_id`, `cwd` and `source` from stdin, registers the session, and prints the session ID for the agent's context. Always exits 0 |
|
|
36
|
-
| `harnessforce usage-limits on [--yes]` / `off` / `status` | Shares your usage limits with the workspace after you agree to the consent text (`on`), or withdraws the consent, deletes the stored values and restores your previous statusLine (`off`), or shows the consent, the workspace opt-in and whether the statusLine is wrapped (`status`). Not supported on Windows |
|
|
37
|
-
| `harnessforce usage-limits statusline` | A statusLine command that wraps your existing one: it passes stdin to the original command, returns its output and exit code unchanged, then sends only the usage percentage and reset time of the 5-hour and 7-day windows. It sends nothing until you have consented |
|
|
38
38
|
| `harnessforce --version` | Prints the version |
|
|
39
39
|
|
|
40
40
|
## Watching your sessions (`harnessforce top`)
|
|
@@ -73,23 +73,12 @@ Codex runs this command through the `[[hooks.SessionStart]]` entry that `harness
|
|
|
73
73
|
Claude Code user settings that `harnessforce init` wrote. When `HARNESSFORCE_ISSUE` is a valid identifier
|
|
74
74
|
(`harnessforce run --issue` sets it), the registration claims `source=cli` with that Issue.
|
|
75
75
|
- prints `{"hookSpecificOutput": {...}}` carrying `harnessforce session_id: <id>` so the agent knows its session
|
|
76
|
-
ID. When
|
|
77
|
-
|
|
76
|
+
ID. When it cannot send, it adds one warning as `systemMessage`, which Codex shows in its UI, and writes it to
|
|
77
|
+
stderr: a revoked key (401), sending not set up, an invalid endpoint, no key in the keychain, a destination
|
|
78
|
+
that differs from the pinned origin, a keychain that could not be read within 1 second, or a failed
|
|
79
|
+
registration. Only `startup`, `clear` and a missing `source` show a warning.
|
|
78
80
|
- never fails the session: any error ends with exit code 0, and nothing is sent outside a git repository.
|
|
79
81
|
|
|
80
|
-
## Usage limits in the statusLine (`harnessforce usage-limits statusline`)
|
|
81
|
-
|
|
82
|
-
Claude Code passes the plan limits (`rate_limits`) only to the statusLine command, not to hooks. `harnessforce usage-limits statusline` is meant to sit in front of the statusLine you already use (for example claude-hud):
|
|
83
|
-
|
|
84
|
-
- It reads all of stdin and, if `~/.harnessforce/usage-limits.json` has an `original` command, runs it with `sh -c`, hands it the same stdin and the same environment, and returns its stdout, stderr and exit code as they are. No time limit is added.
|
|
85
|
-
- After that, only if `usage-limits.json` records your consent for the current text version, it takes `rate_limits.five_hour` and `rate_limits.seven_day` (`used_percentage` and `resets_at`) and sends them to `POST <ingest endpoint>/v1/usage-limits` with the user ingest key from the keychain. Nothing else in the JSON (`cwd`, `transcript_path`, `cost`, and so on) is read or sent.
|
|
86
|
-
- The send runs in a detached child process with stdin, stdout and stderr closed, so the statusLine is never delayed. A failure does not change the output or the exit code.
|
|
87
|
-
- It sends at most once every 5 minutes, unless the reset time of a window changed. The time and the reset times of the last send are kept in `~/.harnessforce/usage-limits-state.json` (owner-only).
|
|
88
|
-
|
|
89
|
-
### Turning it on and off
|
|
90
|
-
|
|
91
|
-
`harnessforce usage-limits on` shows the consent text, asks `y` (skipped with `--yes`), records the consent (`PUT /api/v1/cli/usage-limits/consent`), and then wraps the `statusLine` in your Claude Code user settings: only `command` is replaced with `harnessforce usage-limits statusline`, so `type`, `padding`, `refreshInterval` and the other keys stay. The statusLine you had (for example claude-hud) is saved as-is in `~/.harnessforce/usage-limits.json`. If the statusLine is not `type: "command"`, or the settings cannot be read, nothing is recorded or changed. If writing the mark or the settings fails, the consent is withdrawn again. `harnessforce usage-limits off` withdraws the consent first and then puts the saved statusLine back (or removes it if there was none); a statusLine that another tool has changed since is left alone.
|
|
92
|
-
|
|
93
82
|
## What is never sent
|
|
94
83
|
|
|
95
84
|
Harnessforce never turns on prompt or body logging by itself. `harnessforce run` never turns it on, and `harnessforce init`
|
|
@@ -102,11 +91,15 @@ tokens are sent only to the origins pinned by `harnessforce init`.
|
|
|
102
91
|
|
|
103
92
|
## CLI credentials (`harnessforce init`, `harnessforce otel-headers`)
|
|
104
93
|
|
|
105
|
-
`harnessforce init [--
|
|
94
|
+
`harnessforce init [--port <port>] [--send-content] [--no-plugin-auto-update]` connects this machine to a Harnessforce Workspace:
|
|
106
95
|
|
|
107
|
-
1. It checks the OS keychain first: macOS Keychain
|
|
108
|
-
|
|
109
|
-
|
|
96
|
+
1. It checks the OS keychain first: macOS Keychain or the Windows Credential Manager (through
|
|
97
|
+
[`@napi-rs/keyring`](https://github.com/Brooooooklyn/keyring-node)). On Linux it writes and deletes
|
|
98
|
+
a non-secret `availability-probe` item in the Secret Service (GNOME Keyring or KWallet), waiting at
|
|
99
|
+
most 60 seconds for you to unlock it, and saves there when that works. Otherwise, as on a machine
|
|
100
|
+
reached only over SSH, it saves to the kernel's persistent keyring, which lives in memory and is
|
|
101
|
+
cleared on reboot or after a period of disuse; run `harnessforce init` again then. Without either
|
|
102
|
+
store it stops. Keys are never written to a plain file.
|
|
110
103
|
2. It logs in with the browser: OAuth 2.0 authorization code with PKCE, a `127.0.0.1:8080` loopback
|
|
111
104
|
redirect (`--port <port>` changes the port), the endpoint discovered from
|
|
112
105
|
`/.well-known/oauth-authorization-server`, and a 5 minute wait. When the port is already in use it
|
|
@@ -120,7 +113,10 @@ tokens are sent only to the origins pinned by `harnessforce init`.
|
|
|
120
113
|
`<workspace_id>:api-token` (a JSON object with the access token, the refresh token and their
|
|
121
114
|
expiries), and pins the ingest endpoint origin as `<workspace_id>:ingest-origin`
|
|
122
115
|
and the base URL origin as `<workspace_id>:url-origin`. It deletes both origins first and writes
|
|
123
|
-
them last, so a failure part way never pairs an old origin with a new key or token.
|
|
116
|
+
them last, so a failure part way never pairs an old origin with a new key or token. After saving to
|
|
117
|
+
the Secret Service it deletes the same Workspace's items from the kernel keyring. Later commands read
|
|
118
|
+
a Workspace from the kernel keyring when it holds the ingest key there, and from the Secret Service
|
|
119
|
+
otherwise.
|
|
124
120
|
5. It updates the Claude Code user settings (`~/.claude/settings.json`, or `$CLAUDE_CONFIG_DIR`),
|
|
125
121
|
changing only these keys:
|
|
126
122
|
- `env`: the OTel exporter variables, `HARNESSFORCE_URL`, `HARNESSFORCE_ENDPOINT` and
|
|
@@ -128,6 +124,9 @@ tokens are sent only to the origins pinned by `harnessforce init`.
|
|
|
128
124
|
to content it also adds `OTEL_LOG_USER_PROMPTS=1`
|
|
129
125
|
- `otelHeadersHelper`: `harnessforce otel-headers`
|
|
130
126
|
- `enabledPlugins["harnessforce@harnessforce-agent"]`
|
|
127
|
+
- `extraKnownMarketplaces["harnessforce-agent"]` (or `additionalMarketplaces` when only that alias is
|
|
128
|
+
present): the GitHub source and `autoUpdate: true`, so Claude Code updates the plugin in the
|
|
129
|
+
background. `--no-plugin-auto-update` writes `false`; a value you set yourself is kept.
|
|
131
130
|
6. It updates the Codex `config.toml` (`$CODEX_HOME` when it is an absolute path, otherwise `~/.codex`),
|
|
132
131
|
changing only these three items and keeping every other key and comment:
|
|
133
132
|
- `[otel].exporter`: OTLP/HTTP (`protocol = "binary"`) to the ingest endpoint with
|
|
@@ -162,16 +161,22 @@ authorization server metadata of the base URL, only when that endpoint is on the
|
|
|
162
161
|
origin. The new pair replaces `<workspace_id>:api-token`. A lock older than 60 seconds is treated as
|
|
163
162
|
abandoned, and waiting more than 30 seconds for it counts as a failed refresh. If the refresh fails, or
|
|
164
163
|
the refreshed token is also answered with 401, they stop with
|
|
165
|
-
|
|
164
|
+
"Your login has expired. Run `harnessforce init`".
|
|
165
|
+
|
|
166
|
+
## Updates
|
|
167
|
+
|
|
168
|
+
Every request to Harnessforce carries `User-Agent: harnessforce-cli/<version>` and nothing else about
|
|
169
|
+
the machine. The server answers with the update status, which is kept in
|
|
170
|
+
`~/.harnessforce/update-status.json`:
|
|
166
171
|
|
|
167
|
-
|
|
172
|
+
- A newer version is shown once a day after a command (not with `NO_UPDATE_NOTIFIER` or `CI`).
|
|
173
|
+
- After a security fix, the date this version stops sending is shown after every command.
|
|
174
|
+
- An unsupported version sends nothing: sending commands exit 1, `harnessforce run` starts the agent
|
|
175
|
+
without the Harnessforce environment, `harnessforce otel-headers` exits 5 without a key, and the Codex
|
|
176
|
+
hook registers nothing. After 24 hours it checks again with `GET /v1/client-status`, without
|
|
177
|
+
credentials.
|
|
168
178
|
|
|
169
|
-
|
|
170
|
-
Without it the default is `https://harnessforce.dev`. The value must be `https:`, or `http:` for localhost.
|
|
171
|
-
At run time the base URL is the environment variable `HARNESSFORCE_URL`, then `env.HARNESSFORCE_URL` in the
|
|
172
|
-
user settings (`harnessforce init --url` writes it), then the build default. The plugin build
|
|
173
|
-
uses the same value for its MCP server URL. See
|
|
174
|
-
[Building the local marketplace](https://github.com/lambda-script/harnessforce-agent/blob/main/docs/runbooks/local-marketplace.md).
|
|
179
|
+
Update with `npm install -g @harnessforce/cli@latest`. The CLI never replaces itself.
|
|
175
180
|
|
|
176
181
|
## Launching an agent for an Issue (`harnessforce run`)
|
|
177
182
|
|
|
@@ -229,6 +234,10 @@ work done before the plugin was installed can be linked to issues too. Run `harn
|
|
|
229
234
|
- Sessions are sent 100 at a time to `POST <HARNESSFORCE_ENDPOINT>/v1/imports/sessions` with the user
|
|
230
235
|
ingest key, only when the endpoint origin matches the one pinned by `harnessforce init`. `429` and `503` are
|
|
231
236
|
retried up to 3 times after `Retry-After` (at most 60 seconds).
|
|
237
|
+
- Sessions that are not sent are counted by the first reason that applies (already sent, outside the
|
|
238
|
+
import window, outside a git repository, repository not connected to the Workspace) and printed on one line.
|
|
239
|
+
When nothing was imported because no repository is connected, it says to connect the repository
|
|
240
|
+
with the GitHub App in Harnessforce.
|
|
232
241
|
- Sent sessions are recorded in `~/.harnessforce/import-state.json` per Workspace and ingest endpoint,
|
|
233
242
|
so running it again continues where it stopped and never sends a session twice to the same place.
|
|
234
243
|
Sessions dropped because of the monthly event limit or a read-only Workspace are not recorded and
|