agentlinkops 0.6.11 → 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 (311) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/.codex-plugin/plugin.json +2 -2
  3. package/.mcp.json +0 -0
  4. package/CHANGELOG.md +35 -0
  5. package/LICENSE +0 -0
  6. package/NOTICE +0 -0
  7. package/PACK-ARTIFACT.json +16 -0
  8. package/PACKS.json +591 -0
  9. package/README.md +34 -48
  10. package/bin/agentlinkops.mjs +0 -0
  11. package/bin/linktrail.mjs +0 -0
  12. package/cli/adapters/csv.js +0 -0
  13. package/cli/adapters/sqlite-crm.js +0 -0
  14. package/cli/adapters/suppliers.js +1 -1
  15. package/cli/admission.js +0 -0
  16. package/cli/agent-setup.js +101 -17
  17. package/cli/agentlinkops.mjs +0 -0
  18. package/cli/args.js +0 -0
  19. package/cli/campaign-bridge.js +325 -0
  20. package/cli/campaign-outcomes.js +107 -0
  21. package/cli/catalog.js +47686 -16398
  22. package/cli/check.js +40 -10
  23. package/cli/chronology.js +39 -0
  24. package/cli/citation.js +0 -0
  25. package/cli/client.js +2 -1
  26. package/cli/cloud-error.js +0 -0
  27. package/cli/commands.js +17 -8
  28. package/cli/config.js +0 -0
  29. package/cli/connection.js +92 -18
  30. package/cli/contact-adoption.js +91 -0
  31. package/cli/context-mcp.js +0 -0
  32. package/cli/context.js +13 -2
  33. package/cli/delivery.js +0 -0
  34. package/cli/disavow.js +0 -0
  35. package/cli/doctor.js +15 -4
  36. package/cli/env.js +0 -0
  37. package/cli/fleet.js +30 -19
  38. package/cli/gsc-browser.js +0 -0
  39. package/cli/import.js +7 -1
  40. package/cli/index-observations.js +0 -0
  41. package/cli/index-submissions.js +0 -0
  42. package/cli/key-store.js +115 -0
  43. package/cli/ledger.js +1 -0
  44. package/cli/lifecycle.js +0 -0
  45. package/cli/linktrail.mjs +0 -0
  46. package/cli/local-result.js +41 -6
  47. package/cli/main.js +85 -20
  48. package/cli/mcp-client.js +2 -2
  49. package/cli/migration.js +308 -0
  50. package/cli/mirror.js +0 -0
  51. package/cli/mix.js +0 -0
  52. package/cli/native-lock.js +0 -0
  53. package/cli/oauth-credentials.js +0 -0
  54. package/cli/oauth-flow.js +0 -0
  55. package/cli/placement-projection.js +106 -0
  56. package/cli/platforms.js +0 -0
  57. package/cli/prompt.js +25 -0
  58. package/cli/public-http.js +168 -0
  59. package/cli/receipt-performance.js +0 -0
  60. package/cli/receipts.js +0 -0
  61. package/cli/report.js +51 -19
  62. package/cli/setup-plan.js +0 -0
  63. package/cli/skill.js +0 -0
  64. package/cli/state.js +76 -29
  65. package/cli/status.js +14 -18
  66. package/cli/sync-lock.js +0 -0
  67. package/cli/sync.js +23 -4
  68. package/package.json +7 -8
  69. package/references/citation-panels.md +2 -0
  70. package/references/cli.md +2 -2
  71. package/references/disavow.md +1 -1
  72. package/references/discovery-handoff.md +1 -1
  73. package/references/local-crm-draft-handoff.md +1 -1
  74. package/references/portable-ledger-handoff.md +11 -10
  75. package/references/recipes/catalog.json +263 -7
  76. package/references/recipes/command-notes.md +5 -3
  77. package/references/recipes/internal-link-improvement.md +25 -0
  78. package/references/recipes/placement-reconciliation.md +53 -1
  79. package/references/recipes/qualified-campaign-handoff.md +0 -0
  80. package/references/recipes/site-context-brief.md +0 -0
  81. package/references/recipes/sourced-linkable-asset.md +0 -0
  82. package/references/reports.md +0 -0
  83. package/scripts/agentlinkops.py +0 -0
  84. package/scripts/disavow.py +0 -0
  85. package/scripts/linktrail.py +0 -0
  86. package/scripts/migrations/001_crm.sql +0 -0
  87. package/scripts/migrations/002_cloud_events.sql +0 -0
  88. package/scripts/migrations/003_disavow.sql +0 -0
  89. package/scripts/migrations/004_placement_costs.sql +0 -0
  90. package/scripts/outreach-crm-draft.py +0 -0
  91. package/scripts/portable-handoff.py +17 -4
  92. package/scripts/report.py +0 -0
  93. package/shared/admission-contract.js +3 -2
  94. package/shared/admission-report-contract.js +3 -2
  95. package/shared/agent-clients.js +0 -0
  96. package/shared/analytics-history-contract.js +15 -0
  97. package/shared/campaign-outcome-contract.js +12 -0
  98. package/shared/campaign-prospect-contract.js +23 -0
  99. package/shared/citation-evidence-contract.js +0 -0
  100. package/shared/classification-contract.js +54 -0
  101. package/shared/contact-record-contract.js +27 -0
  102. package/shared/disavow-contract.js +37 -9
  103. package/shared/exec-grammar.js +0 -0
  104. package/shared/index-observation-contract.js +5 -4
  105. package/shared/internal-links-contract.js +143 -0
  106. package/shared/lifecycle-contract.js +0 -0
  107. package/shared/lifecycle-money.js +0 -0
  108. package/shared/link-category-contract.js +35 -0
  109. package/shared/link-category.js +138 -0
  110. package/shared/link-labels.js +26 -0
  111. package/shared/link-summary-contract.js +17 -0
  112. package/shared/monitoring-chronology-export.js +57 -0
  113. package/shared/monitoring-read-contract.js +41 -0
  114. package/shared/observation-event.js +1 -1
  115. package/shared/onboarding.js +20 -0
  116. package/shared/opportunity-record.js +0 -0
  117. package/shared/response-contract.js +41 -5
  118. package/shared/search-connection-contract.js +2 -1
  119. package/shared/table-page-contract.js +13 -0
  120. package/skills/agentlinkops-assets/SKILL-DEPS.json +290 -0
  121. package/skills/agentlinkops-assets/SKILL.md +3 -1
  122. package/skills/agentlinkops-assets/agents/openai.yaml +1 -1
  123. package/skills/agentlinkops-assets/references/portable/references/cli.md +220 -0
  124. package/skills/agentlinkops-assets/references/portable/references/disavow.md +73 -0
  125. package/skills/agentlinkops-assets/references/portable/references/reports.md +53 -0
  126. package/skills/agentlinkops-assets/references/portable/scripts/agentlinkops.py +676 -0
  127. package/skills/agentlinkops-assets/references/portable/scripts/disavow.py +207 -0
  128. package/skills/agentlinkops-assets/references/portable/scripts/migrations/001_crm.sql +45 -0
  129. package/skills/agentlinkops-assets/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  130. package/skills/agentlinkops-assets/references/portable/scripts/migrations/003_disavow.sql +20 -0
  131. package/skills/agentlinkops-assets/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  132. package/skills/agentlinkops-assets/references/portable/scripts/report.py +113 -0
  133. package/skills/agentlinkops-assets/references/portable/templates/campaigns.v1.json +65 -0
  134. package/skills/agentlinkops-campaigns/SKILL-DEPS.json +336 -0
  135. package/skills/agentlinkops-campaigns/SKILL.md +7 -5
  136. package/skills/agentlinkops-campaigns/agents/openai.yaml +1 -1
  137. package/skills/agentlinkops-campaigns/references/first-campaign.md +31 -0
  138. package/skills/agentlinkops-campaigns/references/mailbox-campaign.md +46 -0
  139. package/skills/agentlinkops-campaigns/references/portable/references/cli.md +220 -0
  140. package/skills/agentlinkops-campaigns/references/portable/references/disavow.md +73 -0
  141. package/skills/agentlinkops-campaigns/references/portable/references/reports.md +53 -0
  142. package/skills/agentlinkops-campaigns/references/portable/scripts/agentlinkops.py +676 -0
  143. package/skills/agentlinkops-campaigns/references/portable/scripts/disavow.py +207 -0
  144. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/001_crm.sql +45 -0
  145. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  146. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/003_disavow.sql +20 -0
  147. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  148. package/skills/agentlinkops-campaigns/references/portable/scripts/report.py +113 -0
  149. package/skills/agentlinkops-campaigns/references/portable/templates/campaigns.v1.json +65 -0
  150. package/skills/agentlinkops-connect/SKILL-DEPS.json +104 -0
  151. package/skills/agentlinkops-connect/SKILL.md +2 -0
  152. package/skills/agentlinkops-connect/agents/openai.yaml +1 -1
  153. package/skills/agentlinkops-connect/references/agentlinkops.md +164 -61
  154. package/skills/agentlinkops-connect/references/placement-workflows.md +19 -0
  155. package/skills/agentlinkops-crm/SKILL-DEPS.json +381 -0
  156. package/skills/agentlinkops-crm/SKILL.md +26 -9
  157. package/skills/agentlinkops-crm/agents/openai.yaml +1 -1
  158. package/skills/agentlinkops-crm/references/portable/references/cli.md +220 -0
  159. package/skills/agentlinkops-crm/references/portable/references/disavow.md +73 -0
  160. package/skills/agentlinkops-crm/references/portable/references/local-crm-draft-handoff.md +76 -0
  161. package/skills/agentlinkops-crm/references/portable/references/portable-ledger-handoff.md +51 -0
  162. package/skills/agentlinkops-crm/references/portable/references/reports.md +53 -0
  163. package/skills/agentlinkops-crm/references/portable/scripts/agentlinkops.py +676 -0
  164. package/skills/agentlinkops-crm/references/portable/scripts/disavow.py +207 -0
  165. package/skills/agentlinkops-crm/references/portable/scripts/migrations/001_crm.sql +45 -0
  166. package/skills/agentlinkops-crm/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  167. package/skills/agentlinkops-crm/references/portable/scripts/migrations/003_disavow.sql +20 -0
  168. package/skills/agentlinkops-crm/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  169. package/skills/agentlinkops-crm/references/portable/scripts/outreach-crm-draft.py +121 -0
  170. package/skills/agentlinkops-crm/references/portable/scripts/portable-handoff.py +197 -0
  171. package/skills/agentlinkops-crm/references/portable/scripts/report.py +113 -0
  172. package/skills/agentlinkops-crm/references/portable/templates/campaigns.v1.json +65 -0
  173. package/skills/agentlinkops-discovery/SKILL-DEPS.json +301 -0
  174. package/skills/agentlinkops-discovery/SKILL.md +4 -2
  175. package/skills/agentlinkops-discovery/references/portable/references/cli.md +220 -0
  176. package/skills/agentlinkops-discovery/references/portable/references/disavow.md +73 -0
  177. package/skills/agentlinkops-discovery/references/portable/references/discovery-handoff.md +32 -0
  178. package/skills/agentlinkops-discovery/references/portable/references/reports.md +53 -0
  179. package/skills/agentlinkops-discovery/references/portable/scripts/agentlinkops.py +676 -0
  180. package/skills/agentlinkops-discovery/references/portable/scripts/disavow.py +207 -0
  181. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/001_crm.sql +45 -0
  182. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  183. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/003_disavow.sql +20 -0
  184. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  185. package/skills/agentlinkops-discovery/references/portable/scripts/report.py +113 -0
  186. package/skills/agentlinkops-discovery/references/portable/templates/campaigns.v1.json +65 -0
  187. package/skills/agentlinkops-discovery/references/saved-backlink-workflows.md +23 -0
  188. package/src/citations/adapter.js +0 -0
  189. package/src/citations/alert.js +0 -0
  190. package/src/citations/browser/chatgpt-citations.js +0 -0
  191. package/src/citations/browser/engine.js +0 -0
  192. package/src/citations/browser/gen/core/providers/_shared/rawSourcesDom.js +0 -0
  193. package/src/citations/browser/gen/core/providers/_shared/resetProviderPage.js +0 -0
  194. package/src/citations/browser/gen/core/providers/_shared/sourceUtils.js +0 -0
  195. package/src/citations/browser/gen/core/providers/ai-overview/index.js +0 -0
  196. package/src/citations/browser/gen/core/providers/ai-overview/lib/expand.js +0 -0
  197. package/src/citations/browser/gen/core/providers/ai-overview/lib/extractSources.js +0 -0
  198. package/src/citations/browser/gen/core/providers/ai-overview/lib/pageLifecycle.js +0 -0
  199. package/src/citations/browser/gen/core/providers/ai-overview/lib/session.js +0 -0
  200. package/src/citations/browser/gen/core/providers/chatgpt/index.js +0 -0
  201. package/src/citations/browser/gen/core/providers/chatgpt/lib/dismissAuthModal.js +0 -0
  202. package/src/citations/browser/gen/core/providers/chatgpt/lib/extractSources.js +0 -0
  203. package/src/citations/browser/gen/core/providers/chatgpt/lib/pageLifecycle.js +0 -0
  204. package/src/citations/browser/gen/core/providers/claude/index.js +0 -0
  205. package/src/citations/browser/gen/core/providers/claude/lib/extractSources.js +0 -0
  206. package/src/citations/browser/gen/core/providers/claude/lib/pageLifecycle.js +0 -0
  207. package/src/citations/browser/gen/core/providers/gemini/index.js +0 -0
  208. package/src/citations/browser/gen/core/providers/gemini/lib/extractSources.js +0 -0
  209. package/src/citations/browser/gen/core/providers/gemini/lib/pageLifecycle.js +0 -0
  210. package/src/citations/browser/gen/core/providers/gemini/lib/session.js +0 -0
  211. package/src/citations/browser/gen/core/providers/index.js +0 -0
  212. package/src/citations/browser/gen/core/providers/perplexity/index.js +0 -0
  213. package/src/citations/browser/gen/core/providers/perplexity/lib/extractSources.js +0 -0
  214. package/src/citations/browser/gen/core/providers/perplexity/lib/navigation.js +0 -0
  215. package/src/citations/browser/gen/lib/browser/camoufox.js +0 -0
  216. package/src/citations/browser/gen/lib/browser/display.js +0 -0
  217. package/src/citations/browser/gen/lib/browser/domOps.js +0 -0
  218. package/src/citations/browser/gen/lib/browser/humanBehavior.js +0 -0
  219. package/src/citations/browser/gen/lib/browser/navigate.js +0 -0
  220. package/src/citations/browser/gen/lib/extraction/sourceUtils.js +0 -0
  221. package/src/citations/browser/gen/lib/input/editor/clearInput.js +0 -0
  222. package/src/citations/browser/gen/lib/input/editor/findEditor.js +0 -0
  223. package/src/citations/browser/gen/lib/input/editor/promptInput.js +0 -0
  224. package/src/citations/browser/gen/lib/input/markdown/converter.js +0 -0
  225. package/src/citations/browser/gen/lib/input/markdown/toMarkdown.js +0 -0
  226. package/src/citations/browser/gen/lib/input/response/detectBotPage.js +0 -0
  227. package/src/citations/browser/gen/lib/input/response/isGenerating.js +0 -0
  228. package/src/citations/browser/gen/lib/input/response/waitForFinish.js +0 -0
  229. package/src/citations/browser/gen/lib/input/sources/findButton.js +0 -0
  230. package/src/citations/browser/gen/lib/input/sources/openPanel.js +0 -0
  231. package/src/citations/browser/gen/utils/agent-constants.js +0 -0
  232. package/src/citations/browser/providers.js +0 -0
  233. package/src/citations/browser/proxy-admission.js +0 -0
  234. package/src/citations/browser/shims/errors.js +0 -0
  235. package/src/citations/browser/shims/types.js +0 -0
  236. package/src/citations/browser/shims/utils.js +0 -0
  237. package/src/citations/contract.js +0 -0
  238. package/src/citations/egress-receipts.js +0 -0
  239. package/src/citations/evidence-integrity.js +0 -0
  240. package/src/citations/inventory.js +0 -0
  241. package/src/citations/login.js +0 -0
  242. package/src/citations/match.js +0 -0
  243. package/src/citations/panel-builder.js +0 -0
  244. package/src/citations/report.js +0 -0
  245. package/src/citations/runner.js +0 -0
  246. package/src/citations/stats.js +0 -0
  247. package/src/citations/sync.js +0 -0
  248. package/src/citations/watch.js +0 -0
  249. package/src/competitors/contract.js +17 -1
  250. package/src/competitors/domain-mix.js +0 -0
  251. package/src/context/ga4.js +0 -0
  252. package/src/context/gsc.js +0 -0
  253. package/src/context/site-profile.js +34 -121
  254. package/src/context/util.js +0 -0
  255. package/src/context-tool-registry.js +0 -0
  256. package/src/corpus/acquire.js +68 -32
  257. package/src/corpus/extract.js +147 -0
  258. package/src/corpus/frontier.js +151 -0
  259. package/src/corpus/seeds.js +0 -0
  260. package/src/discovery/contract.js +8 -0
  261. package/src/discovery/import.js +13 -1
  262. package/src/evidence-reference.js +0 -0
  263. package/src/index-observation.js +0 -0
  264. package/src/locator.js +0 -0
  265. package/src/mentions.js +193 -0
  266. package/src/page-acquisition/acquire.js +50 -0
  267. package/src/page-acquisition/bytes.js +138 -0
  268. package/src/page-acquisition/capture.js +11 -0
  269. package/src/page-acquisition/contracts.js +67 -0
  270. package/src/page-acquisition/documents.js +166 -0
  271. package/src/page-acquisition/http.js +307 -0
  272. package/src/page-acquisition/policy.js +20 -0
  273. package/src/page-acquisition/recovery.js +164 -0
  274. package/src/page-acquisition/render-failure.js +82 -0
  275. package/src/page-extraction/html.js +359 -0
  276. package/src/page-extraction/index.js +30 -0
  277. package/src/page-extraction/metadata.js +16 -0
  278. package/src/response-shaping.js +4 -3
  279. package/src/tool-views.js +23 -12
  280. package/src/verifier/fetch.js +4 -339
  281. package/src/verifier/index.js +81 -23
  282. package/src/verifier/observation-kind.js +0 -0
  283. package/src/verifier/page-quality.js +14 -0
  284. package/src/verifier/parser.js +2 -241
  285. package/src/verifier/readiness.js +64 -13
  286. package/src/verifier/render-evidence.js +25 -2
  287. package/src/verifier/robots.js +0 -0
  288. package/src/verifier/target.js +201 -0
  289. package/src/verifier/transition.js +18 -3
  290. package/src/verifier/url.js +31 -0
  291. package/src/watch-crawlability.js +144 -0
  292. package/templates/SITE.template.md +0 -0
  293. package/templates/campaigns.v1.json +0 -0
  294. package/skills/content-defingerprinting/SKILL.md +0 -29
  295. package/skills/content-defingerprinting/scripts/watermarks/README.md +0 -102
  296. package/skills/content-defingerprinting/scripts/watermarks/UPSTREAM.json +0 -26
  297. package/skills/content-defingerprinting/scripts/watermarks/revendor.sh +0 -52
  298. package/skills/content-defingerprinting/scripts/watermarks/vendor/LICENSE.upstream +0 -21
  299. package/skills/content-defingerprinting/scripts/watermarks/vendor/clean_text.py +0 -96
  300. package/skills/content-defingerprinting/scripts/watermarks/vendor/common.py +0 -432
  301. package/skills/content-defingerprinting/scripts/watermarks/vendor/inspect_text.py +0 -86
  302. package/skills/content-defingerprinting/scripts/watermarks/vendor/score_stylometry.py +0 -460
  303. package/skills/content-defingerprinting/scripts/watermarks/vendor/text_unicode.py +0 -651
  304. package/skills/content-defingerprinting/scripts/watermarks/verify-vendor.py +0 -27
  305. package/skills/content-defingerprinting/scripts/watermarks/wm-scan.py +0 -212
  306. package/skills/internal-linking-optimizer/SKILL.md +0 -56
  307. package/skills/internal-linking-optimizer/references/source-prompt.md +0 -76
  308. package/skills/pre-publish-review/SKILL.md +0 -179
  309. package/skills/source-cited-content-builder/SKILL.md +0 -56
  310. package/skills/source-cited-content-builder/references/source-prompt.md +0 -19
  311. package/skills/source-cited-humanizer/SKILL.md +0 -20
