agentlinkops 0.6.13 → 0.6.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/.codex-plugin/plugin.json +2 -2
  3. package/CHANGELOG.md +25 -2
  4. package/PACK-ARTIFACT.json +16 -0
  5. package/PACKS.json +591 -0
  6. package/README.md +34 -48
  7. package/cli/adapters/suppliers.js +1 -1
  8. package/cli/agent-setup.js +101 -17
  9. package/cli/campaign-bridge.js +325 -0
  10. package/cli/campaign-outcomes.js +107 -0
  11. package/cli/catalog.js +45702 -18674
  12. package/cli/check.js +40 -10
  13. package/cli/client.js +2 -1
  14. package/cli/commands.js +17 -8
  15. package/cli/connection.js +92 -18
  16. package/cli/contact-adoption.js +91 -0
  17. package/cli/context.js +13 -2
  18. package/cli/doctor.js +15 -4
  19. package/cli/import.js +7 -1
  20. package/cli/key-store.js +115 -0
  21. package/cli/ledger.js +1 -0
  22. package/cli/local-result.js +41 -6
  23. package/cli/main.js +51 -12
  24. package/cli/mcp-client.js +2 -2
  25. package/cli/migration.js +308 -0
  26. package/cli/prompt.js +25 -0
  27. package/cli/public-http.js +168 -0
  28. package/cli/state.js +14 -0
  29. package/cli/sync.js +1 -1
  30. package/package.json +7 -8
  31. package/references/citation-panels.md +2 -0
  32. package/references/cli.md +2 -2
  33. package/references/disavow.md +1 -1
  34. package/references/discovery-handoff.md +1 -1
  35. package/references/local-crm-draft-handoff.md +1 -1
  36. package/references/portable-ledger-handoff.md +11 -10
  37. package/references/recipes/catalog.json +263 -7
  38. package/references/recipes/command-notes.md +5 -3
  39. package/references/recipes/internal-link-improvement.md +25 -0
  40. package/references/recipes/placement-reconciliation.md +53 -1
  41. package/scripts/portable-handoff.py +17 -4
  42. package/shared/admission-contract.js +3 -2
  43. package/shared/admission-report-contract.js +3 -2
  44. package/shared/analytics-history-contract.js +15 -0
  45. package/shared/campaign-outcome-contract.js +12 -0
  46. package/shared/campaign-prospect-contract.js +23 -0
  47. package/shared/classification-contract.js +54 -0
  48. package/shared/contact-record-contract.js +27 -0
  49. package/shared/disavow-contract.js +37 -9
  50. package/shared/index-observation-contract.js +5 -4
  51. package/shared/internal-links-contract.js +143 -0
  52. package/shared/link-category-contract.js +35 -0
  53. package/shared/link-category.js +138 -0
  54. package/shared/link-labels.js +26 -0
  55. package/shared/link-summary-contract.js +17 -0
  56. package/shared/observation-event.js +1 -1
  57. package/shared/onboarding.js +20 -0
  58. package/shared/response-contract.js +38 -5
  59. package/shared/search-connection-contract.js +2 -1
  60. package/shared/table-page-contract.js +13 -0
  61. package/skills/agentlinkops-assets/SKILL-DEPS.json +290 -0
  62. package/skills/agentlinkops-assets/SKILL.md +3 -1
  63. package/skills/agentlinkops-assets/agents/openai.yaml +1 -1
  64. package/skills/agentlinkops-assets/references/portable/references/cli.md +220 -0
  65. package/skills/agentlinkops-assets/references/portable/references/disavow.md +73 -0
  66. package/skills/agentlinkops-assets/references/portable/references/reports.md +53 -0
  67. package/skills/agentlinkops-assets/references/portable/scripts/agentlinkops.py +676 -0
  68. package/skills/agentlinkops-assets/references/portable/scripts/disavow.py +207 -0
  69. package/skills/agentlinkops-assets/references/portable/scripts/migrations/001_crm.sql +45 -0
  70. package/skills/agentlinkops-assets/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  71. package/skills/agentlinkops-assets/references/portable/scripts/migrations/003_disavow.sql +20 -0
  72. package/skills/agentlinkops-assets/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  73. package/skills/agentlinkops-assets/references/portable/scripts/report.py +113 -0
  74. package/skills/agentlinkops-assets/references/portable/templates/campaigns.v1.json +65 -0
  75. package/skills/agentlinkops-campaigns/SKILL-DEPS.json +336 -0
  76. package/skills/agentlinkops-campaigns/SKILL.md +7 -5
  77. package/skills/agentlinkops-campaigns/agents/openai.yaml +1 -1
  78. package/skills/agentlinkops-campaigns/references/first-campaign.md +31 -0
  79. package/skills/agentlinkops-campaigns/references/mailbox-campaign.md +46 -0
  80. package/skills/agentlinkops-campaigns/references/portable/references/cli.md +220 -0
  81. package/skills/agentlinkops-campaigns/references/portable/references/disavow.md +73 -0
  82. package/skills/agentlinkops-campaigns/references/portable/references/reports.md +53 -0
  83. package/skills/agentlinkops-campaigns/references/portable/scripts/agentlinkops.py +676 -0
  84. package/skills/agentlinkops-campaigns/references/portable/scripts/disavow.py +207 -0
  85. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/001_crm.sql +45 -0
  86. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  87. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/003_disavow.sql +20 -0
  88. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  89. package/skills/agentlinkops-campaigns/references/portable/scripts/report.py +113 -0
  90. package/skills/agentlinkops-campaigns/references/portable/templates/campaigns.v1.json +65 -0
  91. package/skills/agentlinkops-connect/SKILL-DEPS.json +104 -0
  92. package/skills/agentlinkops-connect/SKILL.md +2 -0
  93. package/skills/agentlinkops-connect/agents/openai.yaml +1 -1
  94. package/skills/agentlinkops-connect/references/agentlinkops.md +164 -62
  95. package/skills/agentlinkops-connect/references/placement-workflows.md +19 -0
  96. package/skills/agentlinkops-crm/SKILL-DEPS.json +381 -0
  97. package/skills/agentlinkops-crm/SKILL.md +26 -9
  98. package/skills/agentlinkops-crm/agents/openai.yaml +1 -1
  99. package/skills/agentlinkops-crm/references/portable/references/cli.md +220 -0
  100. package/skills/agentlinkops-crm/references/portable/references/disavow.md +73 -0
  101. package/skills/agentlinkops-crm/references/portable/references/local-crm-draft-handoff.md +76 -0
  102. package/skills/agentlinkops-crm/references/portable/references/portable-ledger-handoff.md +51 -0
  103. package/skills/agentlinkops-crm/references/portable/references/reports.md +53 -0
  104. package/skills/agentlinkops-crm/references/portable/scripts/agentlinkops.py +676 -0
  105. package/skills/agentlinkops-crm/references/portable/scripts/disavow.py +207 -0
  106. package/skills/agentlinkops-crm/references/portable/scripts/migrations/001_crm.sql +45 -0
  107. package/skills/agentlinkops-crm/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  108. package/skills/agentlinkops-crm/references/portable/scripts/migrations/003_disavow.sql +20 -0
  109. package/skills/agentlinkops-crm/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  110. package/skills/agentlinkops-crm/references/portable/scripts/outreach-crm-draft.py +121 -0
  111. package/skills/agentlinkops-crm/references/portable/scripts/portable-handoff.py +197 -0
  112. package/skills/agentlinkops-crm/references/portable/scripts/report.py +113 -0
  113. package/skills/agentlinkops-crm/references/portable/templates/campaigns.v1.json +65 -0
  114. package/skills/agentlinkops-discovery/SKILL-DEPS.json +301 -0
  115. package/skills/agentlinkops-discovery/SKILL.md +4 -2
  116. package/skills/agentlinkops-discovery/references/portable/references/cli.md +220 -0
  117. package/skills/agentlinkops-discovery/references/portable/references/disavow.md +73 -0
  118. package/skills/agentlinkops-discovery/references/portable/references/discovery-handoff.md +32 -0
  119. package/skills/agentlinkops-discovery/references/portable/references/reports.md +53 -0
  120. package/skills/agentlinkops-discovery/references/portable/scripts/agentlinkops.py +676 -0
  121. package/skills/agentlinkops-discovery/references/portable/scripts/disavow.py +207 -0
  122. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/001_crm.sql +45 -0
  123. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  124. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/003_disavow.sql +20 -0
  125. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  126. package/skills/agentlinkops-discovery/references/portable/scripts/report.py +113 -0
  127. package/skills/agentlinkops-discovery/references/portable/templates/campaigns.v1.json +65 -0
  128. package/skills/agentlinkops-discovery/references/saved-backlink-workflows.md +23 -0
  129. package/src/competitors/contract.js +17 -1
  130. package/src/context/site-profile.js +34 -121
  131. package/src/corpus/acquire.js +68 -32
  132. package/src/corpus/extract.js +147 -0
  133. package/src/corpus/frontier.js +151 -0
  134. package/src/discovery/contract.js +8 -0
  135. package/src/discovery/import.js +13 -1
  136. package/src/mentions.js +193 -0
  137. package/src/page-acquisition/acquire.js +50 -0
  138. package/src/page-acquisition/bytes.js +138 -0
  139. package/src/page-acquisition/capture.js +11 -0
  140. package/src/page-acquisition/contracts.js +67 -0
  141. package/src/page-acquisition/documents.js +166 -0
  142. package/src/page-acquisition/http.js +307 -0
  143. package/src/page-acquisition/policy.js +20 -0
  144. package/src/page-acquisition/recovery.js +164 -0
  145. package/src/page-acquisition/render-failure.js +82 -0
  146. package/src/page-extraction/html.js +359 -0
  147. package/src/page-extraction/index.js +30 -0
  148. package/src/page-extraction/metadata.js +16 -0
  149. package/src/response-shaping.js +4 -3
  150. package/src/tool-views.js +22 -12
  151. package/src/verifier/fetch.js +4 -339
  152. package/src/verifier/index.js +81 -23
  153. package/src/verifier/page-quality.js +14 -0
  154. package/src/verifier/parser.js +2 -241
  155. package/src/verifier/readiness.js +64 -13
  156. package/src/verifier/render-evidence.js +25 -2
  157. package/src/verifier/target.js +201 -0
  158. package/src/verifier/transition.js +18 -3
  159. package/src/verifier/url.js +31 -0
  160. package/src/watch-crawlability.js +144 -0
  161. package/skills/content-defingerprinting/SKILL.md +0 -29
  162. package/skills/content-defingerprinting/scripts/watermarks/README.md +0 -102
  163. package/skills/content-defingerprinting/scripts/watermarks/UPSTREAM.json +0 -26
  164. package/skills/content-defingerprinting/scripts/watermarks/revendor.sh +0 -52
  165. package/skills/content-defingerprinting/scripts/watermarks/vendor/LICENSE.upstream +0 -21
  166. package/skills/content-defingerprinting/scripts/watermarks/vendor/clean_text.py +0 -96
  167. package/skills/content-defingerprinting/scripts/watermarks/vendor/common.py +0 -432
  168. package/skills/content-defingerprinting/scripts/watermarks/vendor/inspect_text.py +0 -86
  169. package/skills/content-defingerprinting/scripts/watermarks/vendor/score_stylometry.py +0 -460
  170. package/skills/content-defingerprinting/scripts/watermarks/vendor/text_unicode.py +0 -651
  171. package/skills/content-defingerprinting/scripts/watermarks/verify-vendor.py +0 -27
  172. package/skills/content-defingerprinting/scripts/watermarks/wm-scan.py +0 -212
  173. package/skills/internal-linking-optimizer/SKILL.md +0 -56
  174. package/skills/internal-linking-optimizer/references/source-prompt.md +0 -76
  175. package/skills/pre-publish-review/SKILL.md +0 -179
  176. package/skills/source-cited-content-builder/SKILL.md +0 -56
  177. package/skills/source-cited-content-builder/references/source-prompt.md +0 -19
  178. package/skills/source-cited-humanizer/SKILL.md +0 -20
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # agentlinkops
2
2
 
