npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@visiq/claude-cowork-harness

v0.1.10

Published

VisIQ agent-governance harness for Claude Cowork (the Claude desktop agent) — pre-execution enforcement on host-local sessions via Claude Code hooks, plus audit-stream monitoring for VM-sandboxed sessions the hooks cannot reach.

Downloads

1,035

Readme

@visiq/claude-cowork-harness

VisIQ governance for Claude Cowork — the agent surface in the Claude desktop app.

Cowork is built on Claude Code, but it runs a session in one of two places, and the difference decides what governance is even possible. This package covers both, and labels each honestly.

| Surface | Where it runs | What VisIQ does | How | |---|---|---|---| | Host-local | The desktop app spawns its own claude natively on your machine | Enforcement — action + retrieval governance, deny blocks the tool call before it runs | Claude Code hooks (SessionStart, PreToolUse, PostToolUse) | | VM sandbox | The session runs in the app's Linux guest | Monitoring — every tool call, its arguments, the model, and the sandbox's own allow/deny answer for each call, each reported to the platform as its own record | Tailing the host-side per-session audit stream |

The sandbox surface cannot be enforced. A sandboxed session's config dir lives inside the guest and carries no settings.json, so no host hook can fire for it. The audit stream this package tails is written after a tool has already run. Nothing in this package reports a block on that surface, and status says so.

If an AI coding agent is doing this install

These are its rules — the same guardrail VisIQ prepends to every setup prompt the platform generates. They are here because this page is one an agent gets pasted, and because a step below really can fail: latest tracks main, so it can lag the fixes already on the integration branch. Improvising around a failed step is how an agent ends up building a CLI from source and patching the very harness it was sent to install.

Rules for the AI agent running this setup — read these first:

  • Apply ONLY the changes this prompt explicitly shows: its commands, its environment/config values, and its exact code snippet(s), if any. Nothing else may be written, edited, or patched — not code, not packages, not configuration.
  • Never improvise a workaround when something fails: no building from source, no packing tarballs, no PATH shims or alternative registries, and no debugging or patching the VisIQ software itself.
  • If any command fails, is unavailable, or produces output that differs from what this prompt describes, STOP and report the exact command plus its full output back to your operator. A partial install is a safe no-op by design — leaving it unfinished is always correct; an unauthorized fix never is.
  • Do the steps below IN ORDER, and after each one run the Check: command it shows and confirm the output before moving on. NEVER report the setup complete based on what you intended to do — only on what a check actually showed. If a check does not show what it describes, the step is not done.
  • Reading is always in scope. Inspect these commands, the URLs they call and the integrity checks they run BEFORE executing anything, and tell your operator what you found. These rules bound what you may CHANGE, never what you may examine — if something here looks wrong, refusing to run it and saying why is the correct outcome, not a failed setup.

Install

Install both packages. This one ships visiq-claude-cowork; the sibling ships the visiq-claude-code-hook dispatcher that install wires into settings.json, and the visiq-claude-code CLI that holds your credentials. Our workspace deps are inlined into this bundle at publish time and stripped from dependencies, so npm will not pull the sibling in for you — install only this package and status reports dispatcher: … NOT FOUND on PATH, with tool calls running UNGOVERNED.

npm install -g @visiq/claude-cowork-harness @visiq/claude-code-harness

Check: run visiq-claude-cowork status. Its dispatcher: line must end in a real path. → NOT FOUND on PATH means only one of the two packages landed, and every tool call will run UNGOVERNED.

# 1. Host-local enforcement (writes ~/.claude/settings.json)
visiq-claude-cowork install

Check: hooks: now reads installed — <path to your settings.json>. not installed means the write did not happen; UNKNOWN (settings file is not readable JSON) means your existing settings file could not be parsed and nothing was changed.

# 2. Credentials — shared with @visiq/claude-code-harness
visiq-claude-code configure --api-key <key> --agent-id <id>

Check: credentials: now reads resolved, and agent id: shows the id you passed. While it reads MISSING the hooks are wired but every one of them silently no-ops.

# 3. Sandbox monitoring (optional, long-running)
visiq-claude-cowork monitor

Check: each pass prints [VisIQ] scanned N log(s): …, and status then reports a count on its VM-sandbox line instead of NOT monitored.