package/cli/report.js CHANGED
@@ -18,13 +18,15 @@
18
18
  // evidence reference that resolves to the observation itself. One dataset feeds both exports:
19
19
  // this HTML view for a human client, and `report --json` for an agent, so the two can never
20
20
  // disagree about what was observed.
21
+ // v3 freezes activity separately from retained receipts. Older v2 datasets still render with
22
+ // their original columns and wording; activity never changes an old observation's date/hash.
21
23
  import {indexRowsForUrl} from './index-observations.js';
22
24
  import {reconcileIndexObservations} from '../src/index-observation.js';
23
25
  import { createHash } from 'node:crypto';
24
- import { latestByEntry } from './mirror.js';
26
+ import { projectPlacements } from './placement-projection.js';
25
27
  import { mirrorEvidenceReference } from '../src/evidence-reference.js';
26
28
 
27
- export const REPORT_VERSION = 2;
29
+ export const REPORT_VERSION = 3;
28
30
 
29
31
  /** HTML-escape. Every value below is publisher or customer content and none of it is trusted. */
30
32
  export const escape = value => String(value ?? '')
@@ -35,6 +37,10 @@ const STATE_LABEL = Object.freeze({
35
37
  present: 'Present', absent: 'Not found', unknown: 'Could not check',
36
38
  source_unavailable: 'Page unavailable', unchecked: 'Never checked',
37
39
  });