3
+ <!-- generated:customer-catalog begin -->
4
+ Pack version 0.6.16. 156 commands in 13 toolsets.
5
+ <!-- generated:customer-catalog end -->
6
+
3
7
  A backlink ledger that lives in your repository. `agentlinkops check` fetches a source page,
4
8
  looks for the link you declared and records an observation with the HTTP status, redirect
5
9
  chain, robots posture and a SHA-256 of the fetched bytes. Page HTML never enters the repository.
@@ -30,7 +34,8 @@ the installed version's help before selecting guidance.
30
34
  `https://app.agentlinkops.com/mcp` as a remote HTTP MCP server in your client.
31
35
  On a version whose help supports selection, use `agentlinkops agent setup --skill ID`
32
36
  or `--recipe ID` for optional guidance; repeat either flag for several choices.
33
- Choose `--all-skills` only for the complete pack. Customer instructions and records
37
+ Choose `--all-skills` only for this product pack's skills. Separate optional packs
38
+ require `--pack DIR`. Customer instructions and records
34
39
  remain the customer's choice.
35
40
 
36
41
  Preview `agentlinkops agent remove --recipe ID` or `--skill ID` before adding
@@ -58,8 +63,8 @@ the installed version's help before selecting guidance.
58
63
 
59
64
  The skill is a short door: the first time your agent uses AgentLinkOps in a session it reads
