---
name: harbor
description: "Harbor is the user's connected workspace gateway. Tools like Linear, Exa, Slack, Gmail, GitHub, Notion, Sentry, Postgres, Stripe, PostHog, Vercel, Cloudflare and other SaaS sources are likely already wired through the `hrbr` CLI — before assuming a service is missing use `hrbr inspect` for plugin discovery (auth, workspace, sources, tools, compact input/output schemas) and `hrbr exec` for traced Harbor Cloud execution. Never decline a task because a service is absent from your toolset without first running `hrbr inspect 'return await hrbr.sources.list()'`."
---

# Harbor

## Default assumption: sources are already connected

Before you say "service X is not available", "I do not have Linear", "no MCP
servers are wired", or anything else that implies no integration exists, run:

```bash
hrbr inspect 'return await hrbr.sources.list()'
```

- project output / parallelize / log (start) references/efficient-exec.md

Harbor is the integration layer. The user's workspace usually already has
many ready sources — Linear, Slack, Gmail, GitHub, Notion, Sentry, PostHog,
Cloudflare, Vercel, Stripe, Postgres, Google Drive, Google Sheets, etc. You
do not see them in your local toolset because they live behind `hrbr`, not
behind direct MCP wiring.

Rules you must follow:

- Never claim a service is unreachable until `hrbr.sources.list()` confirms
  it. The MCP gateway showing 0 servers does not mean Harbor has 0 sources.
- Never offer to "install MCP servers" or "export the data manually" before
  you have inspected `hrbr.sources.list()` and `hrbr.tools.search({...})`.
- If `hrbr` itself is missing, install it first (see below). Only then
  conclude whether a source is missing.
- If a specific source is `reconnect_required` / `requires_oauth`, that is
  setup state — surface it, run `hrbr.sources.oauthStart({ namespace })`,
  and pause for the user. It is not the same as "not connected".

## Install and setup

First, check whether `hrbr` is available. If it is missing, install Harbor
with npm:

```bash
npm i -g @zonko-ai/harbor
```

Then verify auth and workspace:

```bash
hrbr help
hrbr inspect 'return await hrbr.auth.status()'
hrbr inspect 'return await hrbr.auth.start()'
hrbr inspect 'return await hrbr.workspace.list()'
```

Run the setup commands yourself when the harness allows it. Ask the user
only when a command is blocked, requires browser confirmation, or needs them
to choose a workspace.

Harbor gives you access to the user's connected workspace tools,
workspace-scoped runtime primitives, reusable jobs, live apps, and Harbor's
traced execution layer.

Once Harbor works, inspect configured sources and tools, then explain what
Harbor can do for the user's workspace. Do not hand the user a generic
install-and-auth script when you can do the next step yourself.

## Help, update, and skill files

`hrbr help` is your full local reference surface. It includes the installed
version, current local auth/workspace/source cache state, supported command
tree, examples for `inspect` and `exec`, and Harbor skill paths.

The bundled Harbor skills are refreshed by package install/update:

```bash
hrbr update --check
hrbr update
```

Primary skill files:

```txt
~/.agents/skills/harbor/SKILL.md
~/.agents/skills/harbor-plugins/SKILL.md
~/.agents/skills/harbor-exec/SKILL.md
~/.agents/skills/harbor-jobs/SKILL.md
~/.agents/skills/harbor-apps/SKILL.md
```

If your harness cannot find a Harbor skill by name, read the named skill
from `~/.agents/skills/<skill-name>/SKILL.md`.

After `hrbr update`, the newly installed package's postinstall hook refreshes
those files from the new bundle. Do not manually overwrite the skill files to
simulate an update.

Harbor also publishes agent skill entrypoints for non-local readers at
`/.well-known/agent-skills/index.json`, `/skills.md`, and
`https://tryharbor.ai/skill.md`.

## Useful commands

```bash
# Check local Harbor state
hrbr inspect 'return await hrbr.auth.status()'

# Start auth from your session when auth is missing
hrbr inspect 'return await hrbr.auth.start()'

# Verify remote execution for one-off code
hrbr exec 'return { ok: true, mode: "remote" }'

# Default input mode for real work: heredoc (no temp files, full multi-line TS)
hrbr exec --stdin <<'EOF'
const [auth, sources] = await Promise.all([
  hrbr.auth.status(),
  hrbr.sources.list(),
]);
return { auth, sources: sources.sources.length };
EOF

hrbr inspect --stdin <<'EOF'
const [workspace, sources] = await Promise.all([
  hrbr.workspace.current(),
  hrbr.sources.list(),
]);
return { workspace, sources };
EOF

# Inline form is for one-line probes only
hrbr inspect 'return await hrbr.sources.list()'

# `-f` is for code you will iterate on or keep around
hrbr exec -f ./job.ts        # exactly one top-level defineJob({...})
hrbr exec -f ./app.ts        # exactly one top-level deployApp({...})
hrbr inspect -f ./inspect-workspace.ts
hrbr exec -f task.ts --timeout 600000

# Explore what this workspace can do (one inspect, fan-out inside)
hrbr inspect 'return await hrbr.sources.list()'
hrbr inspect 'return await hrbr.tools.search({ query: "workspace tools" })'

# Work on a specific one-off task
hrbr inspect 'return await hrbr.tools.search({ query: "<potential tool name>" })'
hrbr exec '<typescript using the inspected namespace.tool({...})>'
```