40
+ const CURRENT_STATE_LABEL = Object.freeze({
41
+ ...STATE_LABEL,
42
+ suspected_missing: 'Suspected missing', confirmed_missing: 'Confirmed missing',
43
+ });
38
44
 
39
45
  /**
40
46
  * The frozen dataset a report renders.
@@ -42,12 +48,10 @@ const STATE_LABEL = Object.freeze({
42
48
  * Selection happens here and nowhere else, so a caller can hold this object, assert it, and know
43
49
  * the rendered document is a pure function of it.
44
50
  */
45
- export function freezeDataset(entries, observations, { asOf, title = 'Backlink report', includeNotes = false, includeRetired = false, indexObservations = [] } = {}) {
46
- const latest = latestByEntry(observations);
47
- const rows = entries
48
- .filter(entry => includeRetired || entry.intent !== 'retired')
49
- .map(entry => {
50
- const row = latest.get(entry.id) ?? null;
51
+ export function freezeDataset(entries, observations, { asOf, title = 'Backlink report', includeNotes = false, includeRetired = false, indexObservations = [], activityState = null } = {}) {
52
+ const rows = projectPlacements(entries.filter(entry => includeRetired || entry.intent !== 'retired'), observations, { state: activityState })
53
+ .map(projection => {
54
+ const { entry, row } = projection;
51
55
  const indexRows=indexRowsForUrl(indexObservations,entry.source);
52
56
  const origin = row?.source === 'cloud' ? 'cloud' : (row?.source ?? null);
53
57
  return {
@@ -57,6 +61,16 @@ export function freezeDataset(entries, observations, { asOf, title = 'Backlink r
57
61
  reason: row?.reason ?? null,
58
62
  occurrences: row?.occurrences ?? 0,
59
63
  checked_at: row?.checked_at ?? null,
64
+ current_state: projection.current_state,
65
+ uncertain: projection.uncertain,
66
+ activity: {
67
+ available: projection.activity_available,
68
+ identity_status: projection.identity_status,
69
+ latest_attempt: projection.latest_attempt,
70
+ last_successful_observation: projection.last_successful_observation,
71
+ last_link_verification: projection.last_link_verification,
72
+ first_present: projection.first_present,
73
+ },
60
74
  checked_by: row?.source ?? null,
61
75
  anchor: row?.result?.occurrences?.[0]?.anchor ?? null,
62
76
  rel: row?.result?.occurrences?.[0]?.rel ?? [],
@@ -105,10 +119,16 @@ export function freezeDataset(entries, observations, { asOf, title = 'Backlink r
105
119
  // Stated rather than implied. A report over a ledger nobody has checked is not a report about
106
120
  // links, and the reader should see that before the table.
107
121
  coverage: {
108
- checked: rows.filter(row => row.checked_at).length,
109
- never_checked: rows.filter(row => !row.checked_at).length,
122
+ checked: rows.filter(row => row.activity.latest_attempt).length,
123
+ never_checked: rows.filter(row => !row.activity.latest_attempt).length,
124
+ retained_receipt_entries: rows.filter(row => row.checked_at).length,
125
+ missing_receipt_entries: rows.filter(row => !row.checked_at).length,
110
126
  observed_from: dates[0] ?? null,
111
127
  observed_to: dates.at(-1) ?? null,
128
+ activity_entries: rows.filter(row => row.activity.available).length,
129
+ identity_unmatched_entries: rows.filter(row => row.activity.identity_status === 'not_matched').length,
130
+ denominator: includeRetired ? 'all_ledger_entries' : 'non_retired_ledger_entries',
131
+ known_present_after_unknown: rows.filter(row => row.current_state === 'present' && row.uncertain).length,
112
132
  },
113
133
  };
114
134
  }
@@ -149,6 +169,18 @@ export function renderReport(dataset, { brand = null } = {}) {
149
169
  const digest = createHash('sha256').update(JSON.stringify(dataset)).digest('hex');
150
170
  const card = (label, value) => `<div class="card"><b>${escape(value)}</b><span>${escape(label)}</span></div>`;
151
171
  const coverage = dataset.coverage;
172
+ const hasActivity = dataset.v >= 3;
173
+ const activityCell = row => {
174
+ const activity = row.activity ?? {};
175
+ const describe = (label, attempt) => `<div>${escape(label)}: ${attempt ? `${escape(attempt.state)} · ${escape(attempt.checked_at)}` : 'not retained'}</div>`
176
+ + (attempt ? `<div class="note">${escape(attempt.reason ?? 'reason not recorded')} · ${escape(attempt.method ?? 'method not recorded')}</div>`
177
+ + `<div class="ref">${escape(attempt.evidence_reference ?? 'no retained receipt reference')}</div>` : '');
178
+ return `<div>${escape(CURRENT_STATE_LABEL[row.current_state] ?? row.current_state ?? 'Unknown')}${row.uncertain ? ' · latest attempt inconclusive' : ''}</div>`
179
+ + describe('Latest attempt', activity.latest_attempt)
180
+ + describe('Last conclusive check', activity.last_successful_observation)
181
+ + describe('Last link verification', activity.last_link_verification)
182
+ + `<div class="note">Activity ${activity.available ? 'recorded' : 'not supplied or not retained'}; identity ${escape(activity.identity_status ?? 'not retained')}</div>`;
183
+ };
152
184
  // The receipt column. "hash not recorded" is stated rather than papered over: a local check
153
185
  // that never completed a fetch has no hash, and a cloud-synced row may carry its snapshot key
154
186
  // without a hash in an older event. Neither is a claim about the link.
@@ -164,10 +196,10 @@ export function renderReport(dataset, { brand = null } = {}) {
164
196
  const rows = dataset.rows.map(row => `<tr>
165
197
  <td class="url"><a href="${escape(row.source)}">${escape(row.source)}</a></td>
166
198
  <td class="url">${escape(row.target)}</td>
167
- <td><span class="state ${escape(row.state)}">${escape(STATE_LABEL[row.state] ?? row.state)}</span></td>
199
+ <td><span class="state ${escape(row.state)}">${escape(hasActivity && !row.evidence && row.activity?.latest_attempt ? 'No retained observation' : (hasActivity ? CURRENT_STATE_LABEL : STATE_LABEL)[row.state] ?? row.state)}</span></td>
168
200
  <td>${row.anchor ? escape(row.anchor) : '<span class="note">&mdash;</span>'}${(row.rel ?? []).length ? `<div class="note">rel: ${escape(row.rel.join(' '))}</div>` : ''}</td>
169
201
  <td>${row.checked_at ? escape(row.checked_at.slice(0, 10)) : '<span class="note">never</span>'}</td>
170
- <td>${evidenceCell(row)}</td>
202
+ <td>${evidenceCell(row)}</td>${hasActivity ? `\n <td>${activityCell(row)}</td>` : ''}
171
203
  ${hasIndex?`<td>${(row.index_observations??[]).map(o=>`${escape(o.tier)} · ${escape(o.reason)}<div>${escape(o.checked_at)} · ${escape(o.backend)}</div><div class="ref">${escape(o.locator)}</div>`).join('<hr>')||'Not checked'}</td>`:''}
172
204
  ${dataset.include_notes ? `<td class="note">${escape(row.ref ?? '')}${row.note ? `<div>${escape(row.note)}</div>` : ''}</td>` : ''}
173
205
  </tr>`).join('\n');
@@ -188,21 +220,21 @@ export function renderReport(dataset, { brand = null } = {}) {
188
220
  <p class="sub">${brand ? `${escape(brand)} &middot; ` : ''}Prepared ${escape(dataset.as_of)}</p>
189
221
  <div class="cards">
190
222
  ${card('links in report', dataset.totals.entries)}
191
- ${card('present', dataset.totals.present ?? 0)}
192
- ${card('not found', dataset.totals.absent ?? 0)}
193
- ${card('could not check', (dataset.totals.unknown ?? 0) + (dataset.totals.source_unavailable ?? 0))}
194
- ${card('never checked', dataset.totals.unchecked ?? 0)}
223
+ ${card(hasActivity ? 'retained present' : 'present', dataset.totals.present ?? 0)}
224
+ ${card(hasActivity ? 'retained not found' : 'not found', dataset.totals.absent ?? 0)}
225
+ ${card(hasActivity ? 'retained could not check' : 'could not check', (dataset.totals.unknown ?? 0) + (dataset.totals.source_unavailable ?? 0))}
226
+ ${card('never checked', hasActivity ? coverage.never_checked : dataset.totals.unchecked ?? 0)}
195
227
  </div>
196
228
  <p class="caveat"><strong>What these dates mean.</strong> Every row shows the date that link was
197
229
  last observed, between ${escape(coverage.observed_from ?? 'n/a')} and ${escape(coverage.observed_to ?? 'n/a')}.
198
230
  The date at the top is when this document was prepared and does not make any observation newer
199
- than it is. ${coverage.never_checked ? `${coverage.never_checked} of ${dataset.totals.entries} links have never been checked and are listed as such.` : ''}</p>
231
+ than it is. ${coverage.never_checked ? `${coverage.never_checked} of ${dataset.totals.entries} links have never been checked and are listed as such.` : ''}</p>${hasActivity ? '\n<p class="caveat"><strong>Activity and verification.</strong> The result and receipt date describe the retained observation. Activity lists later attempts and complete checks separately; an unchanged recheck can be newer than that receipt. An inconclusive attempt preserves the earlier verified state, and does not prove loss. A complete unavailable source is a conclusive source check, not a link verification. Missing activity is not inferred from a report preparation date.</p>' : ''}
200
232
  <p class="caveat"><strong>What a result means.</strong> &ldquo;Present&rdquo; means the link was in
201
233
  the observed HTML. &ldquo;Could not check&rdquo; is not the same as
202
234
  &ldquo;not found&rdquo;: it means the page could not be read, and no conclusion was drawn.
203
235
  Each receipt identifies the check method when it was recorded. A snapshot alone does not prove the link was visible on screen.</p>
204
236
  <table><thead><tr>
205
- <th>Source page</th><th>Target</th><th>Result</th><th>Anchor</th><th>Observed</th><th>Evidence</th>${hasIndex?'<th>Index evidence</th>':''}${dataset.include_notes ? '<th>Notes</th>' : ''}
237
+ <th>Source page</th><th>Target</th><th>Result</th><th>Anchor</th><th>Observed</th><th>Evidence</th>${hasActivity ? '<th>Activity and verified state</th>' : ''}${hasIndex?'<th>Index evidence</th>':''}${dataset.include_notes ? '<th>Notes</th>' : ''}
206
238
  </tr></thead><tbody>
207
239
  ${rows}
208
240
  </tbody></table>
@@ -219,7 +251,7 @@ ${receiptRows}
219
251
  </tbody></table>` : ''}
220
252
  <footer>
221
253
  AgentLinkOps report v${dataset.v}. Dataset digest <code>${escape(digest)}</code> &mdash; the same
222
- ledger and observations always produce this digest. The same dataset exports as JSON
254
+ ledger and observations${hasActivity ? ' and frozen activity' : ''} always produce this digest. The same dataset exports as JSON
223
255
  (<code>agentlinkops report --json</code>) and carries the same digest.
224
256
  ${dataset.include_notes ? 'Private notes are included in this document.' : 'Private notes and references are excluded.'}
225
257
  </footer>
package/cli/setup-plan.js CHANGED
File without changes
package/cli/skill.js CHANGED
File without changes
package/cli/state.js CHANGED
@@ -10,8 +10,20 @@
10
10
  // was this last confirmed" — is exactly what a small, rewritten map answers better.
11
11
  import { readFile, writeFile, mkdir, rename } from 'node:fs/promises';
12
12
  import { dirname, join } from 'node:path';
13
+ import { placementIdentity, attemptSummary, isConclusive, isLinkVerification, validTime } from './placement-projection.js';
13
14
 
14
- export const STATE_VERSION = 2;
15
+ export const STATE_VERSION = 3;
16
+
17
+ // A newer activity format may carry semantics this reducer cannot preserve.
18
+ // Refuse before loading, reducing or replacing it; unknown fields in supported
19
+ // versions still round-trip through the existing migration rules.
20
+ function assertSupportedStateVersion(raw) {
21
+ const version = Number.isInteger(raw?.v) ? raw.v : 1;
22
+ if (version <= STATE_VERSION) return;
23
+ const error = new Error(`Activity state format ${version} is unsupported; this CLI supports up to ${STATE_VERSION}. Keep the original state and history; use a compatible CLI.`);
24
+ error.code = 'UNSUPPORTED_STATE_VERSION';
25
+ throw error;
26
+ }
15
27
 
16
28
  /**
17
29
  * Brings a state file forward without losing anything it already held.
@@ -24,6 +36,7 @@ export const STATE_VERSION = 2;
24
36
  * cursor. Seeding it would skip every target event ever emitted, and the feed would look quiet.
25
37
  */
26
38
  export function upgradeState(raw) {
39
+ assertSupportedStateVersion(raw);
27
40
  const state = { ...(raw ?? {}) };
28
41
  const version = Number.isInteger(state.v) ? state.v : 1;
29
42
  const cursors = { ...(state.cursors ?? {}) };
@@ -49,6 +62,7 @@ export async function readState(path) {
49
62
  }
50
63
 
51
64
  export async function writeState(path, state) {
65
+ assertSupportedStateVersion(state);
52
66
  await mkdir(dirname(path), { recursive: true });
53
67
  const temporary = join(dirname(path), `.${crypto.randomUUID()}.state.tmp`);
54
68
  // Sorted keys so a run that changes one entry produces a one-line diff.
@@ -58,49 +72,82 @@ export async function writeState(path, state) {
58
72
  }
59
73
 
60
74
  /** When each entry was last checked, whatever the answer was. */
61
- export function lastCheckedMap(state) {
62
- return new Map(Object.entries(state.entries ?? {}).map(([id, record]) => [id, record.last_checked]));
75
+ export function lastCheckedMap(state, entries = null) {
76
+ const identities = entries ? new Map(entries.map(entry => [entry.id, placementIdentity(entry)])) : null;
77
+ return new Map(Object.entries(state.entries ?? {}).filter(([id, record]) => !identities
78
+ || identities.get(id) && record.last_placement === identities.get(id)).map(([id, record]) => [id, record.last_checked]));
79
+ }
80
+
81
+ const methodOf = row => row.result?.evidence?.method ?? null;
82
+ const eligible = (row, previous) => validTime(row.checked_at)
83
+ && (!validTime(previous?.last_checked) || Date.parse(row.checked_at) > Date.parse(previous.last_checked));
84
+ const repeats = (row, previous) => previous
85
+ && previous.last_placement === placementIdentity(row)
86
+ && previous.last_state === row.state
87
+ && (previous.last_reason ?? null) === (row.reason ?? null)
88
+ && (previous.last_signature ?? null) === (row.result?.linkSignature ?? null)
89
+ && previous.last_complete === (row.complete === true)
90
+ && (previous.last_method ?? null) === methodOf(row)
91
+ && (previous.last_checker_version ?? null) === (row.checker_version ?? row.result?.evidence?.checkerVersion ?? null);
92
+
93
+ function applyRow(previous, row) {
94
+ const identity = placementIdentity(row);
95
+ const samePlacement = previous?.last_placement === identity;
96
+ const attempt = attemptSummary(row, { retained: !repeats(row, previous) });
97
+ const knownPresent = isConclusive(attempt) && attempt.state === 'present';
98
+ const priorFirst = samePlacement && previous?.first_present_observation?.placement === identity
99
+ && isConclusive(previous.first_present_observation) && previous.first_present_observation.state === 'present'
100
+ ? previous.first_present_observation : null;
101
+ const firstPresent = priorFirst ?? (knownPresent ? attempt : null);
102
+ return {
103
+ ...previous,
104
+ last_checked: row.checked_at,
105
+ last_complete: row.complete === true,
106
+ last_placement: identity,
107
+ last_state: row.state,
108
+ last_reason: row.reason ?? null,
109
+ last_signature: row.result?.linkSignature ?? null,
110
+ last_method: methodOf(row),
111
+ last_checker_version: row.checker_version ?? row.result?.evidence?.checkerVersion ?? null,
112
+ checks: (previous?.checks ?? 0) + 1,
113
+ last_attempt: attempt,
114
+ last_successful_observation: isConclusive(attempt) ? attempt : samePlacement ? previous?.last_successful_observation ?? null : null,
115
+ last_link_verification: isLinkVerification(attempt) ? attempt : samePlacement ? previous?.last_link_verification ?? null : null,
116
+ first_present: firstPresent?.checked_at ?? null,
117
+ first_present_placement: firstPresent ? identity : null,
118
+ first_present_observation: firstPresent,
119
+ ...(samePlacement && previous?.first_present && !previous.first_present_observation
120
+ ? { legacy_first_present: previous.first_present } : {}),
121
+ };
63
122
  }
64
123
 
65
124
  /**
66
125
  * Splits a run's rows into the ones worth recording and the ones that repeat.
67
126
  *
68
- * A row is worth recording when it is the first for its entry, or when the state, the reason or
69
- * the link signature moved. The signature is in there deliberately: a link that is still present
70
- * but whose anchor or rel changed is a real event, and comparing only the state would miss it.
127
+ * A row is worth recording when it is the first for its placement, or when the state, reason,
128
+ * link signature, completeness or method moved. A link that is still present but whose anchor
129
+ * or rel changed is a real event, and comparing only the state would miss it.
71
130
  */
72
131
  export function selectChanged(rows, state) {
73
- const changed = [], repeated = [];
132
+ const changed = [], repeated = [], ignored = [];
133
+ const entries = { ...(state.entries ?? {}) };
74
134
  for (const row of rows) {
75
- const previous = state.entries?.[row.id];
76
- const signature = row.result?.linkSignature ?? null;
77
- const same = previous
78
- && previous.last_state === row.state
79
- && (previous.last_reason ?? null) === (row.reason ?? null)
80
- && (previous.last_signature ?? null) === signature;
81
- (same ? repeated : changed).push(row);
135
+ const previous = entries[row.id];
136
+ if (!eligible(row, previous)) { ignored.push(row); continue; }
137
+ (repeats(row, previous) ? repeated : changed).push(row);
138
+ entries[row.id] = applyRow(previous, row);
82
139
  }
83
- return { changed, repeated };
140
+ return { changed, repeated, ignored };
84
141
  }
85
142
 
86
143
  /** Applies a run to the activity record. Every check counts, recorded or not. */
87
144
  export function applyRun(state, rows) {
145
+ assertSupportedStateVersion(state);
88
146
  const entries = { ...(state.entries ?? {}) };
89
147
  for (const row of rows) {
90
- const previous = entries[row.id] ?? { checks: 0 };
91
- entries[row.id] = {
92
- ...previous,
93
- last_checked: row.checked_at,
94
- last_complete: row.complete === true,
95
- last_placement: row.result?.sourceUrl && row.result?.targetUrl ? JSON.stringify([row.result.sourceUrl, row.result.targetUrl, row.result.targetScope ?? 'exact']) : null,
96
- last_state: row.state,
97
- last_reason: row.reason ?? null,
98
- last_signature: row.result?.linkSignature ?? null,
99
- checks: (previous.checks ?? 0) + 1,
100
- // The date a placement was first seen is worth keeping where nothing can compact it away.
101
- first_present: previous.first_present ?? (row.state === 'present' ? row.checked_at : null),
102
- first_present_placement: previous.first_present ? previous.first_present_placement ?? null : row.state === 'present' && row.result?.sourceUrl && row.result?.targetUrl ? JSON.stringify([row.result.sourceUrl, row.result.targetUrl, row.result.targetScope ?? 'exact']) : null,
103
- };
148
+ const previous = entries[row.id];
149
+ if (!eligible(row, previous)) continue;
150
+ entries[row.id] = applyRow(previous, row);
104
151
  }
105
152
  return { ...state, v: STATE_VERSION, entries };
106
153
  }
package/cli/status.js CHANGED
@@ -4,32 +4,28 @@
4
4
  // expectation, closes the laptop, and three weeks later an agent opens the repository and reads
5
5
  // this. So it answers in the order a person cares about, not the order the data is stored in:
6
6
  // what appeared, what was lost, what we cannot say, and what needs a decision.
7
- import { latestByEntry } from './mirror.js';
7
+ import { projectPlacements } from './placement-projection.js';
8
8
 
9
9
  /** A ledger entry whose observations disagree with its declared intent. */
10
- export function disagreements(entries, observations) {
11
- const latest = latestByEntry(observations);
10
+ export function disagreements(entries, observations, { state = null } = {}) {
12
11
  const out = [];
13
- for (const entry of entries) {
14
- const row = latest.get(entry.id);
15
- if (!row) { out.push({ entry, kind: 'never_checked' }); continue; }
12
+ for (const projection of projectPlacements(entries, observations, { state })) {
13
+ const { entry, row, latest_attempt: attempt } = projection;
14
+ const activity = { current_state: projection.current_state, uncertain: projection.uncertain,
15
+ latest_attempt: attempt, last_successful_observation: projection.last_successful_observation,
16
+ last_link_verification: projection.last_link_verification,
17
+ evidence_observed_at: projection.evidence_observed_at, identity_status: projection.identity_status };
18
+ if (!attempt) { out.push({ entry, kind: 'never_checked', activity }); continue; }
16
19
  if (entry.intent === 'expected') {
17
- const hostedState = row.source === 'cloud' ? row.result?.watchState : null;
18
- const confirmed = row.state === 'confirmed_missing'
19
- || (row.state === 'absent' && hostedState === 'confirmed_missing');
20
- const suspected = row.state === 'suspected_missing' || row.state === 'absent'
21
- || (row.state === 'unknown' && hostedState === 'suspected_missing');
22
- const missing = ['absent', 'suspected_missing', 'confirmed_missing'].includes(row.state);
23
- // The change mirror omits repeated checks, so raw absence cannot prove confirmation.
24
- if ((confirmed || suspected) && row.complete === true) {
25
- out.push({ entry, row, kind: confirmed ? 'lost' : 'suspected_missing' });
26
- } else if (missing || ['unknown', 'source_unavailable'].includes(row.state)) {
27
- out.push({ entry, row, kind: 'cannot_say' });
20
+ if (projection.uncertain || attempt.state === 'source_unavailable') {
21
+ out.push({ entry, row, kind: 'cannot_say', activity });
22
+ } else if (['suspected_missing', 'confirmed_missing'].includes(projection.current_state)) {
23
+ out.push({ entry, row, kind: projection.current_state === 'confirmed_missing' ? 'lost' : 'suspected_missing', activity });
28
24
  }
29
25
  }
30
26
  // Nothing is promoted automatically: whether a link that appeared is one we now EXPECT is
31
27
  // a judgement about a relationship, not about HTML. So this proposes the edit and stops.
32
- if (entry.intent === 'wanted' && row.state === 'present') out.push({ entry, row, kind: 'appeared', suggest: { intent: 'expected' } });
28
+ if (entry.intent === 'wanted' && !projection.uncertain && projection.current_state === 'present') out.push({ entry, row, kind: 'appeared', activity, suggest: { intent: 'expected' } });
33
29
  }
34
30
  return out;
35
31
  }
package/cli/sync-lock.js CHANGED
File without changes
package/cli/sync.js CHANGED
@@ -12,6 +12,24 @@ import { CloudError } from './client.js';
12
12
 
13
13
  const PUSH_BATCH = 100;
14
14
 
15
+ function completeImportRows(result, expected) {
16
+ const record = value => value !== null && typeof value === 'object' && !Array.isArray(value);
17
+ const text = value => typeof value === 'string' && value.trim().length > 0;
18
+ const refuse = () => { throw new CloudError('INVALID_IMPORT_RESPONSE', 502, null,
19
+ 'The cloud returned an incomplete or invalid watch import result. Some watches may already have been stored. Inspect accessible watches and their local references before retrying sync; this request was not automatically replayed.'); };
20
+ if (!record(result) || !Array.isArray(result.rows) || result.rows.length !== expected) refuse();
21
+ const seen = new Set();
22
+ for (const row of result.rows) {
23
+ if (!record(row) || !Number.isInteger(row.index) || row.index < 0 || row.index >= expected || seen.has(row.index)) refuse();
24
+ seen.add(row.index);
25
+ const watch = Object.hasOwn(row, 'watch'), error = Object.hasOwn(row, 'error');
26
+ if (watch === error) refuse();
27
+ if (watch && (!record(row.watch) || !text(row.watch.id) || typeof row.watch.created !== 'boolean')) refuse();
28
+ if (error && (!record(row.error) || !text(row.error.code) || !text(row.error.message))) refuse();
29
+ }
30
+ return result.rows;
31
+ }
32
+
15
33
  export function syncPlan(entries, state, { includeWanted = false, ledgerIds = null } = {}) {
16
34
  if (ledgerIds !== null && (!Array.isArray(ledgerIds) || ledgerIds.some(id => typeof id !== 'string') || ledgerIds.some(id => !entries.some(entry => entry.id === id))))
17
35
  throw new CloudError('INVALID_SYNC_SELECTION', 0);
@@ -38,12 +56,13 @@ export async function pushExpectations(client, entries, state, { projectId, onPr
38
56
  for (let index = 0; index < watched.length; index += PUSH_BATCH) {
39
57
  const batch = watched.slice(index, index + PUSH_BATCH);
40
58
  const result = await client.importWatches(projectId, batch.map(toWatchInput));
41
- for (const row of result.rows ?? []) {
59
+ // Validate the complete acknowledgement before adopting any of this batch.
60
+ // Earlier POSTs may have committed; a malformed response never authorizes a replay.
61
+ for (const row of completeImportRows(result, batch.length)) {
42
62
  const entry = batch[row.index];
43
- if (!entry) continue;
44
63
  if (row.error) { failed.push({ id: entry.id, code: row.error.code, message: row.error.message }); continue; }
45
64
  mapping[entry.id] = row.watch.id;
46
- if (row.watch.created !== false) created.push(entry.id);
65
+ if (row.watch.created) created.push(entry.id);
47
66
  }
48
67
  onProgress?.(Math.min(index + PUSH_BATCH, watched.length), watched.length);
49
68
  }
@@ -53,7 +72,7 @@ export async function pushExpectations(client, entries, state, { projectId, onPr
53
72
  for (const entry of entries.filter(item => actions.get(item.id) === 'pause')) {
54
73
  const watchId = mapping[entry.id];
55
74
  if (!watchId) continue;
56
- try { await client.updateWatch(watchId, { status: 'paused' }); retired.push(entry.id); }
75
+ try { await client.updateWatch(watchId, { status: 'paused', intent: 'retired' }); retired.push(entry.id); }
57
76
  catch (error) { failed.push({ id: entry.id, code: error.code, message: error.message }); }
58
77
  }
59
78
  return { pushed: watched.length, created: created.length, retired: retired.length, failed, watches: mapping, local: plan.filter(row => row.action === 'stay_local').length };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlinkops",
3
- "version": "0.6.11",
3
+ "version": "0.6.16",
4
4
  "description": "Backlink checks with dated evidence and portable results, optional repository ledgers, supplier-export imports and sync to AgentLinkOps.",
5
5
  "author": "AgentLinkOps (https://agentlinkops.com)",
6
6
  "license": "Apache-2.0",
@@ -22,6 +22,8 @@
22
22
  "references",
23
23
  "scripts",
24
24
  ".mcp.json",
25
+ "PACKS.json",
26
+ "PACK-ARTIFACT.json",
25
27
  ".claude-plugin",
26
28
  ".codex-plugin",
27
29
  "README.md",
@@ -46,24 +48,21 @@
46
48
  "mcp",
47
49
  "backlinks",
48
50
  "seo",
49
- "citation-monitoring",
50
- "ai-citations",
51
51
  "agent",
52
52
  "cli",
53
53
  "link-monitoring",
54
- "citation-watch",
55
54
  "ledger",
56
55
  "jsonl"
57
56
  ],
58
57
  "dependencies": {
59
- "@modelcontextprotocol/client": "2.2.0",
60
- "@modelcontextprotocol/server": "2.0.0",
58
+ "@modelcontextprotocol/client": "2.3.1",
59
+ "@modelcontextprotocol/server": "2.3.1",
61
60
  "ipaddr.js": "2.2.0",
62
61
  "koffi": "3.3.2",
63
62
  "parse5": "8.0.1",
64
63
  "turndown": "^7.2.4",
65
- "undici": "7.29.1",
66
- "zod": "4.6.0"
64
+ "undici": "7.30.1",
65
+ "zod": "4.6.7"
67
66
  },
68
67
  "devDependencies": {
69
68
  "esbuild": "^0.28.2"
@@ -1,5 +1,7 @@
1
1
  # Citation panels
2
2
 
3
+ Status: AI citation measurement is not part of the AgentLinkOps offer. No default skill links to this page, and the hosted service lists no citation commands for customers. This reference covers the local `agentlinkops citation` commands, which stay in the CLI for existing panels.
4
+
3
5
  A panel records the questions, target sites, engines and sample budget for a citation check. Start with questions your audience has actually asked. Keep each question's source and collection date in your own research notes. Search Console queries, support conversations and public discussion threads can help when you already have access; the panel builder does not fetch or validate them.
4
6
 
5
7
  Create an editable draft without calling a provider:
package/references/cli.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Local CRM contract
2
2
 
3
3
  Version 0.2.0; SQLite schema version 4. Python 3.10+ is the only prerequisite.
4
- Examples assume the working directory is this plugin root. Resolve paths from
5
- the installed plugin location when running from a skill. Choose a database
4
+ Resolve example helper paths from the plugin root for shared references, or from
5
+ the skill directory for a standalone archive's bundled references. Choose a database
6
6
  path in the user's working directory, outside the replaceable plugin cache.
7
7
 
8
8
  ```sh
@@ -16,7 +16,7 @@ checked September 9, 2026.
16
16
 
17
17
  ## Commands
18
18
 
19
- Run from the installed plugin root and keep the database outside its cache:
19
+ Resolve example helper paths from the plugin root for shared references, or from the skill directory for a standalone archive's bundled references. Keep the database outside the installation directory:
20
20
 
21
21
  ```sh
22
22
  python3 scripts/agentlinkops.py --db ./agentlinkops.sqlite migrate
@@ -29,4 +29,4 @@ The local check includes wanted entries; `--all` rechecks the selected rows even
29
29
 
30
30
  For an already connected workspace, `agentlinkops tools monitoring` and `agentlinkops describe monitor_link` expose the current hosted contract. Inspect project IDs and usage first. Enroll selected source/target pairs only when recurring monitoring is within the user's request; record the returned watch IDs. A successful import or local check alone does not create a recurring hosted job.
31
31
 
32
- The [campaign skill](../skills/agentlinkops-campaigns/SKILL.md) handles subsequent campaign judgment. The [CRM reference](cli.md) covers optional records when the user needs contacts and activities beside the ledger. Outreach stays in the user's chosen tool and requires their instruction.
32
+ Use the customer's brief and evidence to decide the next campaign step. For additional guidance, select `agentlinkops agent setup --skill agentlinkops-campaigns` and read that installed skill. The [CRM reference](cli.md) covers optional records when the user needs contacts and activities beside the ledger. Outreach stays in the user's chosen tool and requires their instruction.
@@ -7,7 +7,7 @@ This adapter writes an inert body and companion manifest under that project's
7
7
  drafts directory. It does not write contacts, replies, suppression, send logs or
8
8
  sequence state. Native vendor APIs remain unsupported by this adapter.
9
9
 
10
- Run from the plugin root:
10
+ Resolve example helper paths from the plugin root for shared references, or from the skill directory for a standalone archive's bundled references:
11
11
 
12
12
  ```sh
13
13
  python3 scripts/outreach-crm-draft.py --crm-root /path/to/customer-crm --request request.json --body-file reviewed-body.txt
@@ -36,15 +36,16 @@ Keep a distinct output for a new snapshot; this is a dated artifact, not a monit
36
36
 
37
37
  Read the saved JSON back before claiming persistence. Its destination outcome is
38
38
  `not_attempted`, and `send_eligible` remains false on every row. Passing this helper
39
- does not prove a CRM import or sequencer readback. Follow the
40
- [qualified campaign handoff](recipes/qualified-campaign-handoff.md) for explicit
41
- mapping, original-operation reconciliation and destination readback. After a
42
- response-lost import, preserve its actual operation identity and unknown outcome;
43
- do not replace it with this file's operation ID or repeat a create to repair an
44
- artifact error. [Placement reconciliation](recipes/placement-reconciliation.md)
45
- keeps monitoring facts apart from sent/replied/suppressed records.
46
-
47
- Package invocation assumes the plugin's root directory. Local source invocation
48
- uses toolkit/plugin/agentlinkops/scripts/portable-handoff.py. Unsupported native
39
+ does not prove a CRM import or sequencer readback. Before a destination write,
40
+ retain the exact mapping and request, then read the destination back when authorized.
41
+ After a response-lost import, preserve its actual operation identity and unknown
42
+ outcome; do not replace it with this file's operation ID or repeat a create to
43
+ repair an artifact error. Keep monitoring facts apart from sent/replied/suppressed
44
+ records. For additional guidance, select `agentlinkops agent setup --recipe qualified-campaign-handoff`
45
+ or `agentlinkops agent setup --recipe placement-reconciliation` and read the selected
46
+ recipe. Neither is required to prepare this local file.
47
+
48
+ Resolve example helper paths from the plugin root for shared references, or from
49
+ the skill directory for a standalone archive's bundled references. Unsupported native
49
50
  vendors, mailbox access and automatic suppression synchronization remain outside
50
51
  this helper. No account, credential or send action is installed by it.