Commit Graph

23 Commits

Author SHA1 Message Date
Auree Aubert bf23619313 Stop documenting the first-party enrichment provider (#122)
* Stop documenting the first-party enrichment provider

Its playbook, its 21 priced actions, and every recipe step that led with it
are removed. Each one now leads with the rung the pack already named as its
fallback, so nothing here is invented — the replacements were sitting in
stage-action-map.md and alternatives.md the whole time.

The substitutions:

    firmographics   aiArk.enrichCompany (0.01) -> companyEnrich (0.25) -> waterfall (1)
    person          aiArk.enrichPerson (0.1) -> waterfall.enrichContact (2)
    funding         enrichCrm.getFunding (1), now the catalog's only one
    technographics  builtwith.getDomainSummary (free) -> enrichDomain (1)
    dedupe          free storage query against the workspace's own models

That last line is the one worth reading twice. The match actions existed to
mint an id the other actions required; every replacement keys on the domain
or a LinkedIn URL, so a step that cost 0.5/record now costs nothing.

The event feed has no replacement and the recipes say so. fetchBusinessEvents
took a timestamp_from and nothing else in the catalog does, so funding-watch,
re-engagement and lost-deal-revival are rewritten as a diff: re-pull, compare
against the stored round date, keep what moved. Cadence becomes the only cost
dial, which is why funding-watch's default drops from daily to weekly.

Every credit budget was recomputed rather than relabelled:

    build-tam        2,955 -> 2,210   (500 companies + 1,500 contacts)
    prospecting P2     821 ->   107   (200 verified prospects)
    icp-discovery      602 ->   204   (200 deals)
    context bootstrap    3 ->     1

Catalog counts now follow the generated data file rather than the older
hand-kept figure: 136 integrations, 176 credits-based actions of 513.

Also bumps the CLI pin 1.0.66 -> 1.0.78.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019wkwbhrEoE5KW9A1CKBmcA

* Fix the three generated artifacts the removal invalidated

CI caught what the local lint could not: skills-lint.mjs is only one of five
checks, and the other four all had something to say.

llms.txt was stale — it is generated from the skill descriptions, and
cargo-gtm's dropped a provider from its roster.

The codex package build enforces override version == upstream version, so
bumping only .github/package-overrides/cargo-quickstart.SKILL.md to 1.0.3 broke
it. The guard exists to stop the override silently shipping last month's demo,
so the fix is to bump both, not to revert the override.

That build also patches literal source strings at package time, and one anchor
ran through a line this branch rewrote:

    snitcher.searchSessions (0 cred) -> cargo.matchProspect
    snitcher.searchSessions (0 cred) -> aiArk.reverseLookup (0.05)

The edit strips the visitor-de-anonymisation path from the packaged skill, so a
silent miss would have shipped it. Re-anchored, and every other PACKAGE_EDITS
find string verified against the tree — 1 broken, now 0.

Also drops the packaged cargo-connection description from 138 integrations to
136, matching the repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019wkwbhrEoE5KW9A1CKBmcA

* Say where revenue went, and that employee ratings went nowhere

Removing the provider took two data points off the map without replacing the
map entry, which is the failure mode this recipe's own feasibility gate exists
to prevent: an attribute that quietly stops being answerable reads the same as
one nobody asked for.

They are not the same case, so they get different rows.

Revenue and NAICS survive. companyEnrich.enrichByDomain (0.25) returns them in
the same call as firmographics, employees, funding and socials — so it fills
several rows at once and the row says that, because the per-account arithmetic
in step 4 gets it wrong otherwise.

Employee ratings have no successor at any price. Nothing credits-based in the
catalog returns Glassdoor-style employer reputation. The row says none, and
says to report it as a research note rather than substituting a scrape that
reads like the real thing — which is the recipe's stated position on valuable
attributes with no obtainable source, applied to itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019wkwbhrEoE5KW9A1CKBmcA

* Fix the data-flow breaks the provider swap left behind

Bugbot found eight, all real, and verifying them turned up five more. The
pattern in every case is the same: I replaced an action but left the code that
consumed its output reading the old response shape, so the step still runs,
still bills, and quietly returns nothing.

Response-shape breaks:

  funding-watch      Pattern A still filtered funding_rounds[].announced_date
                     while Pattern B read lastFundingDate. The 90-day select
                     matched nothing, so the primary path returned an empty
                     list after a paid pull.
  re-engagement      Step 5 read .results[] and .events[] from a file step 3
                     now writes as a bare array. Funding branch always empty.
  lost-deal-revival  Step 4 read .events[0] and expected a deal_id that
                     getFunding does not return. Step 3b now carries deal_id
                     through, which is the only place it exists.

Wrong-source breaks:

  re-engagement      The diff read last_funding_round_at off the Contacts
                     segment. It is a Companies column, so the stored date was
                     always empty and every account re-flagged every week. Now
                     queries the Companies model, and fans the company-level
                     signal back out to contacts, since step 5 unions on email.
  build-tam          Step 4 keyed searchLeads on .linkedinId from the
                     aiArk.enrichCompany output. No enrichment action returns a
                     Sales Navigator accountId. Now carried from the sourcing
                     step, with an explicit gate for rows that have none.
  guide              snitcher.searchSessions identifies a COMPANY; I had
                     chained it into aiArk.reverseLookup, which needs an email
                     or phone. Its own playbook says to route identified
                     domains through the enrich chain.

Budget break:

  prospecting        P2 step 5 ran FullEnrich over every lead while the budget
                     quoted the residue. 200 credits against 60. P1 had the
                     gate; P2 did not.

Pre-existing, fixed because the fixes above sit inside them:

  icp-discovery      [.[] | {domain}] over storage query execute output, which
                     is {rows: [...]}. Enriched one null domain, not 200.
  jq inputs[N]       `inputs` is a generator, not an array, so the union
                     expressions in re-engagement and lost-deal-revival errored
                     out. Slurped to [inputs] as $in.

Every rewritten jq expression was run against fixtures rather than eyeballed.

Also five provider mentions the sweep missed, because they wrote it as bare
`cargo` with a price rather than cargo.actionName: societeInfo, snitcher,
enrichCrm, linkedin, oceanio, apolloio, rocketreach.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019wkwbhrEoE5KW9A1CKBmcA

* Make funding-watch Pattern B stand on its own

It re-pulled two hardcoded example domains and then diffed them against
/tmp/targets.json, a Pattern A artifact. Run Pattern B alone and the slurp
fails; run both and it compares a fresh pull of one set against stored dates
for a different set, so the "new round" filter cannot fire correctly either way.

Both sides of a diff have to come from the same query. Now they do: one storage
read supplies the domains to pull *and* the stored dates to compare against, so
the sets align by construction and the pattern has no cross-dependency.

Also states what a missing stored date means — it compares against "" and always
passes, which is right on a first run and is exactly why the write-back step is
not optional. Without it every run is a first run and the watch re-flags the
whole segment forever.

Verified against fixtures: an account whose round moved is kept, one whose
stored date already matches is dropped, one with no stored date passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019wkwbhrEoE5KW9A1CKBmcA

* Route the questions people ask but the map never answered (#124)

Measured instead of guessed. Of the 176 credits-based actions, 148 were
reachable from a routing surface — the stage map, the phase guides,
alternatives.md, a recipe. The other 28 were reachable only from their own
provider playbook, which is findable if you already know which provider to
open. That is the wrong way round.

The worst of it: headcount had no section at all. It is the most-asked company
attribute and the first row of the custom-datapoints feasibility gate, and not
one of the four salesNavigator.find* actions appeared anywhere in the stage map.
New section carries all four, plus the only historical series
(companyEnrich.getWorkforce), plus linkedin.findCustomHeadcount for the roles
the fixed buckets don't have, plus the companyId prerequisite that quietly
turns a 0.25 action into 0.30 on a list that arrived without one.

"Who works at this domain" was not a documented question either. It is not the
same as searching a population by title, and it has two answers at very
different prices. New section, with the trap stated: looping hunter.searchDomain
to build a list is the pitfall its own playbook warns about.

Also adds rocketreach, mixrank and societeInfo to Enrich/Sourcing —
societeInfo reaches French registry data (NAF code, collective agreement,
filed accounts) that nothing else in the catalog does — and gives Hiring intent
a second index at the same price.

154/176 now. The remaining 22 stay unrouted on purpose, and the CHANGELOG says
why for each: the four LinkedIn engagement actions would undercut the
acceptable-use refusal to batch-blast them, three natives belong to other
skills, searchLeadsLegacy costs 300x its replacement, and the rest are finer
grain on an already-routed surface rather than separate questions.

One thing the build caught: adding aiArk.analyzePersonality re-introduced a
term the codex package deliberately strips, since the directory rejected
person-level inference. Kept for the channels that carry it, with matching
PACKAGE_EDITS so the package still removes it. Dropped the forager rung rather
than add machinery for a last-resort action priced 20x above its own chain.
All 67 anchors verified.


Claude-Session: https://claude.ai/code/session_019wkwbhrEoE5KW9A1CKBmcA

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 16:40:18 -07:00
Auree Aubert be823b3fcb Generate the cost table, and stop omitting the native actions (#111)
* Generate the cost table, and stop omitting the native actions

The credits price list was hand-kept. The first attempt to generate it read
`connection integration list`, which is connector-only — so its own header
claimed "every credits-based action in the cargo catalog" while silently
omitting every billed native action:

    sendEmail    0.1   fixed
    modelAsk     0.5   fixed
    fileSearch   0.02 / 1k tokens

`sendEmail` is the whole outbound send path, priced at 0.1 in the mailbox
skill, and it was absent from the canonical price list.

It is now generated by `cargo-ai orchestration action cost-table --raw`
(getcargohq/cargo#5641), which lives beside `action list` and `action execute`
— the surface that spans connector, native, tool and agent. 197 credits-based
actions of the 534 the catalog exposes, up from the 145 the hand-kept table
claimed and the 194 a connector-only generator found.

What the hand-kept table got wrong, now that a generated one can be diffed
against it: 145 actions where there are 197, four prices quoted from memory,
and cost columns that concatenated every tier into an unreadable comma list —
`openAi.instruct` rendered 28 numbers. Per-config pricing moves to its own
section.

Two providers gain playbooks rather than staying unlisted: brightData
(Instagram/TikTok/Facebook/YouTube by URL, the catalog's only non-LinkedIn,
non-X social coverage) and proxycurl. An undocumented provider still shows up
in the cost table, so the acceptable-use framing is better stated than implied;
brightData.md opens with the consumer-targeting refusal that gates it. Only
openRouter remains without a playbook, and it has no credits-based actions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TBfa73ishJJRe4pccyhTAa

* Pin the CLI at 1.0.66, and fix five commands that never existed (#117)

* Pin the CLI at 1.0.66, and fix five commands that never existed

The pin had drifted 19 releases behind npm, and the bundle now teaches
`orchestration action list`, which only exists in 1.0.66. The pin is what the
session-start refresh installs, so leaving it meant an agent following these
skills would reach for a command its CLI did not have.

Checked before moving it, not after: the CLI's whole command surface was diffed
across 1.0.47..1.0.66. The only removals are three billing commands no skill
documents; `update-payment-method`, which the skills do use, replaced one of
them and is still there. Everything else in the range is additive.

That verification then turned up five documented commands that do not exist —
and all five were already broken at the old pin, so this is old debt the bump
surfaced rather than caused:

- `orchestration draft-release {get,update,deploy}` (18 sites) is really
  `orchestration release {get-draft,update-draft,deploy-draft}`.
- `ai template get <slug>` (6 sites) does not exist. `ai template list` already
  returns each template in full, so the fix is to filter that response.
- `action execute --record` (5 sites) is `--data`.
- `storage model list --dataset-uuid` (4 sites): the command takes no options
  at all, and every model carries `datasetUuid`, so filter client-side.
- `storage model get-ddl --model-uuid <uuid>` takes the uuid positionally.

Also folds in the skills half of getcargohq/cargo#5740: `get-output-schema`
takes an action plus an optional `--data` rather than demanding a `config`, so
the object `action list` returns now pastes into all four action commands
unchanged. The gotcha row about the 400 becomes a note about `--data`, which is
the part worth knowing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TBfa73ishJJRe4pccyhTAa

* Mark --data on get-output-schema as CLI >= 1.0.67

getcargohq/cargo#5740 ships --data in CLI 1.0.67, one above the pin this PR
sets. The config-less --action works on the pinned 1.0.66 as soon as that
backend deploys; --data needs the next release, so it carries a version marker
like every other post-pin flag in the bundle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Document the execution charge the pack said did not exist (#118)

Every node execution bills 0.01 credits (1 per 100) — structural natives
and errored executions included. Three places in the pack said otherwise,
and they reinforced each other:

- troubleshooting.md described executions[].creditsUsedCount as "native
  nodes are zero", accurate about the field but read as pricing;
  play-optimize-credits.md restated it and cited it as the authority.
- The generated cost table prices actions, and structural natives are
  excluded from `action list` entirely, so branch/filter/switch/variables
  appear in no price list here — read as free.
- cargo/SKILL.md said "Everything else is free" outright.

Verified live: `billing usage get-metrics --unit orchestration.executions`
returns success/error as execution counts, reconciling row-for-row with
`SELECT execution_status, count() FROM spans`. spans.execution_credits_used_count
reads 0 on natives that billed, so the charge is attributed to no node and
per-node profiling under-counts every graph.

Two adjacent bugs fixed while verifying:

- `--unit credits` was documented and 400s. The flag takes exactly
  billing.credits | orchestration.executions | storage.records, and with
  no --unit all three interleave in one items[] array with different
  meanings for `count`.
- The example slugs `enrichment` / `ai_message` were invented (real:
  integration.<slug>.action.<action>, native.<action>, success/error,
  insert), as was `.totalUsage` — the response has one key, `metrics`.

Estimates now carry two terms: (provider cost × records) + (nodes ×
records / 100), measured on the pilot rather than inferred from the graph.

cargo-billing 1.1.0 -> 2.0.0 (agents relearn --unit and the slug shapes),
cargo-orchestration 1.11.0, cargo-gtm 1.19.0, cargo-diagnostics 1.4.0,
cargo 1.24.0 + the three plugin manifests.

Still upstream: `orchestration action cost-table` generates the header
"The other 337 actions ... cost no credits" — unfixable from this repo.


Claude-Session: https://claude.ai/code/session_01AbUvyTQMDABiexZgQCsGoe

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Source the cost table from action list, not a bespoke generator

`orchestration action cost-table` is being removed from the CLI: it read
`action list` twice and added no data, and the markdown it generated was
its only consumer. `action list` already returns a `credits` array on every
billed action, so the table is regenerated from that directly.

Regeneration is now two calls (`--kind connector`, `--kind native`) plus
the three cost shapes documented in the header, so a reader can render the
table without the generator's price-formatting logic. Notably `fixedCost`
adds to the metered rate rather than replacing it — the trap that made a
0.175 + 0.025/item search read as 0.025.

Two fixes that fall out of owning the header again:

- The opening line claimed the other 337 actions "cost no credits". True
  of provider price, false of anything that runs in a workflow. It was
  flagged as unfixable while the CLI generated it; it now states no
  *provider* price and points at the execution charge.
- `stage-action-map.md` still carried 194/310 from the connector-only
  generation, against 197/337 everywhere else.

Co-Authored-By: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
Co-Authored-By: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>

Co-authored-by: Auree Aubert <aureeaubert@users.noreply.github.com>

* Fix PACKAGE_EDITS patterns to match regenerated cost table format

Co-authored-by: Auree Aubert <aureeaubert@users.noreply.github.com>

* Add PACKAGE_EDITS rule to remove extractEventAttendees from stage-action-map.md

Co-authored-by: Auree Aubert <aureeaubert@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>
Co-authored-by: Auree Aubert <aureeaubert@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-09-01 16:28:25 -07:00
Auree Aubert aead4ddc20 Search for actions, and config stopped shouting (#115)
* Cargo has a first-party MCP, and `config` stopped shouting

Tracks the platform-MCP branch in the CLI repo: a first-party MCP server at
`/mcp`, a new `orchestration action list`, and `config` split off the top-level
action type. Two of those three contradict sentences this bundle stated as fact.

- The router, `cargo-ai`, and the glossary all said there is no first-party
  Cargo MCP server. There is one now, at `mcp.getcargo.io/mcp`, with a fixed
  operate-the-workspace toolset. Both surfaces — platform and curated — are
  documented, and the routing rule they exist for is unchanged.
- `cargo-ai mcp` with no `--server` now bridges the platform MCP rather than
  resolving "the workspace's only MCP server". Same command, different server
  by CLI version, so it is in the gotchas table.
- New `orchestration action list <keywords>`: free, searches connector, native,
  tool, and agent actions in one call, and returns a ready `action` object with
  `connectorUuid` resolved plus the action's credit costs.
- `config` is optional on execute/execute-batch and still required on nodes,
  alerts, play health actions, and get-output-schema. The dangerous half is the
  deleted guard: misplaced inputs are now dropped silently and the action runs
  with none.
- Nine documents were telling agents to do exactly that. The four LLM playbooks,
  the prompt-library index, and three GTM recipes put `model`/`advancedSettings`/
  `output` (or `customerId`, `spreadsheetId`) in the action's `config`; all now
  put inputs in `--data`/`--records`. The ads recipe also loses two
  `execute-batch --model-uuid --filter` calls against flags that do not exist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TBfa73ishJJRe4pccyhTAa

Co-authored-by: Auree Aubert <aureeaubert@users.noreply.github.com>

* Drop `config` from every top-level action, and lead with action search

The platform-MCP work shipped while this PR was open — CLI 1.0.66 is on npm and
the platform MCP answers at mcp.getcargo.io/mcp — so the hedging this branch was
written with ("newer backends accept…", "stays valid on any version") is now
just the rule.

- `config` is gone from 272 execute / execute-batch examples across 79 files.
  That is the shape `action list` returns, so a search result pastes straight
  into a run. Nodes, alert `--actions`, play `healthAlertActions`, and agent /
  MCP-server `--actions` keep theirs; the strip was scoped around them.
- `get-output-schema` is the exception and still requires `config` — verified
  live: the action object from `action list` fails 400 at `action.config`. The
  CLI's own help examples for that command omit it and therefore all 400. It is
  now a gotcha row, and those examples keep `"config": {}` on purpose.
- `connection action search` shipped in 1.0.36 and no skill had ever mentioned
  it. cargo-connection now opens with both searches and a table for choosing:
  `action list` by default, `action search` for `--credits-only` and
  `--category`. `integration list` / `integration get` drop to what they are
  good at — the integration, and the schema of an action already picked.
- Removed a phantom command: `connection integration list-actions` has never
  existed, and cost-discipline.md was recommending it as a free lookup.
- cargo-mcp gains the one config rule that surface needs: pass the action object
  as-is, never with a config — `get_action_schema` supplies the empty one, which
  makes MCP safer here than the CLI.

Verified against the live workspace at 1.0.66: a config-less `action execute`
returns status success at 0 credits, and `get-output-schema` without `config`
returns 400.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TBfa73ishJJRe4pccyhTAa

* Fix the strip's leftovers, and bump the router so it actually ships

An audit of the 272-example strip turned up one real defect and a set of
phrasings the strip left behind, plus a packaging bug that would have made the
router's half of this PR a no-op.

- `cargo/SKILL.md` was still at 1.22.0, the version main already publishes, so
  its 14 changed lines — including both new critical rules and both new gotcha
  rows — would have been skipped at publish. Bumped to 1.23.0, plugin manifests
  follow.
- `guides/writing-outreach.md` still declared the action shape as
  `integrationSlug + actionSlug + config: {}` directly above an example whose
  config had been stripped. Every sibling "Action shape" line had been rewritten;
  this one was missed.
- Seven places said the action's config "is empty" or "stays empty" where the
  rule is now that there is no config key at all. Normalised, including the
  deliberate contrast in cargo-observability, where alert actions do require one.
- The report template in cargo-workspace-management modelled the old shape in a
  copyable `action execute` string, and cited `config.portalId required` as the
  symptom. Both corrected.
- perplexity's `searchContextSize` is an input, not a "config cost lever" — the
  same rewrite had moved it into `advancedSettings` in `--records`.

Verified clean afterwards: no execute example retains a config, no
get-output-schema example lost one, no node/alert/agent/mcp-server action lost
one, and every JSON block still parses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TBfa73ishJJRe4pccyhTAa

---------

Co-authored-by: Auree Aubert <aureeaubert@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 16:42:39 -07:00
replicas-connector[bot] 5680754126 Add cargo-mcp, and correct the router on the hosted MCP server (#116)
mcp.getcargo.io/mcp is live and serves thirteen platform tools plus
whatever a workspace published with defineMcpServer. Nothing in the
bundle covered it, and cargo/SKILL.md asserted "There is no first-party
'Cargo MCP server'" - true when written, wrong now, and the one claim an
agent would act on.

- cargo-mcp/SKILL.md (1.0.0): connect, the discover to price to execute
  to poll spine, and the routing table for MCP tool vs cargo-ai.
- cargo/SKILL.md (1.22.0): the MCP section becomes a three-way
  disambiguation. Hosted server, workspace server served over stdio by
  cargo-ai mcp, and an MCP client attached to a Cargo agent are three
  things sharing a name and sharing no answers.
- evals/routing.jsonl: "How do I use Cargo from ChatGPT?" now expects
  cargo-mcp; three cases added for the new surface.
- Counts, catalogs, plugin manifests, SKILL_DIRS, llms.txt, metadata.

skills-lint, routing-eval, sync-trigger-slugs and the cargo-gtm script
fixtures all pass.

Co-authored-by: Cargo FSD <fsd@getcargo.io>
Co-authored-by: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
Co-authored-by: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>
2026-08-27 10:28:58 -07:00
Auree Aubert e61f67f105 New skill: cargo-mailbox-management, and Cargo sends now (#113)
`mailboxManagement` landed in the CLI (cargo#5493, plus warm-up stats in
cargo#5646) and nothing in the bundle covered it. It is the first domain
that makes Cargo own the inbox, so the gap was not one more capability
skill — it was a skill whose absence let an agent send mail with no idea
what it cost or what governed it.

- New `cargo-mailbox-management` (the fifteenth capability skill): six
  command groups, the send that is deliberately not in them, and four
  references — the ramp arithmetic and fleet sizing, the `sendEmail`
  action, response shapes, troubleshooting — plus eight worked recipes.

- The two facts an agent gets wrong without it. A mailbox is a recurring
  monthly credit charge (100-160 per mailbox per month), not a per-record
  one, and `mailbox remove` is the only way to stop it. And volume is a
  ramp, not a setting: 5/day to 40/day over 45 days from `warmupStartedAt`,
  pinned at 5/day forever if warm-up never started, reset to day 0 by
  `stop-warmup`, and `dailySendLimit` can only tighten it.

- `cargo-gtm`'s acceptable use said the pack never sends. That paragraph
  is the substance of the plugin-directory remedy, and mailboxManagement
  made it false. Section 1 now says what is true, section 6 names the ramp
  and calls spreading one campaign across extra mailboxes what it is, and
  section 7 cross-references the new skill.

- `cargo-cdk` gained `defineMailbox` and `defineDomain` in the same release
  and had documented neither. Both are now in the builder table with the
  create-only, monthly-fee and whole-zone rules that bite.

- Known gap, filed rather than papered over: `mailbox create --domain-uuid`
  is required and no command lists sending domains.

Counts move 17 to 18 across the router, README, AGENTS, CONTRIBUTING and
the manifests. Seven new routing cases, two of them hard-tier negatives
pinning the boundary the word "email" blurs. Core tier 93/93; lint,
metadata, llms.txt, trigger slugs, cookbooks and hooks all clean.


Claude-Session: https://claude.ai/code/session_01TF1ZRweJqqnPeGbVmUeGkY

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 21:47:30 -07:00
Auree Aubert 6683645b00 CI: run the HOL AI plugin scanner (#102)
* CI: run the HOL AI plugin scanner

hashgraph-online/awesome-ai-plugins requires every listed plugin's source
repo to invoke `ai-plugin-scanner-action` on push/pull_request before the
listing PR can merge (getcargohq/cargo-skills#83 → their PR #72).

Pinned to a full commit SHA instead of the floating `v1` tag, with a
read-only token and no secrets in scope, so a third-party action in our
workflow surface can't reach anything it shouldn't.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CI: publish the scanner report when the scan fails

The action's built-in summary step is `if: success()`, so a failing scan
prints counts and no findings. Render markdown to a file and cat it into
the step summary unconditionally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CI: tee the scanner report to the job log as well

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CI: clear the plugin scanner's 80-point / no-high gate

The scanner job this PR added failed on its first run: 71/100 against a
`min_score` of 80, one critical finding and one high. The awesome-ai-plugins
contribution gate re-scans this repo with the same two thresholds, so the
findings had to be fixed rather than configured around. Now 92/100 (A), zero
critical, zero high.

- Pin every `uses:` in .github/workflows/ to a full commit SHA, and add
  .github/dependabot.yml so the pins move weekly instead of never.
- Add SECURITY.md: private reporting via the Security tab, and an explicit
  boundary between this bundle and the cargo-ai CLI / Cargo platform.
- Stop inlining `curl … install.sh` in cargo/SKILL.md. The command now lives
  only in README.md, where a human runs it; the router keeps the guidance
  (never run it on the user's behalf) and points there.
- Use execFileSync, not execSync, for `npm root -g` in the QA script library —
  no shell to interpret a command string. Windows gets the npm.cmd shim.
- Fill in repository/license/keywords in .codex-plugin/plugin.json, a category
  on the .agents marketplace entry, and add .codexignore.
- Set write_step_summary: false on the scanner action and publish the report
  ourselves, so it lands once whether the gate passes or fails.

Two medium findings are left standing and documented in CHANGELOG.md: the
`anthropic` provider slug in cargo-gtm's description (correct, and CI-synced
with the CLI) and `fetch(` in the run-output downloader (the point of the
function). Neither is a gate failure.

Co-Authored-By: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
Co-Authored-By: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>
Co-authored-by: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
2026-08-14 19:00:18 -07:00
Auree Aubert 8b36982db4 cargo-orchestration 1.7.0: draw the workflow (#100)
* cargo-orchestration 1.7.0: draw the workflow

Nothing in the bundle told an agent to show a node graph as a picture. The
only nudge was one line in interaction.md — "a compact node-flow sketch or
table beats prose" — which left the format to the model, so the plan gate
asked users to approve a graph they had to reconstruct from prose. Every
surface these skills print into renders Mermaid.

- references/node-diagram.md: when to draw one (plan gate, "what does this
  play do", trace reporting), when not to, the shape/edge mapping per node
  kind, cost marking, and the rules that make a diagram true.
- scripts/workflow-to-mermaid.ts: zero-dep TS (Node >= 22.18, 6 fixture
  cases) taking release get / get-draft / template get / an ad-hoc run get /
  a raw node array. Flags: --title, --direction, --paid, --highlight.
- Two doc defects found against a live workspace: release get's documented
  nodes[] was a {uuid, slug, name} summary of the full graph it actually
  returns, and run get carries nodes XOR releaseUuid depending on whether
  the run came from a deployed release.
- Node slugs are not unique within a release (six `variables` in one shipped
  waterfall), so anything walking the graph keys on uuid. Recorded as router
  gotchas, with the knock-on for {{nodes.<slug>...}} ambiguity.
- Call sites wired: plan gate and presenting defaults, router rules,
  nodes.md, the diagnostics trace runbook, and cdk plan.
- CI runs fixtures for every cargo-*/scripts/*.ts, not only cargo-gtm's.

Verified against CLI 1.0.52: every diagram in the reference is script output
from a real release, and each parses under mermaid 11.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Move the diagram generator into the CLI

`cargo-ai orchestration node diagram` replaces the skill script the reference
was prototyped against. It belongs next to `node validate` — same input, same
free/runs-nothing character — and the sequence now reads validate → diagram →
deploy without leaving the CLI.

- scripts/workflow-to-mermaid.ts and its fixtures deleted; the CI fixture glob
  goes back to cargo-gtm/scripts/*.ts.
- node-diagram.md documents the command: one source per call (--nodes / --file
  / --workflow-uuid / --draft / --release-uuid / --run-uuid), the JSON shape,
  and reading `warnings` out loud rather than drawing over a broken graph.
- --run-uuid absorbs the nodes-XOR-releaseUuid split, so the diagnostics
  runbook no longer has to explain which kind of run it was handed.
- Requires CLI >= 1.0.54 (getcargohq/cargo#5580); the cli-version pin moves in
  the follow-up that ships with that release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh skill-metadata content hashes after rebase

The rebase onto main took both sides of the version conflicts in cargo/SKILL.md
and cargo-orchestration/SKILL.md (this branch's 1.20.0 / 1.7.0, which stay ahead
of main's 1.19.2 / 1.6.3), which left the recorded contentHash in the two
skill-metadata.json files stale. Regenerated with skills-metadata.mjs --write;
skills-lint is back to 0 errors.

Co-Authored-By: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
Co-Authored-By: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>
Co-authored-by: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
2026-08-14 18:06:35 -07:00
Auree Aubert 7f5582595a cargo-gtm 1.14.0: design custom datapoints, don't just fill them (#99)
* cargo-gtm 1.14.0: design custom datapoints, don't just fill them

Every recipe in the pack assumed the field was already named:
source-planning sources one field you asked for, icp-discovery needs
closed deals to mine. Nothing covered the blank page — "what should we
even be collecting?" — which is where the GTM conversation starts, and
which a model will answer with ten plausible attributes, four of which
have no obtainable source at any price.

New recipes/custom-datapoints.md: research the seller from fetched pages
rather than model recall, draft candidates against the discriminator
test, gate each one on a real catalog action + credit cost + probed hit
rate, present a costed shortlist with the cut list and the full fan-out
arithmetic, then wire the survivors into columns, scoring, a segment,
and a refresh cadence. The gate is the point — a candidate with no
action slug is a research note, and saying so beats padding to ten.

Attribute vs signal, mechanically: in Cargo a live signal is a tracked
column diff, so the recipe ends by putting the signal-bearing attributes
into --tracking-column-slugs at segment creation and reading the delta
feed — the step whose omission leaves updatedRecordsCount at 0 forever.

Two prompts in the data-extraction shard: custom-attribute-extraction
(one defined attribute from page text, with a confidence band and a
verbatim evidence quote — Unknown is a first-class answer, because an
unsupported value in a scoring column is a decision made on noise) and
technology-adoption-state (mixed evidence to individual_usage …
company_standard, with a hard rule that one job posting never
establishes company-wide adoption).

First-party fields — product usage, activation, billing, visitor
sessions — are called out as structurally unavailable for net-new
accounts and routed to account-expansion instead of quietly ranking
customers above prospects.

Routed from the router table, the cargo-gtm recipe table, the README and
llms.txt; cargo 1.19.1 and the three plugin manifests follow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Fix Bugbot findings on the custom-datapoints recipe

Four issues, all verified against the pack's own docs rather than taken
at face value.

- **`linkup.instruct` payload was invalid.** Step 2 passed `prompt` and
  no `depth`. Per `provider-playbooks/linkup.md`, `instruct` takes `q`
  plus a required `depth`, and a sourced answer needs
  `outputType: "sourcedAnswer"`. As written the seller-research call —
  the step the recipe says determines everything after it — fails.
- **`change fetch --change-uuid` does not exist.** The flag is `--uuid`
  (the change UUID from `change list`), as `cargo-segmentation` documents
  in three places. The delta read 400s as written.
- **The signal-watch filter matched every record.** `conditions` sat
  beside an empty `groups`, and `{"conjonction":"and","groups":[]}` is
  the match-everything filter — so tracking and refresh spend would have
  run over the whole model instead of the ICP. Conditions now nest inside
  a group, in the `{kind, columnSlug, operator, values}` shape the sibling
  recipes use.
- **The `estimated` confidence band was unreachable.** The prompt listed
  it, then told the model to return `unknown` for anything below the
  `inferred` bar — dropping exactly the calculated values Step 7 tells
  agents to store with a companion confidence column. `confirmed`,
  `inferred`, and `estimated` are now all reportable, with `estimated`
  scoped to arithmetic on figures actually in the text.

Also rebased onto main, which shipped router 1.19.1 in #97, so this moves
to **1.19.2** across `cargo/SKILL.md` and the three plugin manifests.

skills-lint 0/0 · skills-metadata --write clean · sync-trigger-slugs
--check up to date · routing-eval 81/81 core, all gating evals pass.

Co-Authored-By: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
Co-Authored-By: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>

* Fix second Bugbot round: ID prereqs, billing shape, shortlist math, snitcher

Four more findings on the recipe's pre-existing content. All four hold up
against the provider playbooks.

- **`salesNavigator.find*` ID prerequisite was missing.** All four `find*`
  actions key on a LinkedIn `companyId`, not a domain (playbook, Inputs
  column). The recipe carefully amortized `cargo.matchBusiness` and said
  nothing about the equivalent fee here, so probes fail on domain-only
  lists and every shortlist under-quotes. Now a named cost mechanic
  alongside `matchBusiness`, at 0.05/account via `searchAccounts`.
- **"`salesNavigator.*` bills per returned row" was wrong for the actions
  this recipe leads with.** Per-row billing is the search/extract family;
  `findEmployeesDistribution` and `findCompanyMetrics` are flat 0.25 per
  account. Split the two families explicitly.
- **The Step 6 approval example did not reconcile.** It quoted ~7,400
  credits, then claimed dropping two 1-credit attributes over 4,100
  accounts saved ~4,200. The table also stopped at row 4 with an ellipsis,
  so the total could not be checked against it. All six rows plus both ID
  prereqs are now explicit and every figure is derived: 3.55/account →
  ~14,600 full, ~4,300 cheaper cut, ~3,200 narrow cut. The cut now makes
  the point the old numbers obscured — dropping both cargo enrichments
  also drops the 0.50 match, so the saving is 2.50/account, not 2.00.
  Step 8's refresh figure is rederived from the same table (~5,100/month
  for the monthly-cadence fields).
- **Snitcher was filed as unavailable for net-new.** Visitor ID surfaces
  companies browsing the seller's site — mostly cold. The pack's own
  playbook calls them "the warmest cold segment there is" and files them
  in the SIGNAL stage, routed to outreach-activation, not expansion. It
  belongs in a net-new schema; what disqualifies it from the sourced
  shortlist is that it is arrival-driven, not that the prospect never
  logged in. Reframed on that axis, with the `fetchOrganisations` 3/company
  recurring cost called out.

Arithmetic verified numerically, not by eye. skills-lint 0/0 ·
routing-eval 81/81 core · llms.txt regenerates clean.

Co-Authored-By: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
Co-Authored-By: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>

* Fix row-billed pricing for theirStack across the gate and the shortlist

Bugbot's third round, one finding, and it lands on an inconsistency the
previous commit introduced: that commit sharpened "search-shaped actions
bill per returned row" while leaving the hiring-intent row priced at a
flat 0.5/account. `cost-discipline.md` uses this exact case as its worked
example of an estimate missing by 2x ("theirStack billed per returned job
posting, and 12 companies had >5 postings each"), so the recipe was
demonstrating the failure its own reference warns about.

- **Gate table** now prices both theirStack rows as `unit x rows`, with
  `limit` named as the cap. `searchTechnologies` had the same defect —
  cost-discipline §4 is explicit that search actions bill on returned
  rows, not matched totals, so it is not specific to `searchJobs`.
- **Step 5** measures rows-per-account as a recorded output, median *and*
  max, since the max is what `limit` has to cap. Without that multiplier
  a per-row price gets copied into the shortlist as if it were per-account,
  which is the whole mechanism of this bug.
- **Step 6** prices #5 as 0.5 x `limit: 3` = 1.50, states the probe's
  median (3) and max (9), and notes that uncapped the top decile costs
  4.50. Every downstream figure rederived: 4.55/account, ~18,700 full,
  ~8,400 cheaper cut, ~4,100 narrow, ~16,400 all-six monthly.
- **Step 8** adds the annual arithmetic on the fastest field, which is
  where row billing actually hurts: #5 weekly over the full list is
  ~6,200/week and ~320,000/year, more than the first fill of everything
  else combined. The fix is a narrower audience, not a lower price.
- **Probe budget** moved 10-40 -> 30-80; the Step 6 schema alone probes
  at ~46, so the old range was unreachable.
- Noted the provider inversion this exposes: theirStack at 0.5/row only
  undercuts cargo's flat-1 technographics while an account returns one
  row. Compare per-account totals, not unit prices — which is also why
  Step 6 picks the flat action for #4.

All 16 figures verified numerically against the table. skills-lint 0/0 ·
routing-eval 81/81 core.

Co-Authored-By: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
Co-Authored-By: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>

* Rebase onto main (#101) and true up the changelog entry

`main` picked up #101 (the Clay migration recipe). Conflicts were all in
generated files — `llms.txt` and two `skill-metadata.json` — resolved by
re-running the generators rather than hand-merging, then verified: both
recipes appear exactly once, 92 metadata entries, zero duplicates, and a
regenerate-after produces no diff.

Two corrections to the changelog entry while here:

- **The snitcher bullet still described the pre-fix behavior**, calling
  visitor sessions structurally unavailable for net-new and routing them
  to expansion — the exact claim the round-2 review corrected in the
  recipe. The entry now says what the recipe says.
- **Added the costing bullet**, since per-account vs per-row pricing and
  the two ID prerequisites are now a substantive part of what the recipe
  teaches and were not mentioned at all.

Also noted that this bump publishes #101's recipe: that PR added
`clay-to-cargo.md` without moving `cargo-gtm` off 1.13.0, so 1.14.0 is
the release that ships it. Drop that line if a separate bump is planned.

Router stays at 1.19.2 — #101 did not touch it. All 15 figures in the
recipe re-verified numerically post-rebase. skills-lint 0/0 ·
routing-eval 81/81 core · sync-trigger-slugs up to date.

Co-Authored-By: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
Co-Authored-By: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>
Co-authored-by: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
2026-08-14 18:02:56 -07:00
Auree Aubert 0d3981a8b7 cargo-diagnostics 1.1.0: find the run when all you have is a symptom (#97)
* cargo-diagnostics 1.1.0: find the run when all you have is a symptom

`orchestration run list` requires --workflow-uuid and has no "most recent
run" form, and run-trace.md § 0 — the one place documenting how to find a
run — already assumed a workflow_uuid in hand. Asked to "look at the last
run", an agent has nothing to call and reports the node inputs/outputs as
inaccessible. They aren't: orchestration SQL over `runs` takes no filter.

- run-trace.md § 0 rewritten as a discovery ladder ordered by what the user
  hands you: no UUID (unfiltered `runs`), a company/domain (record_title
  ILIKE), a play by name (play list -> workflowUuid -> filter).
- Explicit instruction not to give up at `run list`, in the runbook and in
  the diagnostics routing section.
- Error rows for the discovery step, both hit while verifying against CLI
  1.0.52: `SELECT * FROM runs` exceeds the 50-column read cap (51 columns),
  and `runs` has no play_uuid / name / trigger-source column.
- States what the UI editor does not change: CLI, scheduled, and UI-triggered
  runs all land in `runs`. One that doesn't is a bug to report.
- Same caveat at the other two doors: cargo-orchestration troubleshooting
  and two rows in the router's gotchas table.

Every documented query was run against CLI 1.0.52 before being written down.
No CLI change needed — the surfaces existed, they just weren't reachable
from the question a user asks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Bump the Codex and Cursor manifests, regenerate skill metadata

The plugin version mirrors the router's, and .claude-plugin was the only one
of the three I bumped. Regenerates the three stale skill-metadata.json files
alongside it. skills-lint is clean locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 13:04:19 -07:00
Auree Aubert 8d45b172df Stop calling it email finding in the plugin manifests (#95)
"Email finding" is the phrasing that drew the OpenAI spam rejection on
cargo-gtm, and it sat unchanged in the three channel plugin descriptions
and the root manifest blurb -- the copy a reviewer or a user reads before
anything else. It also misdescribes the product: the CLI queries licensed
data providers and verifies what comes back, which is the part that makes
the difference to whoever is deciding whether this is a scraper.

Channel descriptions now say "contact enrichment and verification from
licensed data providers" and name lead scoring, replacing "enrichment,
email finding". The root manifest picks up the blurb merged in #91, so
all five manifests finally describe the same product.

No version bump: marketplace copy only, no skill content, and the OpenAI
archive does not contain these files -- verified by diffing an archive
built with and without this change (identical, only zip mtimes differ).

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 19:03:41 -07:00
Auree Aubert d0458d0b8a Version the acceptable-use work for an OpenAI resubmission (#93)
The directory is a submission-time snapshot — published skills do not update
live — so getting the gates in front of a reviewer needs a version to claim,
not just a merged branch.

cargo-gtm goes to 1.13.0: a new mandatory reference and new sections, no
command or response-shape changes, which is MINOR under the policy at the top
of the changelog. The router goes to 1.19.0 and carries the gate in its own
cargo-gtm critical-rules block, so an agent that stops at the router still
learns that basis, suppression, and relevance are blocking — the router is
what gets read first, and a rule only in the leaf skill is a rule an agent can
route around. The three plugin manifests mirror the router by lint rule.

Root plugin.json stays at 1.17.1: different channel, not linted against the
router, and not read by the package builder.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 18:42:54 -07:00
Auree Aubert 8afa8ecd95 Close the ClawHub security-audit documentation gaps (cargo 1.18.2) (#84)
* Close the ClawHub security-audit documentation gaps (cargo 1.18.2)

The automated audit of the published bundle returned Review with five
findings. Three were real documentation gaps — places where the bundle
assumed the reader had the whole thing in context. No behavior changes.

- use-cases.md §6 opened on `token create` as a bare numbered step. The
  "shown once → secrets manager" rule lived in ~10 other files but not
  this one, so an agent loading only the use-cases reference never saw
  it. Added token handling, least-privilege scoping, and a confirm-first
  note on the steps that change who can access the workspace.
- README.md and cargo/SKILL.md now say what `curl … install.sh | sh`
  actually is, how to read it first, and the by-hand equivalent.
  sessions.md already pointed at the installer source; the two
  more-read files did not. The router also tells the agent never to run
  it on the user's behalf without asking.
- "Why the pin" now documents cli-version as the reason session-start
  refresh is not a blind auto-update, tells the agent to surface a
  global npm install rather than doing it silently, and marks the pin
  read-only.

The other two findings need no change: the flagged egress is the
product's own API, and the script-fetching match is the same installer.

Plugin manifests bumped in lockstep (linter enforces they mirror the
router). skills-lint clean, llms.txt and trigger slugs in sync, routing
evals 81/81 core.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Make the audit response survive an actual audit

Review of the 1.18.2 branch found the trust paragraph pointing readers at
`apps/backend/src/http/routes/install.sh` in `getcargohq/cargo` — a private
repo. Every external reader following that gets a 404, from the one paragraph
whose entire job is making `curl | sh` auditable. Removed from README.md and
cargo/SKILL.md; neither now sends anyone somewhere they cannot go.

`curl … | less` followed by `curl … | sh` is two fetches, and the server can
serve different bytes to the second, so reading first guarantees nothing about
what runs. Replaced with download-once-then-execute in both files.

The use-case gate named the wrong steps: step 9 is `folder create` (no access
change) and step 2 is `role list` (read-only). The steps that actually grant
access are 1 (`token create`) and 3 (`user create`), so an agent was pausing on
a benign organizational step and skipping a credential mint.

Also: README pointed "below" at the plugin install, which is above it — now an
anchor link; and cargo-workspace-management/SKILL.md still inlined the bare
pipe-to-shell command the rest of this change set was written to caveat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 15:36:31 -07:00
Auree Aubert 9b5a78ba46 Stop the SessionEnd hook from spawning an endless summarizer chain (#79)
hooks/session-end.sh refines the session title by piping the transcript
to `claude -p`. That subprocess is a full Claude Code session, so when it
exits it fires SessionEnd, which runs the hook again, which spawns
another summarizer. Nothing broke the chain except a child that happened
to have no transcript.

Reported from the field on v1.17.1: 264 headless sessions against 8 real
ones in a day (~16.5M tokens), each spawn burning ~66K tokens — 46.5K
cache write plus 20K cache read — to produce a 150-token title. The
session log shows the signature: distinct session ids seconds apart, all
refined with an identical title, because each child summarizes the
previous child's transcript.

The fix is a CARGO_SESSION_SUMMARIZER marker exported across the
`claude -p` call. It reaches the child's own hooks through the
environment, and all three lifecycle hooks now exit on it — so a
summarizer child registers no session row, refreshes no CLI, writes no
checkpoint, and spawns no second summarizer.

session-end.sh also keeps a rolling-hour spawn budget (20 by default,
CARGO_SUMMARIZER_MAX) as a mechanism-independent backstop: if the marker
is ever lost to a hook runner that re-execs with a scrubbed environment,
a runaway is capped instead of unbounded. Exhausting the budget costs a
refined title, nothing else — the row is already finalized with the
placeholder.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 13:47:58 -07:00
Auree Aubert 3708f8c955 Agent discoverability: trigger-shaped descriptions, routing evals, standalone bootstrap (#77)
* Agent discoverability: trigger-shaped descriptions, routing evals, standalone bootstrap

The `description` field is the only text an agent weighs before deciding
whether to load a skill. Everything else in the bundle is invisible until
that decision is already made — and twelve of the seventeen descriptions
read as "Manage <CLI nouns> using the Cargo CLI", which matches a user who
is already thinking in Cargo's object model rather than one stating a job.

This rebuilds that layer and, for the first time, tests it.

Routing
- Every description rewritten to a four-part template: job in the user's
  words -> literal quoted trigger phrases -> proper nouns -> an explicit
  `Skip when:` pointing at the sibling skill that owns the adjacent case.
- Integration and provider names are generated, not hand-maintained.
  sync-trigger-slugs.ts fills cargo-gtm's `Providers:` list from the
  playbook tree and cargo-connection's `Integrations:` list from a
  committed catalog snapshot (138 integrations, 50 credits-based).
  `--refresh` re-pulls from a logged-in CLI; `--check` gates CI.
- evals/routing.jsonl (92 prompts) + routing-eval.ts, wired into CI.
  Structural tier asserts the template and that each skill keeps >=5 terms
  unique to it; lexical tier requires the expected skill to rank first;
  an opt-in --llm tier asks a real model to route from descriptions alone.
  Deep paraphrases are tagged "hard" and reported rather than gated — a
  lexical proxy cannot judge them fairly.

Standalone installs
- `skills add --skill <one>` is a supported path and the registry lists
  each skill separately, but thirteen skills' only install instructions
  were a relative link to ../cargo/references/prerequisites.md, which does
  not exist in a single-skill install. Every SKILL.md now has a
  self-contained Bootstrap section, enforced by the linter.

Coverage
- New capability skill cargo-segmentation — the segmentation CLI domain,
  previously uncovered while segments were load-bearing in most recipes.
  Verified against the live CLI, including the flag traps.
- Three cargo-gtm recipes: source-planning (probe and cost candidate
  sources per *hit* before any fan-out), ads-audience-activation (Google
  Customer Match / LinkedIn Matched Audiences), review-and-iterate (human
  review of judgment output, kept as an eval set).
- Router gains a "these skills vs a workspace MCP server" section: Cargo
  has no first-party MCP server — a workspace builds one and serves it
  over `cargo-ai mcp` — so the routing rule is scale and reproducibility.

Index and hygiene
- llms.txt now lists every recipe and provider playbook with a generated
  summary: 17 entries -> ~80 job-named addressable URLs.
- Router trimmed ~80 lines of per-skill summaries that restated each
  skill's own description; critical rules kept in full.
- Linter: bootstrap check, skill-count claims validated against the tree,
  `doctor` and `mcp` added to the known CLI domains.
- CONTRIBUTING.md, keyword-dense README opening, updated repo metadata.

Also carries the aiArk/apolloio priority-stack promotion authored in a
parallel session on this branch (cargo-gtm 1.12.0) — the two touch the
same SKILL.md files and could not be split cleanly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Surface the 100-credit free tier where an agent meets it

There is no purchase gate between installing Cargo and producing a real
deliverable — a new account starts with 100 free credits and no card — and
nothing in the bundle said so. An agent that doesn't know the budget can't
tell a first-time user whether the demo is a look around or a commitment,
and defaults to hedging.

Now stated at every point an agent encounters the question:

- README opening and the generated llms.txt install block: an agent can
  install, sign the user up, and hand back a list in one turn.
- Router install section and prerequisites.md, with anchors for what 100
  credits buys (~5,000 sourced leads, ~1,000 verified-email enrichments,
  ~50 fully enriched contacts).
- cargo-quickstart: the demo spends ~0.5 of the 100, and the skill now
  says so before the first paid call — that reframes the moment from a
  purchase decision into a look around, which is the job of a quickstart.
- cargo-billing: a "what 100 credits buys" table, plus two new trigger
  phrases so free-tier questions route here at all.
- cost-discipline.md: on a new account, receipts and next-step proposals
  must be framed against the free tier. "87.6 of your 100 free credits
  left" is a number the user can act on; "87.6 remaining" is not. Also
  warns that phone lookups (6-7/call) are the fastest way to burn a tier
  a first session should end with something left to try.

Two routing eval cases added for free-tier questions (79/79 core).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Run the routing evals against a real model in CI

The lexical tier passes 79/79, but it is a deterministic proxy written
alongside the descriptions it grades — it cannot fail in a way that
surprises us, which is what makes it weak evidence. The only honest
scoreboard for the description rewrite is whether a real model, shown
nothing but the 17 descriptions, routes the eval prompts correctly.

That tier existed but had never run. Now it does, in its own workflow.

- New .github/workflows/routing-evals-llm.yml, using the repo's
  ANTHROPIC_API_KEY. Deliberately not part of skills-lint: it costs money
  and is non-deterministic, so it must never stand between a typo fix and
  a merge. Triggers on PRs and pushes that touch a SKILL.md, the eval
  cases, or the runner; weekly on a schedule so model drift surfaces even
  when nobody edits a description; and on demand with a model override.
- runLlm reworked for CI: 6-way concurrency (92 sequential calls is a slow
  job), retry with backoff on 429/5xx, tolerant answer parsing, and a
  core/hard/overall breakdown instead of one number.
- Results are written to GITHUB_STEP_SUMMARY as a table, including every
  misroute with what the model picked instead — so a regression is
  readable on the run page without opening logs.
- Optional --llm-min=<percent> floor, wired to the exit code. Left unset
  until the first runs establish a baseline; a floor guessed before the
  data would either never fire or block on noise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Act on the first real-model routing run: 92/94, two fixes

Baseline from the model tier (claude-haiku-4-5, judging from the 17
descriptions alone): 92/94 overall — core 79/79, hard 13/15. The lexical
proxy's 3/15 on the hard tier was noise about the proxy, not the
descriptions, which is what that tier existed to establish.

Both misroutes were worth having:

- "Our outbound is going to the wrong people — help me figure out who
  actually buys from us" routed to cargo-context instead of cargo-gtm.
  A real collision: cargo-context's triggers include "who do we sell to",
  which reads as ICP ownership. It owns *writing the conclusion down*;
  deriving it from won/lost data is cargo-gtm. Added that boundary to
  cargo-context's Skip when, and the two phrasings to cargo-gtm.

- "Give the assistant access to our sales collateral so it stops making
  things up" routed to cargo-content instead of cargo-ai — and the model
  was right. As written the first step is getting the collateral into the
  workspace. Corrected the expectation and added the other side of the
  boundary as its own case ("attach the library we already uploaded to
  the agent"), which the lexical tier then failed, correctly: cargo-ai
  never said "library" or "release resources". Added both, so the
  content/ai split is now legible to a ranker and a model.

Set --llm-min=93 now that a baseline exists. Deliberately below the
observed 98%, not at it: a model tier that fails on one non-deterministic
flip trains people to ignore it.

Lexical 80/80 core.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Document cargo-ai mcp — the bridge that was written down nowhere

Cargo reaches coding agents through two doors. Skills reach agents that
implement skills.sh; a workspace MCP server reaches everything else —
Claude Desktop, ChatGPT, and whatever ships next. The second door was
fully built and documented in exactly one place: `cargo-ai mcp --help`.

Worse, cargo-ai/SKILL.md taught `ai mcp-server create` (building a server)
without ever mentioning the command that serves it, so an agent could
construct one and have no idea how to connect it — and the section
conflated publishing a server with consuming someone else's, which are
unrelated surfaces.

- MCP section rewritten around the two directions, with a table: publish
  (`ai mcp-server`, called by any MCP client, wired via `cargo-ai mcp`)
  vs consume (`ai mcp-client`, called by your agents, wired via
  `release update-draft --mcp-clients`). States plainly that there is no
  first-party Cargo MCP server — a workspace builds its own.
- Documents `cargo-ai mcp` with the registration one-liner, the
  CARGO_MCP_SERVER_UUID fallback, and the stdout-is-protocol constraint.
- Details verified against the live API rather than --help: actions take
  kind "tool" *or* "agent" (an agent is exposable as a callable MCP
  tool), resources take model or file, and `update` replaces the arrays
  wholesale instead of merging — a real footgun.
- Routing rule for which door to use, pointing at the router section.
- Two trigger phrases ("expose our tools as an MCP server", "use Cargo
  from Claude Desktop or ChatGPT") and two eval cases, so the publish
  direction is reachable from a description at all. It previously wasn't.

Lexical 81/81 core.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 10:18:32 -07:00
replicas-connector[bot] e28aabd521 Point the skills at browserless login, and unpin agents from a CLI that lacks it (#73)
* Point the skills at browserless login, and unpin agents from a CLI that lacks it

cargo-ai 1.0.47 (2026-08-10) added `login --email`: it mails a one-time code,
creates the account on first use, and needs no browser at any point. All 16
skills still described sign-in as "browser sign-in via --oauth, or an API
token".

The load-bearing change is `cargo/cli-version`. The router tells every agent to
run `npm install -g @cargo-ai/cli@$(cat cli-version)` at session start, and that
pin was 1.0.29 (2026-07-09). Its `login` command exposes only --oauth, --token
and --workspace-uuid, so documenting --email without moving the pin would ship a
flag the installed binary does not have.

- cargo/cli-version: 1.0.29 -> 1.0.47
- 16 compatibility lines rewritten
- router + README install blocks lead with --email, and name the agent-shell
  behaviour (no terminal: first call sends the code and exits, re-run with
  --code) and the stdin form
- patch version bump on each changed SKILL.md so clawhub republishes them

Co-Authored-By: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
Co-Authored-By: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>

* Regenerate skill-metadata.json for the changed skills

The bundle linter tracks a version and a contentHash per skill and failed with
19 stale entries. Regenerated with the documented command, so the diff is only
the bumped versions and their matching hashes.

Co-Authored-By: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
Co-Authored-By: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>

* Mirror the router version bump into the three plugin manifests

The linter requires .claude-plugin, .codex-plugin and .cursor-plugin to carry
the router's version. 1.17.0 -> 1.17.1.

Co-Authored-By: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
Co-Authored-By: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>

---------

Co-authored-by: replicas-connector[bot] <replicas-connector[bot]@users.noreply.github.com>
Co-authored-by: Auree Aubert <16732193+aureeaubert@users.noreply.github.com>
2026-08-11 14:42:02 -07:00
Auree Aubert f6d752d869 Add cargo-observability skill — scheduled threshold alerts (#72)
* Add cargo-observability skill for the alerts domain

Introduces a new capability skill documenting the observability CLI
domain (cargo-ai observability alert/event) shipped in the backend/api.

Alerts are scheduled threshold checks: measure a scope (spans/runs/
records/orchestrationQuery/storageQuery/model), compare to a threshold
(metric + gte/lte + value), and fire actions as runs on breach.

New skill: cargo-observability/
  - SKILL.md — commands, the preview-before-create discipline, the
    scope/threshold/action model, permissions, and cost discipline
  - references/scopes-and-thresholds.md — the scope↔threshold matrix,
    metric meanings/units, and the empty-window-vs-real-zero rule
  - references/alert-lifecycle.md — cron windows, at-most-once firing,
    dead-man's switches, and the {{alert.*}}/{{event.*}} templating
  - references/examples/recipes.md — seven copy-paste alert recipes

Pack integration:
  - Router cargo/SKILL.md → 1.17.0: table row, recap, dependency rule,
    diagram box, defineAlert added to the CDK builder list, counts
    (15→16 skills; twelve→thirteen capability skills)
  - README.md, AGENTS.md: counts + Observability capability row + a
    "get alerted when something breaks" use case
  - Plugin manifests bumped to 1.17.0 to mirror the router
  - skills-lint.mjs: cargo-observability in SKILL_DIRS, observability
    in the known-CLI-domains allowlist
  - CHANGELOG entries; regenerated skill-metadata.json

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UzK9huhSm588ThVFS1GcE

* Document defineAlert in the cargo-cdk skill

Wires the new observability domain into the declarative skill so alerts
can be managed as workspace-as-code, not only via the imperative CLI.

cargo-cdk → 1.2.0:
  - references/resources.md: defineAlert Builders-table row + a "Notes on
    specific fields" entry — the scope↔threshold matched pair (TS narrows
    the metric menu by scope.kind), scope wiring by handle, the {ref,config}
    action wrapper + alertConnectorAction/alertToolAction helpers +
    {{event.*}}/{{alert.*}} templating, and the slugless-identity note
  - guides/authoring-resources.md: an Observability example wiring an alert
    to a definePlay handle with an agent action
  - SKILL.md §6: alert added to the slugless "commit cargo.state.json"
    resources; new critical rule that a defineAlert whose actions call paid
    nodes re-bills on every breach (preview the threshold; prefer cheap
    notification actions); cross-links to cargo-observability

Also: cargo-observability scopes reference now states telemetry duration is
in seconds (confirmed authoritative from the CDK builder types).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UzK9huhSm588ThVFS1GcE

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-09 15:57:34 -07:00
Auree Aubert 895ade0bcc Require a 10–20 record sample and explicit approval before enrolling a full batch (#70)
* Require a 10–20 record sample and explicit approval before enrolling a full batch

`batch create` / `action execute-batch` fan out across every record in their
data source, so a config mistake and the full bill arrive together. Every
surface that can launch one now requires the same three steps: count the pool
for free, sample 10–20 records through the exact workflow and config, then ask
the user to approve the full enrollment with both the record count and the
credit estimate in the question.

The canonical gate lives in cargo-orchestration ("The sample gate"), with the
sampling mechanics per data kind — `kind: "filter"` + `limit`, `recordIds`, a
sliced `records` array, a truncated CSV — and the note that `kind: "segment"`
and `kind: "change"` take no limit, so they can't be sampled directly.

The GTM cost gate was a 1–3 row pilot, which can't produce a hit-rate, and
hit-rate is what drives the estimate. Split it: 1–3 rows proves one action's
config shape, 10–20 records before any batch. The approval message now states
how many records the full run enrolls alongside what they cost, and approval of
the sample is explicitly not approval of the full run.

Propagated to the router recap, gotchas, and interaction conventions (the gate
holds even when a batch arrives through cargo-orchestration or cargo-cdk with
no GTM framing), and to cargo-cdk (a deployed play's first batch, and the
per-run re-bill of a scheduled one). Aligned the downstream copy that quoted
the old 1–3 row pilot: the execution-planner agent, build-tam, and save-as-play
(whose play pilot was a single record).

Verified against the live CLI: `batch create --help` for where `limit` is
accepted, `segment list` for `recordsCount`/`filter`, and `file upload --file`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Bump plugin versions to 1.16.0 and regenerate skill-metadata.json files

Co-authored-by: Auree Aubert <aureeaubert@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Auree Aubert <aureeaubert@users.noreply.github.com>
2026-08-08 14:19:47 -07:00
Auree Aubert 3033d2f2ca Surface URL-anchored enrichment, document action input schema, remove proxycurl (#64)
* skills(gtm/connection): surface URL-anchored enrich, document action input schema, remove proxycurl

An agent holding LinkedIn URLs was routed into the sourcing-first stack and
improvised connector lookups instead of using the native linkedin enrichment
that already existed. Two fixes: surface the "already have identifiers" path,
and write down the one genuinely-undocumented mechanic.

- cargo-connection: new "Reading an action's input schema" section — inputs live
  at actions.<slug>.config.schema; top-level action execute/execute-batch take
  them via --data not config (documents the "does not use action.config" error);
  identity-driven actions need identityIds via the listIdentityIds autocomplete.
- cargo-gtm: callout + recipe-spine ENRICH branch routing LinkedIn URLs to
  linkedin.enrichProfile/enrichCompany (0.25), event URLs to extractEventAttendees,
  emails to leadMagic/contactOut before the pricier waterfall rungs.
- cargo (router): priority-stack line now points URL/event-URL holders at linkedin.
- Remove proxycurl: API is sunset (live calls return API_SUNSET). Dropped the
  playbook, its credits-cost-table rows, and skill-metadata entry.

Version bumps: cargo 1.14.3→1.15.0, cargo-connection 1.1.0→1.2.0, cargo-gtm 1.7.0→1.8.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* skills(gtm): add AI Ark provider playbook; fix CI (plugin versions + metadata) (#65)

Two new integrations were released. Apollo (`apolloio`) was already fully
documented and its 11 product actions still match its playbook exactly, so no
change was needed there. AI Ark (`aiArk`) was undocumented — added it, grounded
in the product's connector definition (apps/backend .../official/aiArk).

AI Ark (all six actions credits-based, run on the managed connection):
- new provider-playbooks/aiArk.md with exact slugs, costs, input schemas, and
  the nested filter-group shape for searchPeople/searchCompanies.
- enrichPerson (0.1, profile + verified email, bills 0 on no-email),
  reverseLookup (0.05), analyzePersonality (0.05, catalog-unique),
  findMobilePhone (0.5, cheapest phone rung), searchPeople (0.05/record),
  searchCompanies (0.01/record + lookalike seeds, cheapest company search).
- wired into SKILL.md §11, stage-action-map, alternatives, credits-cost-table
  (six rows). Catalog action count 141 -> 145.
- cargo-gtm 1.8.0 -> 1.9.0.

CI fix (broken by the prior proxycurl-removal commit):
- .{claude,codex,cursor}-plugin/plugin.json 1.14.3 -> 1.15.0 to match the
  router SKILL.md version the linter enforces.
- regenerated all skill-metadata.json (were stale).
- router catalog count 141 -> 145.

skills-lint and the QA fixture suite both pass.


Claude-Session: https://claude.ai/code/session_0118N88UEVUtRnyDmBS9pjv4

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 12:16:45 -07:00
Auree Aubert 512c792f11 chore: bump plugin bundle to 1.14.3 (#63)
Sync cargo/SKILL.md and the three plugin manifests to 1.14.3 and
regenerate cargo/skill-metadata.json so the lint's version-lockstep
and content-hash checks pass.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 17:09:54 -07:00
Auree Aubert 9dd8442760 fix: rename --file-path to --file for file upload commands (#61)
* fix: rename --file-path to --file for file upload commands

Update all docs and examples to use the current `--file` flag for
`content file upload` and `workspaceManagement file upload`, and bump
plugin version to 1.14.2.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: sync router version to 1.14.2 and regenerate skill metadata

Bump cargo/SKILL.md to 1.14.2 so the plugin manifests mirror the router
version, and regenerate stale skill-metadata.json files.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 23:26:17 -07:00
Auree Aubert a92d53541a feat(storage): add "show the rows, not the schema" preview convention (#60)
Add a pack-wide interaction convention (cargo router §4 + cargo-storage)
telling the agent to preview ~10 real rows once data lands, echo the
schema (not an empty query) right after model/column creation, and treat
an unexpectedly empty/null preview as a finding. Bump bundle to 1.14.1
across router + all three plugin manifests and regenerate skill metadata.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 21:54:15 -07:00
Auree Aubert 3610dc442c feat: native subagents + opt-in statusline (#56)
* feat: native subagents + opt-in statusline

Rebased onto main (post #55): cargo-execution-planner and cargo-list-builder
ship as Claude Code plugin subagents (haiku tier; canonical role specs in
cargo-gtm/agents/ for every channel), plus hooks/cargo-statusline.mjs — an
opt-in statusline (workspace, credits, CLI pin state) whose render path is
cache-only with a detached refresh. README documents the settings.json wiring
and the plugin's subagent benefit. cargo -> 1.14.0 (manifests lockstep),
cargo-gtm -> 1.5.0 (list-builder + §2 fan-out routing). Metadata regenerated
over main's tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(statusline): exact semver pin match; single-flight refresh lock

- The pin check used a substring test, so 21.0.29 / 1.0.290 / 10.0.29 all
  satisfied pin 1.0.29 and rendered as pinned. Extract the semver from
  --version output and require exact equality.
- render() spawned a detached --refresh child on every call while the cache
  was missing/stale, with no in-flight guard — frequent statusline updates
  stacked overlapping cargo-ai processes until the first refresh landed. A
  lock file created atomically (flag "wx", stale after 60s for crashed
  children) makes refresh single-flight; the child releases it after writing
  the cache, and a failed spawn frees it immediately.

Verified: 6 parallel cold renders spawn exactly 1 refresh child; lock releases
after the cache write; warm render never spawns; all tricky version shapes
(21.0.29, 1.0.290, 10.0.29, "Loading..." prefix) compare correctly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(statusline): refresh preserves the previous cache across failures

refresh() rebuilt the cache from scratch, so one transient CLI failure
blanked fields that were still valid (workspace, credits, pin state) and a
fresh `at` was stamped even when nothing was fetched — pushing the retry a
full TTL away. It now starts from the previous cache, overwrites a field only
on a successful fetch, and advances `at` only when at least one fetch
succeeded; a fully failed refresh leaves the file byte-identical so the next
render retries immediately (rate-limited by the refresh lock).

Verified with a stub cargo-ai (bypassing volta's PATH re-injection): total
failure leaves the cache byte-identical; partial failure (--version only)
keeps workspace/credits, updates the pin segment, advances the clock.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 00:41:09 -07:00
Auree Aubert a418d76ee7 Fan the plugin out to Codex and Cursor; bundle session-lifecycle hooks (#50)
* Fan the plugin out to Codex and Cursor; bundle session-lifecycle hooks

- One plugin source, three targets: .agents/plugins/marketplace.json
  (Codex) + .cursor-plugin/marketplace.json, with .codex-plugin/ and
  .cursor-plugin/ plugin manifests version-locked to the router
  (linter-enforced across all three, negative-tested). The approval hook
  is wired per target - PreToolUse (Claude Code), PermissionRequest via
  hooks/codex-hooks.json (Codex), beforeShellExecution via
  hooks/cursor-hooks.json (Cursor) - reusing approve-cli.sh's existing
  per-agent verdict dispatch.
- Plugin-bundled lifecycle hooks (Claude Code): session-start (pinned
  CLI refresh from the plugin's own cli-version + placeholder session
  row; deliberately no skills add - the plugin owns the skills),
  session-checkpoint (Stop; jq-derived title, 45s throttle), and
  session-end (sync finalize + detached claude -p refinement). Each
  defers when the installer's counterpart exists at ~/.claude/hooks/,
  so installer + plugin never double-register a session.
- .gitignore: un-ignore .agents/plugins/ (vendored skills stay ignored).
- README: per-target install steps; router 1.11.0, plugins 1.11.0.

Verified: lint 0/0; scripts behavior-tested with stubbed npm/cargo-ai
(pin read from plugin root, defer guards, checkpoint derivation +
throttle, --finished finalize); live plugin install on Claude Code
2.1.205 shows all four hook events and both extra manifests in the
1.11.0 cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Make the plugin the single source of truth; agent-portable docs

- session-start.sh now also refreshes the plugin itself (marketplace
  update + plugin update, detached, effective next session) - the plugin
  channel's replacement for the installer's skills-add refresh, with
  zero duplication by construction.
- Docs made agent-portable: the pinned-install command in cargo/SKILL.md
  and prerequisites.md reads cli-version from the skill directory the
  agent loaded instead of hardcoding ~/.claude/skills/ (which silently
  degraded Codex/Cursor to @latest); manual-path session titles are
  agent-neutral; job 1 says to skip skills add when skills came from a
  plugin; the permission-prompts note covers all three agents' events.
- README Claude Code section describes the refactored installer flow
  (installs the plugin; skills add only as fallback; legacy scaffolding
  migrated away). cargo 1.12.0, plugins 1.12.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Single-source the lifecycle scripts: dual-mode plugin/standalone

The three hooks/session-*.sh scripts are now the only copies of the
session lifecycle - the installer's fallback channel downloads them
instead of embedding heredocs. Mode auto-detects from location
(~/.claude/hooks/ = standalone; anywhere else = plugin) with an explicit
plugin|standalone arg override:

- standalone: skills add refresh, pin from ~/.claude/skills/cargo/,
  no plugin self-update, never defers.
- plugin: pin from plugin root, detached plugin self-update, no skills
  add, defers when a standalone copy exists (exactly one lifecycle runs).

Log lines carry the mode. Behavior-tested both modes of all three
scripts with stubbed npm/npx/cargo-ai/claude (mode detection, defer,
no-self-defer, per-mode pin paths, per-mode refresh, mode-aware logs).
cargo 1.13.0, plugins 1.13.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(lifecycle): registration-aware defer, wider PATH, checkpoint hygiene

Review feedback on the lifecycle scripts:

- Defer guard now requires the standalone copy to be REGISTERED in
  ~/.claude/settings.json, not merely present - an unregistered leftover
  file no longer suppresses the plugin copy (which would silently skip
  CLI pinning and session logging). Falls back to file existence when jq
  is unavailable.
- session-start's PATH widening for the detached plugin self-update now
  matches session-end's full list (Volta, asdf shims, nvm version dirs,
  /opt/node*) so `claude` resolves under version managers and the
  update isn't silently skipped.
- Checkpoint prompt snippets strip control characters. The reported
  command-substitution injection does not exist - shell assignments and
  quoted expansions never evaluate a value's content, and the upsert
  passes --title/--summary as single argv words (verified empirically:
  $(touch ...) and backticks pass through as literal data) - but control
  chars could mangle logs/rows, so they are removed and the inertness is
  documented in a comment for future reviewers.

Verified: unregistered leftover -> plugin runs; registered standalone ->
plugin defers; clean env -> runs; injection payload inert with ESC
sequences stripped from the stored title.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(lifecycle): run when standalone registration is unverifiable

Review follow-up: the defer guard's no-jq / no-settings.json fallback
returned "owned" (defer), which silently skipped CLI pinning and session
logging whenever registration couldn't be checked - exactly the failure
the registration-awareness was meant to prevent. Flipped: when jq or
settings.json is unavailable, default to NOT owned so the plugin copy
runs. Safe direction - the session upsert is idempotent, so a duplicate
with an active standalone is harmless, whereas a wrong defer is a silent
skip. Unit-tested all five branches (registered->defer; not-registered,
no-settings, no-jq, no-file->run).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 18:33:21 -07:00