See `references/input-modes.md` for the full rationale (heredoc vs `-f` vs
inline, timeouts, stdin generation).

## First inspect should be a planner

For any non-trivial workspace task, the first `hrbr inspect` call should carry
most of the planning weight. Do not run a shallow `Object.keys(result)` probe,
then inspect again, then exec, then inspect again. Write one inspect program
that gathers auth/workspace/source readiness, fans out the likely tool searches,
projects the hits to call + compact schema information, records blockers, and returns
the exact execution packet needed for the next `hrbr exec`.

The core rule: `hrbr.tools.search` should return compact hits with
`tool_id`, `name`, `call`, `input_schema`, `output_schema`, and
`output_paths`. Copy `call` verbatim, use the compact schemas as the default
argument/output contract, and use `output_paths` to pick nested values in exec
code. Read the raw nested contract only when those compact schemas and paths
are insufficient.

Use symbolic task specs, not plugin-specific examples:

```bash
hrbr inspect --stdin <<'EOF'
const task_specs = [
  {
    id: "task_a",
    goal: "<user-facing task A>",
    source: "<likely-source-namespace-A>",
    searches: ["<intent noun phrase>", "<fallback capability phrase>"],
    needs: ["<data or action needed before exec>"],
  },
  {
    id: "task_b",
    goal: "<user-facing task B>",
    source: "<likely-source-namespace-B>",
    searches: ["<intent noun phrase>"],
    needs: ["<data or action needed before exec>"],
  },
];

const settle = async (label, run) => {
  try {
    return { label, ok: true, value: await run() };
  } catch (error) {
    return {
      label,
      ok: false,
      error: String(error?.message ?? error).slice(0, 500),
    };
  }
};

const compact_hit = (hit) => ({
  tool_id: hit.tool_id,
  name: hit.name,
  call: hit.call,
  input_schema: hit.input_schema,
  output_schema: hit.output_schema,
  output_paths: hit.output_paths,
});

const [auth, workspace, sources, ...searches] = await Promise.all([
  settle("auth", () => hrbr.auth.status()),
  settle("workspace", () => hrbr.workspace.current()),
  settle("sources", () => hrbr.sources.list()),
  ...task_specs.flatMap((task) =>
    task.searches.map((query) =>
      settle(task.id + ":" + query, () =>
        hrbr.tools.search({ query, source: task.source, limit: 5 }),
      ),
    ),
  ),
]);

const source_rows = sources.ok
  ? (sources.value.sources ?? []).map((source) => ({
      namespace: source.namespace,
      status: source.status,
    }))
  : [];
const source_by_namespace = Object.fromEntries(
  source_rows.map((source) => [source.namespace, source]),
);

const searches_by_task = searches.reduce((acc, entry) => {
  const task_id = entry.label.split(":")[0];
  (acc[task_id] ??= []).push(entry);
  return acc;
}, {});

const tasks = task_specs.map((task) => {
  const source = source_by_namespace[task.source] ?? null;
  const search_results = searches_by_task[task.id] ?? [];
  const candidates = search_results.flatMap((result) =>
    result.ok ? (result.value.hits ?? []).slice(0, 3).map(compact_hit) : [],
  );
  const blockers = [
    !source ? "source_not_found" : null,
    source && source.status !== "ready" ? "source_" + source.status : null,
    candidates.length === 0 ? "no_candidate_tool" : null,
    ...search_results
      .filter((result) => !result.ok)
      .map((result) => "search_failed:" + result.label),
  ].filter(Boolean);

  return {
    id: task.id,
    goal: task.goal,
    source,
    blockers,
    candidates,
    exec_contract: {
      use: "if blockers is empty, write one hrbr exec from candidates[0].call, input_schema, output_schema, and output_paths",
      return_shape: "<compact object with ids/counts/urls/snippets, not raw plugin JSON>",
    },
  };
});

return {
  auth,
  workspace,
  sources: source_rows,
  tasks,
  next_step:
    "If every task has no blockers, write the exec now from this packet; inspect again only for a specific missing type or error class.",
};
EOF
```