60
65
  the full agent reference, which `agentlinkops skill` prints and
61
- https://agentlinkops.com/SKILL.md serves. Hosted monitoring is an invitation-only pilot; the
62
- local commands below need no account. The package is `agentlinkops` on npm, published by the
66
+ https://agentlinkops.com/SKILL.md serves. Hosted monitoring is a private pilot, and access opens
67
+ in stages; the local commands below need no account. The package is `agentlinkops` on npm, published by the
63
68
  `agentlinkops` account; the source is https://github.com/sunlover7/agentlinkops-cli.
64
69
 
65
70
  ## Install the CLI on its own
@@ -168,46 +173,16 @@ agentlinkops sync --dry-run
168
173
  agentlinkops sync
169
174
  ```
170
175
 
171
- Hosted access is an invitation-only pilot. The hosted checks, evidence store and MCP server are a
172
- separate service and are not part of this package.
173
-
174
- ## Citation watches
175
-
176
- An AI citation is a placement: a reference to your asset on a surface you do not control,
177
- re-decided by the model on every ask. So the CLI watches it the way it watches a link — a prompt
178
- panel, run against engine APIs on cadence, with dated evidence and honest statistics. A citation
179
- is never reported as "removed": each epoch states the citation rate with a Wilson interval and
180
- its sample size, classified against the previous epoch as declined, grown or
181
- not_distinguishable, or `insufficient_data` when n is too thin to interpret.
176
+ Hosted access opens in stages: anyone can create an account at https://app.agentlinkops.com/sign-up,
177
+ and an account outside the pilot is told it is waiting for access. The hosted checks, evidence store
178
+ and MCP server are a separate service and are not part of this package.
182
179
 
183
- ```sh
184
- agentlinkops citation panel --domain example.com --brand Example --out panel.json
185
- agentlinkops citation run panel.json --max-usd 1.00
186
- ```
180
+ ## Citation commands
187
181
 
188
- New panels use the free mock engine. A live engine requires its own configuration and credentials;
189
- missing credentials never select mock results. The JSON panel contains `targets`, `prompts`,
190
- `engines`, optional `samples` and `maxUsd`. Answer snapshots live in
191
- `.agentlinkops/citations/evidence/`; observation and epoch ledgers live at the `.agentlinkops/` root.
192
- The runner reserves estimated cost before each call and retry. A reported supplier
193
- overrun stops later calls; estimates do not enforce a provider invoice cap. A budget
194
- abort leaves a partial-epoch receipt and exit code 2. A cell
195
- that declined with complete evidence exits 1, like an expected link observed absent.
196
-
197
- Browser measurement requires `AGENTLINKOPS_PROXY_URL` and refuses direct fallback.
198
- For an explicitly authorized direct diagnostic, set `AGENTLINKOPS_BROWSER_EGRESS=direct-diagnostic`
199
- and leave the proxy unset. The standard URL proxy provides no actual usage meter;
200
- its cost remains an estimate. Browser login and live account acceptance are separate.
201
-
202
- Browser epochs retain supplied usage and cleanup receipts in
203
- `.agentlinkops/citations/egress/<epochId>.jsonl`. Unknown meter values stay null. A missing
204
- final record means the journal is incomplete; it does not establish complete costs or cleanup.
205
- Journals stop at 10,000 records or 8 MiB, and a failed write stops further samples.
206
-
207
- Frozen inventories and evidence uploads check files against their retained observation hashes.
208
- Historical hashes verify the recorded answer fields and screenshot filename; they do not prove
209
- when an answer was captured or authenticate old screenshot pixels. Hosted uploads retain a
210
- separate hash of the exact uploaded file bytes.
182
+ The local `agentlinkops citation` commands stay in this package for existing panels.
183
+ AI citation measurement is not part of the AgentLinkOps offer, and the hosted service does
184
+ not list citation commands for customers. `agentlinkops citation help` describes the local
185
+ commands.
211
186
 
212
187
  ## Environment variables
213
188
 
@@ -228,13 +203,24 @@ ledger directory are still accepted with a one-line notice on stderr until 2026-
228
203
  ## Agent plugin
229
204
 
230
205
  The package doubles as a plugin: `.claude-plugin/plugin.json` and `.codex-plugin/plugin.json`
231
- point at `skills/`, which holds the connect, CRM, campaign and asset skills plus the
232
- content-pipeline skills, with templates in `templates/` and contracts in `references/`.
233
- `agentlinkops agent setup` installs the same skills for any detected client, and the discovery
234
- index at `https://agentlinkops.com/.well-known/agent-skills/index.json` serves them to
235
- `npx skills add` (the content pipeline sits behind `https://agentlinkops.com/content`). Node.js
236
- runs the ledger CLI; Python 3.10 or later runs the optional CRM helper and the watermark
237
- scanner.
206
+ point at five product skills in `skills/`: connect, discovery, campaigns, CRM and assets.
207
+ `agentlinkops agent setup` keeps the connection-only default; `--skill` and `--recipe`
208
+ add explicit selections. `--all-skills` selects this pack's five skills. Upgrades retain
209
+ older optional selections and customer edits until explicit removal.
210
+
211
+ The separate optional content pack contains builder, humanizer, text cleanup,
212
+ internal-linking and pre-publish guidance. Its source-rights and portable-dependency
213
+ qualification remain unresolved; the product build preserves its
214
+ older published index and archives without qualifying them. When a separately qualified
215
+ pack is available, `--pack DIR` selects its local skills directory. An optional pack alone
216
+ does not configure AgentLinkOps MCP. The existing product discovery index and content
217
+ index paths remain unchanged.
218
+
219
+ Each standalone product archive includes its required references and Python resources.
220
+ Its dependency receipt declares the separately installed CLI, selected recipes and
221
+ customer tools. Node.js runs the CLI; Python 3.10 or later runs the optional CRM helper.
222
+ Inspect the installed version and help before using a local feature. Refreshing the
223
+ hosted command catalog does not install a newer CLI.
238
224
 
