@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.
Files changed (3) hide show
  1. package/README.md +43 -34
  2. package/dist/bin.js +1386 -835
  3. 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 Secret Service on Linux, or the Windows Credential Manager.
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 [--url <base URL>] [--port <port>] [--send-content]` | Logs in with the browser, stores a user ingest key and an API token in the keychain, and configures Claude Code and Codex |
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 the key was revoked (401) it prints that line and the message as plain text instead, and writes the
77
- message to stderr.
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 [--url <base URL>] [--port <port>] [--send-content]` connects this machine to a Harnessforce Workspace:
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, the Secret Service on Linux, or the Windows
108
- Credential Manager (through [`@napi-rs/keyring`](https://github.com/Brooooooklyn/keyring-node)).
109
- Without one it stops. Keys are never written to a plain file.
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
- 「ログインの有効期限が切れました。`harnessforce init`を実行してください」.
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
- ### Default connection
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
- The default base URL is an optional build input: `HARNESSFORCE_BUILD_URL=<apps/web base URL> pnpm build`.
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