This is a DAG-shaped inspect pass: shared prerequisites are gathered once,
independent searches run concurrently, each branch reports its own blockers,
and the result is the next exec contract. The shape is the point; replace the
symbolic task specs with the real user's task.

## When to use Harbor

Use Harbor for tasks involving connected services such as Linear, Sentry,
Slack, Notion, Gmail, GitHub, Postgres, Stripe, PostHog, Vercel, Cloudflare,
workspace state, remote TypeScript execution, and workspace runtime
primitives.

Use your local coding harness for ordinary repo edits that do not need
workspace tools or Harbor execution.

If you are unsure whether the task needs Harbor, run
`hrbr inspect 'return await hrbr.sources.list()'` first and decide from the
real readiness list — not from your local toolset.

## On any CLI error: research first (top-level rule)

This is the single most important rule when you work through `hrbr`. Any
time the CLI returns an error — `ok: no`, a non-zero exit, a schema
validation failure, an `auth_refresh_required`, a `CredentialReconnectRequired`,
an `EXEC_ERROR`, `INVALID_TOOL_INPUT`, `LOCAL_RUNTIME_UNAVAILABLE`, a missing
namespace, or any other surprise — stop and research before retrying.

Research is `hrbr inspect`, not guessing.

Minimum research per error class:

```txt
   error class                              research step (before any retry)
   ───────────                              ────────────────────────────────
   schema / argument validation             hrbr inspect 'return await hrbr.tools.search({ query, source })' — re-read input_schema/output_schema/output_paths and call
   unknown plugin / namespace / call        hrbr inspect 'return await hrbr.sources.list()' + hrbr.tools.search; copy call verbatim
   source state surprise (not ready)        hrbr inspect 'return await hrbr.sources.list()' — inspect status and surface setup state
   reconnect_required / requires_oauth      hrbr inspect 'return await hrbr.sources.oauthStart({ namespace })' — hand the URL to the user, do not retry
   exec timeout                             inspect the workload size; either split into step.do under a workflow lane or raise --timeout
   sand bridge errors                       hrbr exec 'return { sand: typeof sand, git: typeof sand.git }' — probe before retrying; surface the recovery hint verbatim
   TOON / artifact / wrapper response       no retry; read `references/response-shapes.md` and parse the actual shape
   unfamiliar error string                  hrbr inspect to confirm source + tool, then re-read the surface that produced it
```

Rules:

- Never retry the same call with the same args. Either your inputs change
  after research, or you stop and surface the error to the user.
- Treat the error message as data, not noise. Quote the exact code/string in
  your next inspect query when relevant.
- Re-inspect the tool schema after every schema-shaped failure. Copy the
  returned `call` verbatim; do not patch on intuition.
- For source readiness failures, inspect once, surface the setup state,
  stop. Do not poll.
- For OAuth failures, kick off `oauthStart` and pause for the user; do not
  loop.
- If two research passes do not produce a different next call, escalate to
  the user with the inspect output, not another retry.

See `references/wrong-guesses.md` for the predictable guesses that produce
these errors in the first place, and `references/general-specs.md` for the
full recovery-loop spec.

## Tool rules

- On any CLI error, research with `hrbr inspect` before retrying. This is a
  top-level rule — see the section above for the per-error-class research
  step.
- Before you say a service is unavailable, run
  `hrbr inspect 'return await hrbr.sources.list()'`. Your local toolset is
  not the source of truth; Harbor is.
- Search before calling:
  `hrbr inspect 'return await hrbr.tools.search({ query: "<intended action or likely function name>" })'`.
  Distill the user's request to an action/capability phrase instead of
  pasting the full request verbatim.
- Use the returned `call`, `input_schema`, `output_schema`, and
  `output_paths`. Raw `tool_id` / `name` are lookup metadata, not exec syntax.
- Treat the compact `input_schema` and `output_schema` summaries as the default
  callable truth, and `output_paths` as the default nested-value guide for exec
  result shaping. Read the raw nested contract only when those summaries and
  paths are insufficient for the exact argument or output path.
- In `hrbr exec`, plugin calls return structured objects in the common case
  but the runtime also surfaces three other shapes — artifact envelopes,
  TOON-encoded strings, and LLM-wrapper text. Detect the shape before
  reading fields. See `references/response-shapes.md`.
- If a source is not ready, treat it as setup state, not a retryable tool
  failure.
- Only active tools are callable.
- For `hrbr exec`, reference plugin namespaces in code; do not invent plugin
  flags.
- Workspace selection is inspect state: use
  `hrbr.workspace.switch({ target })`, not `--workspace-id`.
- Output shape is the returned value. Do not ask for `--json`, `--human`, or
  `--full`; shape the return object in inspect/exec code. The CLI envelope
  itself is TOON — do not pipe to `jq`.
