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
@@ -0,0 +1,220 @@
1
+ # Local CRM contract
2
+
3
+ Version 0.2.0; SQLite schema version 4. Python 3.10+ is the only prerequisite.
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
+ path in the user's working directory, outside the replaceable plugin cache.
7
+
8
+ ```sh
9
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite init --workspace local-example
10
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite status
11
+ python3 references/portable/scripts/agentlinkops.py templates
12
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite product upsert --file product.json
13
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite opportunity list
14
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite export --format json --output crm-export.json
15
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite export --format csv --entity placement --output placements.csv
16
+ ```
17
+
18
+ `init` binds this database to exactly one workspace ID. Use an actual cloud
19
+ workspace ID if cloud sync is planned. For a purely offline CRM, choose a
20
+ stable local name. Workspace and cloud-origin binding cannot be silently
21
+ changed. A separate local database is required for a different workspace.
22
+
23
+ `migrate` creates a SQLite backup before applying pending migrations. It
24
+ refuses unrelated databases, newer schema versions and changed migration
25
+ checksums. Initialization is idempotent for the same workspace. The CLI has
26
+ no destructive reset command. SQLite is not encrypted; rely on the user's
27
+ device security and store the file in an appropriate location.
28
+
29
+ `upsert` reads one object or an atomic array of objects from a file or stdin
30
+ (`--file -`, the default). IDs are explicit, stable strings; updating an
31
+ existing record changes only supplied fields. References must already exist;
32
+ insert product, campaign/contact, opportunity, then activity/placement.
33
+
34
+ | Entity | Required for a new record, plus `id` | Optional editable fields |
35
+ |---|---|---|
36
+ | product | `name`, `url` | `audience`, `description`, `target_pages` (URL array), `notes` |
37
+ | campaign | `name`, `type` | `product_id`, `template_version`, `status`, `goal`, `target_url`, `notes` |
38
+ | contact | `publisher_url` | `name`, `email`, `contact_url`, `source_url`, `observed_at`, `confidence`, `notes` |
39
+ | opportunity | `source_url`, `target_url` | `campaign_id`, `contact_id`, `type`, `status`, `evidence` (object), `notes` |
40
+ | activity | `kind`, `occurred_at` | `opportunity_id`, `contact_id`, `campaign_id`, `channel`, `external_message_id`, `outcome`, `notes` |
41
+ | placement | `source_url`, `target_url` | `opportunity_id`, `project_id`, `watch_id`, `status`, `notes`, `cost_amount`, `cost_currency` |
42
+
43
+ Timestamps require ISO 8601 with a timezone. Link fields require HTTP(S) URLs.
44
+ Null clears an optional relationship ID. Passwords, API keys, tokens,
45
+ mailbox credentials and email/message body fields are rejected. Never put
46
+ those values into free-text notes either. Contact confidence describes the
47
+ evidence, not a promise of email delivery.
48
+
49
+ Example `product.json`:
50
+
51
+ ```json
52
+ {"id":"product-example","name":"Example Resource","url":"https://example.com/","audience":"Resource-page readers","target_pages":["https://example.com/guide"]}
53
+ ```
54
+
55
+ Example opportunity evidence:
56
+
57
+ ```json
58
+ {"id":"opportunity-example","source_url":"https://publisher.example/resources","target_url":"https://example.com/guide","type":"resource_addition","evidence":{"source_url":"https://publisher.example/resources","observed_at":"2026-09-09T12:00:00Z","context":"Relevant resources section; candidate requires browser review."}}
59
+ ```
60
+
61
+ Use `entity list --id ID` to retrieve a specific record. JSON exports preserve
62
+ all fields, raw events, the sync cursor, disavow rules and their source provenance. They are portable data exports,
63
+ not an automatic full-database restore format. CSV exports select one entity
64
+ and prefix possible spreadsheet formulas with `'`; JSON is lossless.
65
+ Export files are created with restrictive permissions and never overwritten.
66
+ For a restorable database backup, use SQLite's backup API or the SQLite CLI
67
+ `.backup` command; do not copy a live database file while a write is running.
68
+
69
+ ## Disavow lists and placement reports
70
+
71
+ For user-selected disavow files, read [the disavow contract](disavow.md).
72
+ The `disavow import/upsert/list/export` commands work offline, require an
73
+ explicit URL-prefix property and preserve source provenance. No command
74
+ uploads to Google or chooses rules from automated risk labels.
75
+
76
+ For branded HTML output, read [the report contract](reports.md). The `report`
77
+ command supports a project filter and optional local notes/cost fields. Notes
78
+ and costs are excluded unless explicitly included. Record a placement cost
79
+ as a decimal string paired with an uppercase currency label, for example
80
+ `"cost_amount":"12.50","cost_currency":"USD"`. Clear both fields with null.
81
+ The CLI validates decimal precision and reports each currency separately.
82
+ It does not verify currency labels against a current exchange-rate service.
83
+
84
+ Existing CRM databases require `migrate` before using version 0.2.0. Schema
85
+ versions 3 and 4 add disavow provenance tables and optional placement cost
86
+ fields. Existing placements, notes, event history and workspace bindings are
87
+ preserved; the helper creates a restorable database backup before upgrading.
88
+
89
+ ## Connect the ledger CLI to an existing project
90
+
91
+ Local ledger commands work without an account. Cloud sync uses a separate scoped
92
+ API key. MCP OAuth credentials stay in the MCP client and cannot authorize REST.
93
+
94
+ 1. Open the invited account's workspace at `https://app.agentlinkops.com/app`.
95
+ An owner or admin selects **Agent access > Create API key**. Choose the
96
+ existing project and the scopes `projects:read`, `watches:read`,
97
+ `watches:write`, `events:read`, and `exports:create`.
98
+ 2. Save the once-shown credential in the user's secret store. Supply it through
99
+ `AGENTLINKOPS_API_KEY` in the CLI process environment. The ledger CLI also accepts
100
+ `AGENTLINKOPS_TOKEN`; if both are set, they must agree. The `LINKTRAIL_` spellings are
101
+ still read, with a one-line warning, during the pilot compatibility window. Never paste credentials
102
+ into chat, command arguments, or ledger files.
103
+ 3. Run the installed CLI from the directory containing the existing `.agentlinkops/` (or `.linktrail/`)
104
+ ledger. Replace the script path and IDs below with the installed package path
105
+ and IDs returned by `get_workspace` and `list_projects`.
106
+
107
+ ```sh
108
+ node /path/to/installed/plugin/cli/agentlinkops.mjs connect --origin https://app.agentlinkops.com --workspace WORKSPACE_ID --project-id PROJECT_ID --selection selected-links.json
109
+ node /path/to/installed/plugin/cli/agentlinkops.mjs sync --dry-run
110
+ node /path/to/installed/plugin/cli/agentlinkops.mjs sync --pull-only
111
+ ```
112
+
113
+ `selected-links.json` contains an array of existing ledger IDs, such as
114
+ `["local-placement-1", "local-placement-2"]`. A mapping file with
115
+ `entries: [{"local_id": "local-placement-1"}]` also works. Save an explicit
116
+ selection when only part of an existing ledger belongs in cloud monitoring.
117
+
118
+ `connect` verifies workspace identity, project access and required scopes through
119
+ read-only REST calls. It saves connection metadata and the selection in
120
+ `.agentlinkops/config.json`, without saving the credential or creating cloud records.
121
+ `sync --dry-run` reads local files only and previews uploads. `sync --pull-only`
122
+ retrieves cloud history without creating watches. After reviewing the plan, run
123
+ `sync` when the user has authorized its proposed monitoring. A saved selection
124
+ limits uploads; without one, sync selects expected or earned entries and pauses mapped retired
125
+ entries. Wanted entries require `--include-wanted`.
126
+
127
+ The cloud stores normalized source URLs: `https://example.com` becomes
128
+ `https://example.com/`. Send the ledger ID as `localReference` and join returned
129
+ rows using `local_reference`. Raw URL string equality can miss an existing watch.
130
+ Copy watch IDs from tool results without shortening them.
131
+
132
+ The Python CRM helper has its own environment settings and workspace binding,
133
+ described in [optional cloud sync](#optional-cloud-sync). Ledger `connect` does
134
+ not configure that helper. If account access or permission to create a credential
135
+ is unavailable, use the existing MCP connection for cloud work and keep local
136
+ records intact.
137
+
138
+ ## Optional cloud sync
139
+
140
+ Set `AGENTLINKOPS_API_URL` to the actual service origin and
141
+ `AGENTLINKOPS_API_KEY` using the shell's existing secure environment mechanism.
142
+ Create the scoped credential in **Agent access > Create API key** as described
143
+ [above](#connect-the-ledger-cli-to-an-existing-project). For this read-only event
144
+ sync, select `events:read` and the projects whose events the CRM should receive.
145
+ This toolkit does not bundle credentials or create an account. Never paste keys into chat or command-line arguments.
146
+
147
+ ```sh
148
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite sync --limit 100 --max-pages 100
149
+ ```
150
+
151
+ The client requests `GET /v1/events?limit=100&cursor=...`. Production origins
152
+ require HTTPS. Loopback HTTP is supported for local development. Redirects
153
+ are rejected so bearer credentials never follow an endpoint redirect.
154
+ Requests time out after 30 seconds and responses are limited to 4 MiB.
155
+
156
+ Expected response:
157
+
158
+ ```json
159
+ {
160
+ "workspace_id": "workspace-example",
161
+ "events": [{
162
+ "id": "event-example", "workspace_id": "workspace-example",
163
+ "cursor": "opaque-cursor", "type": "watch.state_changed",
164
+ "project_id": "project-example", "watch_id": "watch-example",
165
+ "created_at": "2026-09-09T12:00:00Z",
166
+ "data": {
167
+ "before": {"state": "unknown"},
168
+ "after": {"state": "present", "source_url": "https://publisher.example/resources", "target_url": "https://example.com/guide"},
169
+ "observation_id": "observation-example", "evidence_key": null
170
+ }
171
+ }],
172
+ "next_cursor": "opaque-cursor", "has_more": false
173
+ }
174
+ ```
175
+
176
+ Each page commits new events, placement overlays and its cursor in one
177
+ transaction. Duplicate event IDs are ignored only when the payload is
178
+ identical. A changed payload, malformed event or mismatched workspace rolls
179
+ back the entire page. Earlier successful pages remain committed if a later
180
+ page fails; rerunning resumes at the last committed cursor. Two concurrent
181
+ syncs detect a cursor conflict and require a retry.
182
+
183
+ Events update placement `cloud_state`, `cloud_observed_at` and
184
+ `cloud_event_id`. Existing local `status`, `notes` and opportunity links are
185
+ preserved. URLs follow the cloud watch snapshot. A previously unseen watch
186
+ creates `id: cloud:<watch_id>` only when the snapshot supplies source and
187
+ destination URLs. Partial snapshots without a URL pair and unknown event types are retained in the event
188
+ feed for later interpretation; they do not fabricate a placement.
189
+
190
+ HTTP 410 reports `CURSOR_EXPIRED` and does not advance the cursor. Preserve
191
+ the database and export first; obtain a current watch snapshot and recover
192
+ into a separate database before replaying retained events. Automated snapshot
193
+ recovery is not implemented in this version. Removing the cursor alone would
194
+ hide a history gap and is not a supported recovery procedure.
195
+
196
+ All success output is JSON except requested CSV export. Errors are JSON on
197
+ stderr with nonzero exit status. HTTP response bodies and credential values
198
+ are never included in errors. Local operation has no telemetry.
199
+
200
+ ## Writer-lock recovery candidate
201
+
202
+ The maintained development source adds verified-owner recovery for `sync.lock`
203
+ and the ledger's secondary writer lock. This source acceptance is separate from
204
+ an installed release and hosted/client acceptance. A persistent OS advisory gate
205
+ serializes cooperating writers; valid same-user/same-boot metadata for a departed
206
+ writer can be recovered. Marker age alone never authorizes removal. Live, reused,
207
+ legacy empty, malformed or unverifiable markers are preserved with a refusal.
208
+
209
+ Keep the local ledger, mirror and state files. Read-only `status`/`report` remain
210
+ useful while writer ownership is unresolved. Inspect a copy; do not delete a live
211
+ lock or signal a process you did not create. An uncertain cloud request still
212
+ needs its original operation/readback before any write is retried; releasing a
213
+ local lock does not prove the remote request failed.
214
+
215
+ The candidate uses pinned prebuilt native assets for Darwin/Linux, with no compiler
216
+ fallback in the package build. Actual crash and relocated-package acceptance is
217
+ recorded for Darwin arm64; other architectures, Windows, musl and network-file
218
+ systems require separate proof. Unsupported/native-unavailable writers fail
219
+ closed with actionable setup diagnostics. Native lock acceptance does not enable
220
+ Darwin OAuth custody or establish customer credentials/grants.
@@ -0,0 +1,73 @@
1
+ # User-managed disavow lists
2
+
3
+ This is an offline record/file tool. It does not evaluate whether a link
4
+ should be disavowed, connect to Search Console, or upload a file. Record the
5
+ user's explicit choices and their provenance.
6
+
7
+ Google's documented file format uses one page URL or `domain:hostname` per
8
+ line, UTF-8 or ASCII `.txt` files, and `#` comment lines. The limits are 2,048
9
+ characters per URL, 100,000 lines including comments/blanks, and 2 MB. Domain
10
+ and subdomain rules are supported; a path prefix is not a domain-wide rule.
11
+ The tool does not support Search Console Domain properties. Uploading a new
12
+ list replaces the list for the selected property; local merging here does
13
+ not establish what Google currently holds. Google says most sites do not
14
+ need disavowals. [Official format and usage guidance](https://support.google.com/webmasters/answer/2648487?hl=en),
15
+ checked September 9, 2026.
16
+
17
+ ## Commands
18
+
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
+
21
+ ```sh
22
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite migrate
23
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite disavow import --property https://my-product.com/ --source "User-reviewed existing list" --file existing-list.txt
24
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite disavow list --property https://my-product.com/
25
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite disavow export --property https://my-product.com/ --output reviewed-disavow.txt
26
+ ```
27
+
28
+ Every command requires the exact URL-prefix property. HTTP and HTTPS remain
29
+ separate; a subdomain or path prefix remains separate from its parent. The
30
+ helper records this local scope; it does not verify property ownership.
31
+
32
+ Imports merge rules transactionally. Any invalid line rejects the entire
33
+ file with no new rules or source record. Duplicates reuse stable rule IDs.
34
+ Each source retains its label, original text and SHA-256; provenance records
35
+ the original line, line number and associated comments. Reimporting identical
36
+ content under the same source label is idempotent. A new source label adds
37
+ provenance without duplicating the rule. Imports never overwrite local notes
38
+ or reactivate a rule the user disabled.
39
+
40
+ To add or explicitly change a rule, prepare `rule.json` and run:
41
+
42
+ ```sh
43
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite disavow upsert --property https://my-product.com/ --source "User decision after review" --file rule.json
44
+ ```
45
+
46
+ ```json
47
+ {"kind":"domain","value":"publisher.example","active":false,"notes":"User removed this rule after reviewing the source."}
48
+ ```
49
+
50
+ `kind` is `domain` or `url`. `active` defaults to the existing value, or true
51
+ for a new rule. Omitted notes are preserved. Deactivation is reversible and
52
+ retains provenance; there is no destructive delete command.
53
+
54
+ Domain normalization changes case and a terminal DNS dot only. Hostnames
55
+ must be ASCII or already Punycode; the helper does not guess IDN conversion.
56
+ URLs preserve path case, query strings and fragments. A path ending in `/`
57
+ is still one exact page URL; wildcard patterns are rejected. URL credentials
58
+ and malformed domains are rejected. UTF-8 paths, LF/CRLF and an optional UTF-8
59
+ BOM are accepted. Original source text remains available in the full JSON
60
+ CRM export.
61
+
62
+ `.txt` exports contain active rules for one property, sorted by rule kind and
63
+ value with stable header comments. They omit timestamps and private notes,
64
+ so unchanged rules produce identical bytes. The export is checked against
65
+ the documented line limit and a conservative 2,000,000-byte ceiling, and never
66
+ overwrites an existing file. The same byte ceiling applies to imports.
67
+ Without `--output`, it writes the same text to stdout. The local list can be
68
+ empty; an empty export is not evidence that a Google-side list was cleared.
69
+
70
+ `disavow list --active-only` filters the view. The general `export` command
71
+ includes all disavow rules, sources and provenance in the local JSON backup.
72
+ No monitoring result, authority metric or contact inspection automatically
73
+ creates a rule.
@@ -0,0 +1,32 @@
1
+ # From research to a checked shortlist
2
+
3
+ Keep research notes beside the user's project. The graph release, source retrieval date and qualification explain why a page is on the shortlist. They do not establish a live backlink.
4
+
5
+ Use `agentlinkops skill --list` to see the installed set and `agentlinkops skill agentlinkops-discovery` to read the research workflow. `agentlinkops skill` still prints the connection and evidence reference.
6
+
7
+ Create a CSV with exact source and target pages:
8
+
9
+ ```csv
10
+ source_url,target_url
11
+ https://publisher.example.org/resources,https://example.com/guide
12
+ ```
13
+
14
+ Preview normalization before writing:
15
+
16
+ ```sh
17
+ agentlinkops import shortlist.csv --target example.com --from csv --map source=source_url,target=target_url
18
+ ```
19
+
20
+ The preview is not a live check or a watch. Inspect accepted and rejected rows. For a selected opportunity that has not been earned, initialize a local ledger when needed and add it as wanted:
21
+
22
+ ```sh
23
+ agentlinkops init
24
+ agentlinkops add --source https://publisher.example.org/resources --target https://example.com/guide --intent wanted --note "Research candidate; link not yet earned"
25
+ agentlinkops check --all --json
26
+ ```
27
+
28
+ The local check includes wanted entries; `--all` rechecks the selected rows even if their cadence is not yet due. Keep observed absence separate from unknown access failures. Once the user has an earned placement, record the exact source/target pair as expected and retain the observation date and evidence.
29
+
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
+
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.
@@ -0,0 +1,53 @@
1
+ # Branded HTML placement reports
2
+
3
+ Reports are generated entirely from the local CRM. They make no network
4
+ requests, run no additional checks, and do not imply broader backlink-index
5
+ coverage. The output is standalone HTML with embedded styles and print
6
+ styling; it is not a generated PDF.
7
+
8
+ ```sh
9
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite report --project project-id --brand "My Agency" --title "Placement monitoring report" --output placements.html
10
+ ```
11
+
12
+ Omit `--project` only when the requested report covers every local placement.
13
+ By default, a report includes source/destination URLs, cloud-observed state,
14
+ observation timestamp, report timestamp and aggregate placement counts.
15
+ It excludes local notes, local costs, contacts, outreach activity, credentials,
16
+ disavow rules, workspace IDs and raw event payloads. URLs are the selected
17
+ records' actual URLs; review the selection before sharing the file.
18
+
19
+ `--include-notes` and `--include-costs` explicitly add those private local
20
+ fields. Use these only when the user requests that content. Included cost
21
+ totals are calculated separately per currency without exchange rates.
22
+ Local CRM status is not substituted for a verified cloud observation.
23
+
24
+ Record a cost with an ordinary partial placement upsert:
25
+
26
+ ```json
27
+ {"id":"placement-id","cost_amount":"12.50","cost_currency":"USD"}
28
+ ```
29
+
30
+ Amounts are nonnegative decimal strings with at most six decimal places,
31
+ not JSON floating-point numbers. Both amount and currency are required for
32
+ a newly recorded cost. The currency is an uppercase three-letter label;
33
+ the helper does not perform a current currency-directory lookup. Updating
34
+ other fields or synchronizing cloud events preserves local costs. Set both
35
+ cost fields to null to clear a cost explicitly.
36
+
37
+ The generator escapes all text/attributes and creates clickable links only
38
+ for HTTP(S) URLs without credentials. It loads no external scripts, fonts,
39
+ logos or tracking pixels. The title and brand are user-selected text labels.
40
+ Local notes remain escaped text, even if they contain HTML.
41
+
42
+ Rows have a stable ordering. Supply a fixed `--generated-at` ISO 8601
43
+ timestamp to reproduce identical output from the same data and options:
44
+
45
+ ```sh
46
+ python3 references/portable/scripts/agentlinkops.py --db ./agentlinkops.sqlite report --title "Placement report" --generated-at 2026-09-09T14:00:00Z --output reproducible.html
47
+ ```
48
+
49
+ Otherwise the timestamp is the current time. Coverage reports the range of
50
+ stored observation times separately from report generation. Unknown and
51
+ unobserved states remain distinct from confirmed loss. Existing output files
52
+ are never overwritten. Browser printing, PDF generation and automated report
53
+ delivery are separate capabilities and are not performed by this helper.