Endpoint (optional — but read this if you are not on the VisIQ cloud)

--api-key and --agent-id are the two REQUIRED values. The endpoint is optional and defaults to the VisIQ SaaS control plane, https://api.visiqlabs.com. If you run a sovereign / self-hosted / on-prem VisIQ, you MUST set it — otherwise this harness reports every governed tool call to the VisIQ cloud instead of your own control plane:

visiq-claude-code configure \
  --api-key <key> \
  --agent-id <id> \
  --base-url https://visiq.internal.example.com   # OPTIONAL (defaults to the VisIQ cloud)

# or via the environment (same precedence as the Claude Code harness)
export VISIQ_BASE_URL=https://visiq.internal.example.com   # OPTIONAL

The credential and endpoint resolution is the Claude Code harness's own — the hooks and the monitor both read it — so VISIQ_API_KEY / VISIQ_AGENT_ID / VISIQ_BASE_URL behave exactly as documented for @visiq/claude-code-harness.

Restart the Claude desktop app after installing. Then:

visiq-claude-cowork status
[VisIQ] Claude Cowork harness 0.1.10 — status
  this process:       unknown (cannot enforce)
  credentials:        resolved
  agent id:           agent_01HQ7ZK3M4N5P6R7S8T9V0W1X2
  hooks:              installed — /Users/you/.claude/settings.json
  dispatcher:         visiq-claude-code-hook → /usr/local/bin/visiq-claude-code-hook
  Claude Cowork (host-local): ENFORCED (PreToolUse deny blocks)
  Claude Cowork (VM sandbox): monitor-only — 3 session(s), 5 audit log(s), all observed
  session tree:       /Users/you/Library/Application Support/Claude/local-agent-mode-sessions
  monitor state:      /Users/you/.visiq/claude-cowork/monitor-state.json

That block is not hand-written. __tests__/readme.e2e.test.ts builds exactly the state it depicts — three sessions, five audit logs, all of them already tailed — runs the built CLI against it, and requires this sample to match what came back. Absolute paths differ per machine and per OS, so those four values are the only thing it does not compare; every label, every count and the version are bound. The sandbox line in particular is a coverage claim the monitor has to have earned: with no monitor run it reads NOT monitored — … none observed.

Commands

| Command | Purpose | |---|---| | install [--settings <path>] [--command <cmd>] | Wire the governance hooks (host-local enforcement) | | uninstall [--settings <path>] [--command <cmd>] | Remove them, leaving your own hooks untouched | | status [--settings <path>] [--command <cmd>] | What is installed, detected, and actually covered | | monitor [--once] [--interval <ms>] | Tail the sandbox audit stream (monitor-only) |

This table is not hand-maintained either. __tests__/usage.test.ts derives each signature from the CLI's own flag table (src/cli/flags.ts) and requires this table — and the docs quickstart — to match, so a flag the CLI accepts and this page omits reds a test rather than shipping. It had: status --settings, status --command and uninstall --command are all accepted and acted on, and none of them was documented anywhere.

--settings and --command must be repeated on every command that touches that file. They configure ONE invocation, not a saved preference: after install --settings /custom.json, a bare status reads ~/.claude/settings.json and truthfully reports it as uncovered — and following that output's advice with a bare install would write your live config, the outcome --settings exists to avoid.

So the CLI carries the flag forward. Every command it suggests that accepts --settings is printed with the file already in effect, ready to paste:

visiq-claude-cowork install --settings /custom.json
visiq-claude-cowork uninstall --settings /custom.json
visiq-claude-cowork status --settings /custom.json

monitor does not take --settings — it is suggested bare, because the CLI never prints an invocation it would refuse. These examples are not hand-maintained either: __tests__/hints.test.ts re-derives them from the renderer the CLI itself calls, so if the carry-forward ever stopped, this promise would red rather than ship.

Why install wires the Claude Code dispatcher

Host-local Cowork sessions read the same ~/.claude/settings.json as the Claude Code CLI, so this package installs the shared visiq-claude-code-hook dispatcher rather than a second one. Two dispatchers over one file would double-evaluate every tool call and the two installers would fight over the same JSON. If you already run @visiq/claude-code-harness, your host-local Cowork sessions are already governed — this package adds the Cowork surface labelling and the sandbox monitor.