- Independent plugin/runtime calls inside a single inspect or exec must run
  under `Promise.all` (or `Promise.allSettled` when one source may fail).
  Sequential `await` for independent work is the most common waste pattern.
  See `references/promises-and-parallelism.md`.
- App and job lifecycle authoring is file-based: use one top-level
  `deployApp({...})` or `defineJob({...})`. Do not invent
  `hrbr.apps.create(...)`, `hrbr.apps.publish(...)`, `hrbr.jobs.create(...)`,
  or `hrbr.jobs.publish(...)` inside runtime code.
- Plugin globals are called directly (for example,
  `<pluginRuntimeVar>.<toolFunction>({...})`).
  There is no `hrbr.tools.call(namespace, tool, input)`; that is a common
  wrong guess. See `references/wrong-guesses.md`.

## Topic skills

Load a narrower skill when the task matches one of these surfaces. After
loading the narrower skill, open only the referenced support file that
matches the immediate task; do not bulk-load every reference or template.

```txt
surface      skill            use for
plugins      harbor-plugins   discover tools, source readiness, OAuth/connect setup state
exec         harbor-exec      one-off remote TypeScript with traced runs and authoring files
jobs         harbor-jobs      reusable immutable job versions via defineJob({...}); load orbit for runtime primitives used inside run(input)
apps         harbor-apps      live routed app versions and stable URLs via deployApp({...}); load orbit for app route primitive rules
orbit        harbor-orbit     runtime primitives: storage, cache, db, ai, tools, jobs, and app-local state
```

Relevant skills: `harbor-plugins`, `harbor-exec`, `harbor-jobs`,
`harbor-apps`, `harbor-orbit`.

## Usage references

These describe the `hrbr` usage layer itself — runtime envelopes,
parallelism discipline, input modes, and predictable wrong guesses —
independent of any specific plugin. Load them when the task is shaped at the
hrbr level.

```txt
need                                       file
project output / parallelize / log (start) references/efficient-exec.md
shape of plugin/runtime returns            references/response-shapes.md
parallelism rules across inspect/exec      references/promises-and-parallelism.md
which input mode to use (heredoc/-f/inline)references/input-modes.md
common wrong guesses at the hrbr layer     references/wrong-guesses.md
overarching usage spec (principles)        references/general-specs.md
```

## Support file routing

When your task needs more detail than this root skill provides, first load
the named skill, then read or copy the listed file from inside that skill's
directory:

```txt
task need                       skill          file inside that skill
tool discovery / schemas         harbor-exec    references/tool-discovery-and-inspect.md
inspect-vs-exec boundaries       harbor-exec    references/runtime-boundaries.md
plugin result normalization      harbor-exec    references/result-shaping.md
pagination / fan-out exec code   harbor-exec    references/exec-patterns.md
multi-plugin workflow recipes    harbor-exec    references/combination-recipes.md
exec vs job vs app choice        harbor-exec    references/exec-job-app-selection.md
one-off plugin exec              harbor-exec    assets/templates/exec-plugin-call.ts
runtime smoke probe              harbor-exec    assets/templates/exec-runtime-probe.ts
call promoted job from exec      harbor-exec    assets/templates/exec-call-job.ts
compact result normalizer        harbor-exec    assets/templates/exec-normalize-results.ts
local inspect research snippet   harbor-exec    assets/templates/exec-multi-inspect.js
job authoring/runtime contract   harbor-jobs    references/job-runtime-contract.md
job plus app composition         harbor-jobs    references/job-app-composition.md
plugin-backed job template       harbor-jobs    assets/templates/plugin-search-job.ts
storage report job template      harbor-jobs    assets/templates/storage-report-job.ts
app backing job template         harbor-jobs    assets/templates/app-backed-job.ts
app routes / URL behavior        harbor-apps    references/app-route-patterns.md
app plus job/plugin recipes      harbor-apps    references/app-job-plugin-combinations.md
raw HTML app template            harbor-apps    assets/templates/raw-html-app.ts
stateful HTML app template       harbor-apps    assets/templates/stateful-html-app.ts
job-backed app template          harbor-apps    assets/templates/job-backed-plugin-app.ts
runtime primitive rules          harbor-orbit   SKILL.md
```

When you use Harbor through `cli-mcp`, remember that `hrbr serve mcp`
exposes only two protocol tools: `inspect` and `exec`. `inspect` is the
control-plane method host for `hrbr.auth`, `hrbr.workspace`, `hrbr.sources`,
`hrbr.tools`, `hrbr.jobs`, and `hrbr.apps`; `exec` is the task-backed Harbor
Cloud execution path. Apps and jobs follow the same split: author through
`hrbr exec -f`, inspect through control surfaces, then use Orbit primitives
inside runtime code. Do not add runtime, worker, or placement fields to
authoring files; Harbor chooses the execution plan internally.