239
225
  ## What it does not do
240
226
 
@@ -69,7 +69,7 @@ export const SUPPLIERS = Object.freeze({
69
69
  },
70
70
  google_search_console: {
71
71
  label: 'Google Search Console linking pages export',
72
- source: alias('Linking page', 'Linking Page', 'Source page'),
72
+ source: alias('Linking page', 'Linking Page', 'Source page', 'Links', 'Source URL'),
73
73
  target: alias('Target page', 'Target Page', 'Linked page'),
74
74
  last_seen: alias('Last crawled', 'Last crawled date'),
75
75
  // GSC's headline exports are COUNTS PER SITE, not placements. Importing one would mean
@@ -17,13 +17,15 @@ import { parseArgs } from "./args.js";
17
17
  import { ConfigError } from "./config.js";
18
18
  import { AGENT_CLIENTS, clientById } from "../shared/agent-clients.js";
19
19
  import { NUDGE } from "./skill.js";
20
+ import { STORED_KEY } from "./key-store.js";
20
21
 
21
22
  // DP-0036-T09: `agentlinkops agent setup` (Stripe's `stripe agent setup` shape). It detects the
22
23
  // agent clients on this machine, installs the skill pack at each client's path with digest
23
24
  // verification, and writes the MCP entry for the view that client should use, from the one
24
- // client table in shared/agent-clients.js. It stores no credential: OAuth happens in the
25
- // client. It is idempotent, never overwrites a file it did not write, and prints exactly what
26
- // changed. `agent status` reports what is installed without touching anything.
25
+ // client table in shared/agent-clients.js. It stores no credential itself: OAuth happens in the
26
+ // client, and on a terminal the caller may offer to connect the CLI too (connectCli), which saves
27
+ // a pasted key outside the repository. It is idempotent, never overwrites a file it did not write,
28
+ // and prints exactly what changed. `agent status` reports what is installed without touching anything.
27
29
  const USAGE =
28
30
  "agent setup [--client ID ...] [--scope project|user] [--origin URL] [--pack DIR] [--skill ID ...] [--recipe ID ...] [--all-skills] [--dry-run] [--json] | agent status [--json] | agent remove [--skill ID ...] [--recipe ID ...] [--scope project|user] [--apply] [--json] | agent recover [--scope project|user] [--apply] [--json]";
29
31
  export const DEFAULT_ORIGIN = "https://app.agentlinkops.com";
@@ -31,6 +33,7 @@ export const SERVER_NAME = "agentlinkops";
31
33
  const RECEIPT = ".agentlinkops/agent-setup.json";
32
34
  const SHARED = Symbol("package siblings");
33
35
  const SELECTION = Symbol("selected payloads");
36
+ const ARTIFACT = Symbol("pack artifact contract");
34
37
  const CORE_SKILL = "agentlinkops-connect";
35
38
  const selectionIds = (value, flag) => {
36
39
  const ids = value === undefined ? [] : [].concat(value);
@@ -187,6 +190,38 @@ export async function readPack(dir) {
187
190
  "Skill package exceeds the 10000-file/32MiB install bound.",
188
191
  );
189
192
  Object.defineProperty(skills, SHARED, { value: siblings });
193
+ await safePath(join(dir, "..", "PACK-ARTIFACT.json"));
194
+ await safePath(join(dir, "..", "PACKS.json"));
195
+ const artifactBytes = await bytesAt(join(dir, "..", "PACK-ARTIFACT.json"));
196
+ const contractBytes = await bytesAt(join(dir, "..", "PACKS.json"));
197
+ if (artifactBytes || contractBytes) {
198
+ let artifact, contract;
199
+ try {
200
+ contract = JSON.parse(contractBytes);
201
+ // The maintained source tree has PACKS but no generated artifact receipt.
202
+ // Its explicit default still uses additive product selection; missing
203
+ // generated metadata must not restore the old prune-everything mode.
204
+ artifact = artifactBytes ? JSON.parse(artifactBytes) : {
205
+ schemaVersion:1,pack:contract.defaultPack,skills:contract.packs?.[contract.defaultPack]?.skills,
206
+ defaultCliSkills:contract.packs?.[contract.defaultPack]?.defaultCliSkills,
207
+ mcp:contract.packs?.[contract.defaultPack]?.mcp,contractSha256:sha256(JSON.stringify(contract)),
208
+ selectionSource:'maintained manifest; no generated artifact qualification',
209
+ };
210
+ } catch { throw new ConfigError("The selected pack metadata is unavailable or invalid."); }
211
+ const selected = contract?.packs?.[artifact?.pack];
212
+ if (!artifact || artifact.schemaVersion !== 1 || contract?.schemaVersion !== 1 || !["product", "content"].includes(artifact.pack)
213
+ || !selected || selected.sourcePolicy !== (artifact.pack === "product" ? "maintained-product-source" : "reviewed-portable-source")
214
+ || artifact.contractSha256 !== sha256(JSON.stringify(contract))
215
+ || !Array.isArray(artifact.skills) || !Array.isArray(artifact.defaultCliSkills)
216
+ || !isDeepStrictEqual(artifact.skills, selected.skills) || !isDeepStrictEqual([...artifact.skills].sort(), Object.keys(skills).sort())
217
+ || !isDeepStrictEqual(artifact.defaultCliSkills, selected.defaultCliSkills) || artifact.mcp !== selected.mcp
218
+ || artifact.mcp !== (artifact.pack === "product")
219
+ || !isDeepStrictEqual(artifact.defaultCliSkills, artifact.pack === "product" ? [CORE_SKILL] : [])
220
+ || artifact.defaultCliSkills.some(id => !Object.hasOwn(skills,id))
221
+ || artifact.skills.some(id => contract.dependencies?.[id]?.unresolved?.length))
222
+ throw new ConfigError("The selected pack metadata disagrees with its files or source hold.");
223
+ Object.defineProperty(skills, ARTIFACT, {value:artifact});
224
+ }
190
225
  return skills;
191
226
  }
192
227
 
@@ -195,12 +230,20 @@ export async function readPack(dir) {
195
230
  export function selectPack(pack, { skills = [], recipes = [], all = false } = {}) {
196
231
  if (all && (skills.length || recipes.length))
197
232
  throw new ConfigError("Choose --all-skills or named --skill/--recipe selections.");
198
- if (!Object.hasOwn(pack, CORE_SKILL) || !pack[CORE_SKILL]["SKILL.md"]?.bytes.length)
233
+ const artifact = pack[ARTIFACT], connects = artifact?.mcp !== false;
234
+ if (connects && (!Object.hasOwn(pack, CORE_SKILL) || !pack[CORE_SKILL]["SKILL.md"]?.bytes.length))
199
235
  throw new ConfigError("The core connection skill is missing from the package.");
200
- if (!pack[CORE_SKILL]["references/agentlinkops.md"]?.bytes.length)
236
+ if (connects && !pack[CORE_SKILL]["references/agentlinkops.md"]?.bytes.length)
201
237
  throw new ConfigError("The core connection reference is missing from the package.");
202
- if (all) return { pack, selection: { mode: "all", skills: Object.keys(pack), recipes: [] } };
203
- const selected = new Set([CORE_SKILL, ...skills]);
238
+ if (all) {
239
+ // A new product-only artifact must not prune optional payloads installed by
240
+ // an older mixed bundle. Legacy packs retain their existing explicit mode.
241
+ if (artifact) Object.defineProperty(pack, SELECTION, {value:true});
242
+ return { pack, selection: { mode: "all", skills: Object.keys(pack), recipes: [] } };
243
+ }
244
+ const selected = new Set([...(artifact?.defaultCliSkills ?? [CORE_SKILL]), ...skills]);
245
+ if (!connects && (!selected.size || recipes.length))
246
+ throw new ConfigError("Select optional pack skills with --skill ID or --all-skills; product recipes are a separate selection.");
204
247
  const siblings = pack[SHARED] ?? {};
205
248
  let catalog = null;
206
249
  if (recipes.length) {
@@ -239,6 +282,7 @@ export function selectPack(pack, { skills = [], recipes = [], all = false } = {}
239
282
  }
240
283
  Object.defineProperty(result, SHARED, { value: support });
241
284
  Object.defineProperty(result, SELECTION, { value: true });
285
+ if (artifact) Object.defineProperty(result, ARTIFACT, {value:artifact});
242
286
  return { pack: result, selection: { mode: "selected", skills: [...selected], recipes: chosenRecipes } };
243
287
  }
244
288
 
@@ -900,6 +944,7 @@ export async function agentMain(
900
944
  packDir = null,
901
945
  now = () => new Date().toISOString(),
902
946
  afterOperation,
947
+ connectCli = null,
903
948
  } = {},
904
949
  ) {
905
950
  const args = parseArgs(argv, ["client", "skill", "recipe"]);
@@ -1109,9 +1154,9 @@ export async function agentMain(
1109
1154
  scope,
1110
1155
  receipt,
1111
1156
  });
1112
- for (const path of skills.desired) desired.add(path);
1113
- for (const path of skills.roots) selectedRoots.add(path);
1114
- const mcp = await planMcp(client, { cwd, home, origin, scope });
1157
+ const mcp = pack[ARTIFACT]?.mcp === false
1158
+ ? {action:"not selected",file:null,url:null}
1159
+ : await planMcp(client, { cwd, home, origin, scope });
1115
1160
  const entry = {
1116
1161
  id: client.id,
1117
1162
  name: client.name,
@@ -1130,6 +1175,19 @@ export async function agentMain(
1130
1175
  ...(mcp.snippet ? { snippet: mcp.snippet } : {}),
1131
1176
  },
1132
1177
  };
1178
+ // Autodetection is a convenience, not one shared permission boundary. Leave a
1179
+ // conflicting client's files and receipt ownership alone while installing
1180
+ // independent clients. An explicit multi-client selection remains atomic.
1181
+ if (!only && (skills.conflicts.length || mcp.action === "conflict")) {
1182
+ entry.skipped = true;
1183
+ if (mcp.action === "write") entry.mcp.action = "not applied";
1184
+ if (mcp.action === "conflict") report.next.push(`${client.name}: ${mcp.note}`);
1185
+ for (const c of skills.conflicts) report.next.push(`${client.name}: ${c.target} ${c.note}`);
1186
+ report.clients.push(entry);
1187
+ continue;
1188
+ }
1189
+ for (const path of skills.desired) desired.add(path);
1190
+ for (const path of skills.roots) selectedRoots.add(path);
1133
1191
  for (const item of skills.install) {
1134
1192
  operations.set(item.target, {
1135
1193
  path: item.target,
@@ -1191,9 +1249,11 @@ export async function agentMain(
1191
1249
  const conflicts = report.clients.some(
1192
1250
  (c) => c.skills.conflicts.length || c.mcp.action === "conflict",
1193
1251
  );
1252
+ const readyClients = report.clients.filter(c => !c.skipped);
1253
+ const blocked = conflicts && (Boolean(only) || readyClients.length === 0);
1194
1254
  if (
1195
1255
  !report.dryRun &&
1196
- !conflicts &&
1256
+ !blocked &&
1197
1257
  (operations.size || ownershipChanged || !receipt)
1198
1258
  ) {
1199
1259
  const oldReceipt = await bytesAt(paths.receipt),
@@ -1203,7 +1263,7 @@ export async function agentMain(
1203
1263
  installedAt: now(),
1204
1264
  origin,
1205
1265
  scope,
1206
- clients: report.clients.map((c) => c.id),
1266
+ clients: readyClients.map((c) => c.id),
1207
1267
  lastSetupSelection: selection,
1208
1268
  files: ownedFiles,
1209
1269
  mcp: ownedMcp,
@@ -1225,7 +1285,7 @@ export async function agentMain(
1225
1285
  for (const c of report.clients)
1226
1286
  if (c.mcp.action === "planned") c.mcp.action = "written";
1227
1287
  }
1228
- if (conflicts) {
1288
+ if (blocked) {
1229
1289
  report.pruned = [];
1230
1290
  report.next.push(
1231
1291
  "Conflicts prevented all installation writes. Resolve them and retry.",
@@ -1235,14 +1295,38 @@ export async function agentMain(
1235
1295
  if (c.mcp.action === "planned") c.mcp.action = "not applied";
1236
1296
  }
1237
1297
  }
1238
- report.next.push(
1239
- "For a chosen hosted task, sign in through the client's MCP OAuth flow. Local supplied-link checks require no AgentLinkOps account; no credential was stored by this command.",
1298
+ if (conflicts && !blocked) report.next.push(
1299
+ report.dryRun
1300
+ ? "Conflicting clients would be skipped; other clients can be installed."
1301
+ : "Conflicting clients were skipped; other clients were installed. Retry a skipped client with --client ID after resolving its conflicts.",
1302
+ );
1303
+ const connects = pack[ARTIFACT]?.mcp !== false;
1304
+ if (!blocked && connects) {
1305
+ // The plain next step: where the sign-in happens, then where to approve it.
1306
+ for (const c of readyClients)
1307
+ report.next.push(c.id === "claude-code"
1308
+ ? `Claude Code: open it${scope === "project" ? " in this folder" : ""}, run /mcp, select ${SERVER_NAME} and choose Authenticate.`
1309
+ : `${c.name}: sign in when it first connects to ${SERVER_NAME}; the client opens the sign-in page.`);
1310
+ report.next.push(`Then approve the agent's request on the Agent page: ${origin}/#/access`);
1311
+ }
1312
+ const offerCli = connects && !json && !report.dryRun && !blocked && typeof connectCli === "function";
1313
+ if (connects) report.next.push(
1314
+ `For a chosen hosted task, sign in through the client's MCP OAuth flow. Local supplied-link checks require no AgentLinkOps account; ${offerCli
1315
+ ? "this command stores a credential only if you paste a CLI key below."
1316
+ : "no credential was stored by this command."}`,
1240
1317
  );
1318
+ const cliReady = connects && (["AGENTLINKOPS_API_KEY", "AGENTLINKOPS_TOKEN", "LINKTRAIL_API_KEY", "LINKTRAIL_TOKEN"].some((name) => env[name]) ||
1319
+ !!env[STORED_KEY]?.(origin));
1320
+ if (connects && !cliReady && !offerCli)
1321
+ report.next.push(`To use the agentlinkops CLI too, run \`npx -y agentlinkops connect\` in a terminal and paste an API key from ${origin}/#/access.`);
1241
1322
  if (report.retainedExisting.length)
1242
1323
  report.next.push("Earlier payloads outside this selection were retained. Preview agent remove --skill ID or --recipe ID to remove them explicitly.");
1243
- report.next.push("Optional skills and recipes: agent setup --skill ID or --recipe ID; --all-skills installs the complete pack.");
1244
- report.next.push(NUDGE);
1324
+ report.next.push(connects
1325
+ ? "Select available product skills or recipes with --skill ID or --recipe ID. --all-skills installs this pack's skills; separate optional packs require --pack DIR."
1326
+ : "This optional selection does not configure AgentLinkOps MCP. Use --pack DIR and --skill ID or --all-skills to select its available skills.");
1327
+ if (connects) report.next.push(NUDGE);
1245
1328
  out(json ? JSON.stringify(report, null, 2) : renderSetup(report));
1329
+ if (offerCli) await connectCli(origin);
1246
1330
  return report.clients.some(
1247
1331
  (c) => c.skills.conflicts.length || c.mcp.action === "conflict",
1248
1332
  )