Fail posture

Agent-side, this harness fails open by default (owner decision 2026-07-15): if VisIQ itself cannot answer — no credentials, endpoint unreachable, no bundle — the agent keeps working and the failure is reported loudly. A policy deny always blocks. Opt into strict fail-closed with VISIQ_FAIL_MODE=closed.

The monitor is fail-open by construction: sessions are created and deleted under it constantly, so a vanished directory, a truncated write, or a corrupt line is skipped, counted, and reported — never fatal.

What is captured

From the sandbox audit stream, per tool call: tool name (including fully-qualified mcp__* tools), tool arguments, model, session/tool-call ids, sub-agent parentage, and the observation timestamp. Each observation is stamped with the surface it came from and mode: "monitor".

Also captured: the sandbox's own permission decisions. Cowork asks you to approve a tool call, and it writes both halves of that exchange — the request (with the tool name and arguments) and your answer (granted, and whether you allowed it once or always). Every pass of monitor reports them:

[VisIQ] scanned 1 log(s): 0 new tool call(s), 0 duplicate(s), 0 corrupt line(s), 0 truncated line(s)
[VisIQ] sandbox permission gate: 2 request(s), 2 answered (1 REFUSED, 1 standing 'always' grant(s), 0 unreadable, 0 unattributed)
[VisIQ] REFUSED by the sandbox's own gate, so these calls did NOT run: session sess: Bash ×1 — 0 bound to a tool call in this pass

That block is not hand-written either — __tests__/readme.e2e.test.ts drives the built monitor over a session whose gate exchange lands in this pass and requires these lines to come back.

The counter line is printed every pass, zeros included, so a monitor that has seen no refusals is distinguishable from one that cannot report them. The second line appears only when there was a refusal, and it is the line to alert on — so it names the session each refusal came from. A pass walks every account, org and session under the tree, and an alert that names tools but no session cannot be actioned.

standing 'always' grant(s) counts only the answers that were granted. A standing deny (always + refused — "never allow this tool again") is counted under REFUSED, where it belongs; counting it as a standing grant made one refusal read as two answers, one of them permissive.

unattributed counts the answers whose tool the sandbox did not name — a permission_response written with no readable tool_name. Those records used to be dropped by the parser before the subtype was even consulted, so a refusal carrying one landed in no counter at all: the pass printed 0 answered (0 REFUSED …), exited 0, and committed its cursor, which meant those bytes were never read again. For a response the record is the only evidence the refusal happened, so it is now counted and reported, with <unattributed> in place of the tool name. It is a different axis from unreadable — that one is an answer we could not read, this one is an answer whose tool was not named, and a record can be either, both or neither. Such a record is deliberately not bound to any tool call: the tool name is the binding evidence and there is none, so attributing it to the nearest call would invent a denial the vendor never wrote.

Every permission decision is reported to the platform in its own right, as a record carrying cowork.record: "permission_decision" and the same cowork.execution value the table below describes. That matters because of the next paragraph: it is what makes a refusal survive the process.

bound to a tool call in this pass is the honest part of the limit. When a call and its answer are read together, the answer is also stamped onto that call's own observation as cowork.execution (see below). When they are not — you took longer to answer the prompt than the poll interval, which is the usual case, and what the sample above shows — that call has already been reported unconfirmed, and the decision record is what carries the refusal.

The monitor will not advance its cursor past a pass whose records did not reach the platform, including a pass whose only records are permission decisions. Such a pass exits non-zero and re-reads those bytes next time, rather than reporting the refusal to a terminal and forgetting it.

An observed call is not a call that ran

The audit record for a tool call is written when the model issues it — before the permission prompt is answered. So a call you refuse still appears in the stream, and on its own it looks exactly like one that executed.

Every observation therefore carries cowork.execution:

| value | means | |---|---| | refused | you denied it. The tool never ran. | | granted | you approved it. | | unconfirmed | the stream has not said — the call was not gated, or its answer has not been read yet. This is the default, and it is not a claim that the call ran. |

Counting unconfirmed as executed would over-report; the monitor does not do it, and neither should anything reading these observations.

License

See LICENSE.