agentlinkops 0.5.0 → 0.6.0

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 (174) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/.mcp.json +8 -0
  4. package/CHANGELOG.md +15 -1
  5. package/LICENSE +0 -0
  6. package/NOTICE +0 -0
  7. package/README.md +2 -1
  8. package/bin/agentlinkops.mjs +0 -0
  9. package/bin/linktrail.mjs +0 -0
  10. package/cli/adapters/csv.js +0 -0
  11. package/cli/adapters/sqlite-crm.js +0 -0
  12. package/cli/adapters/suppliers.js +0 -0
  13. package/cli/agent-setup.js +0 -0
  14. package/cli/agentlinkops.mjs +0 -0
  15. package/cli/args.js +0 -0
  16. package/cli/catalog.js +0 -0
  17. package/cli/check.js +0 -0
  18. package/cli/citation.js +124 -2
  19. package/cli/client.js +1 -1
  20. package/cli/commands.js +0 -0
  21. package/cli/config.js +0 -0
  22. package/cli/connection.js +0 -0
  23. package/cli/context-mcp.js +0 -0
  24. package/cli/context.js +0 -0
  25. package/cli/delivery.js +0 -0
  26. package/cli/doctor.js +19 -0
  27. package/cli/env.js +0 -0
  28. package/cli/fleet.js +0 -0
  29. package/cli/gsc-browser.js +0 -0
  30. package/cli/import.js +0 -0
  31. package/cli/ledger.js +0 -0
  32. package/cli/linktrail.mjs +0 -0
  33. package/cli/local-result.js +0 -0
  34. package/cli/main.js +0 -0
  35. package/cli/mirror.js +0 -0
  36. package/cli/mix.js +0 -0
  37. package/cli/platforms.js +0 -0
  38. package/cli/receipt-performance.js +0 -0
  39. package/cli/receipts.js +0 -0
  40. package/cli/report.js +0 -0
  41. package/cli/setup-plan.js +0 -0
  42. package/cli/skill.js +0 -0
  43. package/cli/state.js +0 -0
  44. package/cli/status.js +0 -0
  45. package/cli/sync.js +0 -0
  46. package/package.json +14 -3
  47. package/references/cli.md +0 -0
  48. package/references/disavow.md +0 -0
  49. package/references/reports.md +0 -0
  50. package/scripts/agentlinkops.py +0 -0
  51. package/scripts/disavow.py +0 -0
  52. package/scripts/linktrail.py +0 -0
  53. package/scripts/migrations/001_crm.sql +0 -0
  54. package/scripts/migrations/002_cloud_events.sql +0 -0
  55. package/scripts/migrations/003_disavow.sql +0 -0
  56. package/scripts/migrations/004_placement_costs.sql +0 -0
  57. package/scripts/report.py +0 -0
  58. package/shared/agent-clients.js +0 -0
  59. package/shared/observation-event.js +0 -0
  60. package/shared/response-contract.js +0 -0
  61. package/shared/search-connection-contract.js +0 -0
  62. package/skills/agentlinkops-assets/SKILL.md +0 -0
  63. package/skills/agentlinkops-assets/agents/openai.yaml +0 -0
  64. package/skills/agentlinkops-campaigns/SKILL.md +0 -0
  65. package/skills/agentlinkops-campaigns/agents/openai.yaml +0 -0
  66. package/skills/agentlinkops-connect/SKILL.md +0 -0
  67. package/skills/agentlinkops-connect/agents/openai.yaml +0 -0
  68. package/skills/agentlinkops-connect/references/agentlinkops.md +0 -0
  69. package/skills/agentlinkops-crm/SKILL.md +0 -0
  70. package/skills/agentlinkops-crm/agents/openai.yaml +0 -0
  71. package/skills/content-defingerprinting/SKILL.md +0 -0
  72. package/skills/content-defingerprinting/scripts/watermarks/README.md +0 -0
  73. package/skills/content-defingerprinting/scripts/watermarks/UPSTREAM.json +0 -0
  74. package/skills/content-defingerprinting/scripts/watermarks/revendor.sh +0 -0
  75. package/skills/content-defingerprinting/scripts/watermarks/vendor/LICENSE.upstream +0 -0
  76. package/skills/content-defingerprinting/scripts/watermarks/vendor/clean_text.py +0 -0
  77. package/skills/content-defingerprinting/scripts/watermarks/vendor/common.py +0 -0
  78. package/skills/content-defingerprinting/scripts/watermarks/vendor/inspect_text.py +0 -0
  79. package/skills/content-defingerprinting/scripts/watermarks/vendor/score_stylometry.py +0 -0
  80. package/skills/content-defingerprinting/scripts/watermarks/vendor/text_unicode.py +0 -0
  81. package/skills/content-defingerprinting/scripts/watermarks/verify-vendor.py +0 -0
  82. package/skills/content-defingerprinting/scripts/watermarks/wm-scan.py +0 -0
  83. package/skills/internal-linking-optimizer/SKILL.md +0 -0
  84. package/skills/internal-linking-optimizer/references/source-prompt.md +0 -0
  85. package/skills/pre-publish-review/SKILL.md +0 -0
  86. package/skills/source-cited-content-builder/SKILL.md +0 -0
  87. package/skills/source-cited-content-builder/references/source-prompt.md +0 -0
  88. package/skills/source-cited-humanizer/SKILL.md +0 -0
  89. package/src/citations/adapter.js +173 -0
  90. package/src/citations/alert.js +53 -0
  91. package/src/citations/browser/engine.js +267 -0
  92. package/src/citations/browser/gen/core/providers/_shared/google.js +4 -0
  93. package/src/citations/browser/gen/core/providers/_shared/rawSourcesDom.js +15 -0
  94. package/src/citations/browser/gen/core/providers/_shared/resetProviderPage.js +22 -0
  95. package/src/citations/browser/gen/core/providers/_shared/sourceUtils.js +8 -0
  96. package/src/citations/browser/gen/core/providers/ai-overview/index.js +70 -0
  97. package/src/citations/browser/gen/core/providers/ai-overview/lib/expand.js +59 -0
  98. package/src/citations/browser/gen/core/providers/ai-overview/lib/extractSources.js +60 -0
  99. package/src/citations/browser/gen/core/providers/ai-overview/lib/pageLifecycle.js +18 -0
  100. package/src/citations/browser/gen/core/providers/ai-overview/lib/session.js +62 -0
  101. package/src/citations/browser/gen/core/providers/chatgpt/index.js +28 -0
  102. package/src/citations/browser/gen/core/providers/chatgpt/lib/dismissAuthModal.js +39 -0
  103. package/src/citations/browser/gen/core/providers/chatgpt/lib/extractSources.js +66 -0
  104. package/src/citations/browser/gen/core/providers/chatgpt/lib/pageLifecycle.js +11 -0
  105. package/src/citations/browser/gen/core/providers/claude/index.js +16 -0
  106. package/src/citations/browser/gen/core/providers/claude/lib/extractSources.js +54 -0
  107. package/src/citations/browser/gen/core/providers/claude/lib/pageLifecycle.js +9 -0
  108. package/src/citations/browser/gen/core/providers/gemini/index.js +40 -0
  109. package/src/citations/browser/gen/core/providers/gemini/lib/extractSources.js +65 -0
  110. package/src/citations/browser/gen/core/providers/gemini/lib/pageLifecycle.js +14 -0
  111. package/src/citations/browser/gen/core/providers/gemini/lib/session.js +45 -0
  112. package/src/citations/browser/gen/core/providers/index.js +15 -0
  113. package/src/citations/browser/gen/core/providers/perplexity/index.js +31 -0
  114. package/src/citations/browser/gen/core/providers/perplexity/lib/extractSources.js +84 -0
  115. package/src/citations/browser/gen/core/providers/perplexity/lib/navigation.js +38 -0
  116. package/src/citations/browser/gen/core/providers/types.js +0 -0
  117. package/src/citations/browser/gen/lib/browser/camoufox.js +587 -0
  118. package/src/citations/browser/gen/lib/browser/display.js +147 -0
  119. package/src/citations/browser/gen/lib/browser/domOps.js +339 -0
  120. package/src/citations/browser/gen/lib/browser/humanBehavior.js +121 -0
  121. package/src/citations/browser/gen/lib/browser/navigate.js +46 -0
  122. package/src/citations/browser/gen/lib/browser/playwrightCompat.js +321 -0
  123. package/src/citations/browser/gen/lib/browser/proxy/forwarder.js +251 -0
  124. package/src/citations/browser/gen/lib/browser/runtimeTypes.js +0 -0
  125. package/src/citations/browser/gen/lib/extraction/sourceUtils.js +71 -0
  126. package/src/citations/browser/gen/lib/input/editor/assertNotBlocked.js +54 -0
  127. package/src/citations/browser/gen/lib/input/editor/clearInput.js +52 -0
  128. package/src/citations/browser/gen/lib/input/editor/findEditor.js +54 -0
  129. package/src/citations/browser/gen/lib/input/editor/findSendButton.js +21 -0
  130. package/src/citations/browser/gen/lib/input/editor/promptInput.js +117 -0
  131. package/src/citations/browser/gen/lib/input/markdown/converter.js +101 -0
  132. package/src/citations/browser/gen/lib/input/markdown/toMarkdown.js +14 -0
  133. package/src/citations/browser/gen/lib/input/response/detectBotPage.js +13 -0
  134. package/src/citations/browser/gen/lib/input/response/getText.js +10 -0
  135. package/src/citations/browser/gen/lib/input/response/isGenerating.js +59 -0
  136. package/src/citations/browser/gen/lib/input/response/waitForFinish.js +105 -0
  137. package/src/citations/browser/gen/lib/input/sources/findButton.js +41 -0
  138. package/src/citations/browser/gen/lib/input/sources/openPanel.js +11 -0
  139. package/src/citations/browser/gen/utils/agent-constants.js +87 -0
  140. package/src/citations/browser/providers.js +166 -0
  141. package/src/citations/browser/shims/errors.js +26 -0
  142. package/src/citations/browser/shims/types.js +6 -0
  143. package/src/citations/browser/shims/utils.js +32 -0
  144. package/src/citations/contract.js +19 -3
  145. package/src/citations/fixtures/dataforseo-aio.json +82 -0
  146. package/src/citations/match.js +0 -0
  147. package/src/citations/panel-builder.js +44 -0
  148. package/src/citations/report.js +165 -0
  149. package/src/citations/runner.js +43 -16
  150. package/src/citations/stats.js +63 -0
  151. package/src/citations/watch.js +85 -0
  152. package/src/competitors/contract.js +0 -0
  153. package/src/competitors/domain-mix.js +0 -0
  154. package/src/context/ga4.js +0 -0
  155. package/src/context/gsc.js +0 -0
  156. package/src/context/site-profile.js +0 -0
  157. package/src/context/util.js +0 -0
  158. package/src/context-tool-registry.js +0 -0
  159. package/src/corpus/acquire.js +0 -0
  160. package/src/corpus/seeds.js +0 -0
  161. package/src/discovery/contract.js +0 -0
  162. package/src/discovery/import.js +0 -0
  163. package/src/tool-views.js +0 -0
  164. package/src/verifier/fetch.js +0 -0
  165. package/src/verifier/index.js +0 -0
  166. package/src/verifier/observation-kind.js +0 -0
  167. package/src/verifier/parser.js +0 -0
  168. package/src/verifier/readiness.js +0 -0
  169. package/src/verifier/render-evidence.js +0 -0
  170. package/src/verifier/robots.js +0 -0
  171. package/src/verifier/transition.js +0 -0
  172. package/src/verifier/url.js +0 -0
  173. package/templates/SITE.template.md +0 -0
  174. package/templates/campaigns.v1.json +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlinkops",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Connect an agent to AgentLinkOps and run backlink work with it: the connect skill (views, the three discovery verbs, evidence rules), local ledger and campaign skills, a portable CRM, the source-cited content pipeline, user-managed disavow lists, HTML reports and monitoring-event sync.",
5
5
  "author": {
6
6
  "name": "AgentLinkOps"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlinkops",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Connect an agent to AgentLinkOps and run backlink work with it: the connect skill (views, the three discovery verbs, evidence rules), local ledger and campaign skills, a portable CRM, the source-cited content pipeline, user-managed disavow lists, HTML reports and monitoring-event sync.",
5
5
  "author": {
6
6
  "name": "AgentLinkOps"
package/.mcp.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "agentlinkops": {
4
+ "type": "http",
5
+ "url": "https://app.agentlinkops.com/mcp"
6
+ }
7
+ }
8
+ }
package/CHANGELOG.md CHANGED
@@ -3,7 +3,21 @@
3
3
  All notable changes to the `agentlinkops` package are recorded here. The version follows the
4
4
  plugin manifests in `.claude-plugin/` and `.codex-plugin/`.
5
5
 
6
- ## 0.5.0 (unreleased; DP-0036 agent surface)
6
+ ## 0.5.1 (2026-09-17)
7
+
8
+ - The plugin's `.mcp.json` ships in the tarball. 0.5.0 left it out, so a marketplace install
9
+ got the skills but no MCP server entry; the package build now carries and requires it.
10
+ - Package identity: `author` is AgentLinkOps (https://agentlinkops.com); `repository` and
11
+ `bugs` point at `sunlover7/agentlinkops-cli`. No personal identity in the registry metadata.
12
+ - `agentlinkops citation run PANEL.json --engine chatgpt:web-own-browser`: measures the
13
+ consumer UI through the customer's own Camoufox browser instead of an API, with a screenshot
14
+ of every answer in the evidence. `--engine NAME[:PROVIDER]` overrides the panel's engine list.
15
+ `playwright-core` is an optional dependency; `doctor` reports the browser stack as a skip,
16
+ never a failure, when it is absent (API and mock engines need none of it).
17
+ - Repository: CI on every push and a tag-driven release workflow that publishes through npm
18
+ trusted publishing with provenance.
19
+
20
+ ## 0.5.0 (2026-09-16; DP-0036 agent surface)
7
21
 
8
22
  - `agentlinkops tools [TOOLSET]`, `agentlinkops describe NAME` and `agentlinkops call NAME`:
9
23
  the three discovery verbs, answered offline from the bundled catalog snapshot (`--refresh`
package/LICENSE CHANGED
File without changes
package/NOTICE CHANGED
File without changes
package/README.md CHANGED
@@ -37,7 +37,8 @@ Four steps, the same ones the docs and the landing page show. Node.js 22.13 or l
37
37
  The skill is a short door: the first time your agent uses AgentLinkOps in a session it reads
38
38
  the full agent reference, which `agentlinkops skill` prints and
39
39
  https://agentlinkops.com/SKILL.md serves. Hosted monitoring is an invitation-only pilot; the
40
- local commands below need no account.
40
+ local commands below need no account. The package is `agentlinkops` on npm, published by the
41
+ `agentlinkops` account; the source is https://github.com/sunlover7/agentlinkops-cli.
41
42
 
42
43
  ## Install the CLI on its own
43
44
 
File without changes
package/bin/linktrail.mjs CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/cli/args.js CHANGED
File without changes
package/cli/catalog.js CHANGED
File without changes
package/cli/check.js CHANGED
File without changes
package/cli/citation.js CHANGED
@@ -8,7 +8,7 @@
8
8
  // Exit codes follow the link commands: 0 nothing actionable (including
9
9
  // insufficient_data — an unknown never fails), 1 a cell classified declined with
10
10
  // complete evidence, 2 usage, configuration or budget-abort errors.
11
- import { resolve } from 'node:path';
11
+ import { resolve, join } from 'node:path';
12
12
 
13
13
  import { parseArgs } from './args.js';
14
14
  import { loadPanel, runEpoch } from '../src/citations/runner.js';
@@ -21,6 +21,21 @@ const USAGE = `agentlinkops citation — AI citation watches in this repository
21
21
  agentlinkops citation run PANEL.json [--dir DIR] [--samples N] [--max-usd USD] [--json]
22
22
  run one fixed-n epoch over a panel; evidence and
23
23
  tallies land under .agentlinkops/citations/
24
+ agentlinkops citation run PANEL.json --engine chatgpt:web-own-browser
25
+ measure the consumer UI through Camoufox
26
+ (dedicated account, personal cadence; evidence
27
+ includes a screenshot of every answer)
28
+ agentlinkops citation report PANEL.json [--out FILE] [--dir DIR]
29
+ render the latest epoch as a self-contained
30
+ HTML report: trends, screenshots inline,
31
+ Wilson intervals, honest classifications
32
+ agentlinkops citation watch PANEL.json --cadence weekly|daily|hourly|monthly
33
+ register a cron entry that runs the epoch
34
+ and generates the report on schedule
35
+ agentlinkops citation watch --list list registered citation watches
36
+ agentlinkops citation watch --remove NAME remove a registered watch
37
+ agentlinkops citation alert PANEL.json check the latest epoch for declines;
38
+ logs an alert line if any cell declined
24
39
  agentlinkops citation help this help
25
40
 
26
41
  Panel (JSON): targets (domain or url, brand, aliases), prompts, engines
@@ -37,6 +52,81 @@ export async function citationMain(argv = [], { cwd = process.cwd(), out = conso
37
52
  const args = parseArgs(argv);
38
53
  const command = args._[0] ?? 'help';
39
54
  if (command === 'help' || args.help) { out(USAGE); return 0; }
55
+ if (command === 'alert') {
56
+ const panelPath = args._[1];
57
+ if (!panelPath) { err('citation alert needs a panel file'); return 2; }
58
+ const { readFile } = await import('node:fs/promises');
59
+ const dir = resolve(cwd, args.dir ?? '.agentlinkops');
60
+ let latestEpochId = null;
61
+ try {
62
+ const epochsText = await readFile(join(dir, 'citations-epochs.jsonl'), 'utf8');
63
+ const rows = epochsText.split('\n').filter((l) => l.trim()).map((l) => JSON.parse(l));
64
+ if (rows.length > 0) latestEpochId = rows[rows.length - 1].epoch_id;
65
+ } catch { /* no epochs */ }
66
+ if (!latestEpochId) { out('no epochs to check'); return 0; }
67
+ const { shouldAlert } = await import('../src/citations/alert.js');
68
+ const epochsText = await readFile(join(dir, 'citations-epochs.jsonl'), 'utf8');
69
+ const cells = epochsText.split('\n').filter((l) => l.trim()).map((l) => JSON.parse(l)).filter((r) => r.epoch_id === latestEpochId);
70
+ if (shouldAlert(cells)) {
71
+ const declined = cells.filter((c) => c.classification === 'declined');
72
+ err(`ALERT: ${declined.length} cell${declined.length === 1 ? '' : 's'} declined:`);
73
+ for (const c of declined) err(` ${c.cell_id}: rate ${c.rate.toFixed(2)} (was ${c.baseline?.rate?.toFixed(2) ?? 'n/a'})`);
74
+ return 1; // exit 1 = actionable, same convention as link checks
75
+ }
76
+ out('no declines detected');
77
+ return 0;
78
+ }
79
+ if (command === 'watch') {
80
+ const { registerWatch, listWatches, removeWatch } = await import('../src/citations/watch.js');
81
+ if (args.list) {
82
+ const watches = await listWatches();
83
+ if (watches.length === 0) { out('no citation watches registered'); return 0; }
84
+ for (const w of watches) out(`${w.panel} ${w.cron}`);
85
+ return 0;
86
+ }
87
+ if (args.remove) {
88
+ const result = await removeWatch(args.remove);
89
+ out(`removed watch: ${result.removed}`);
90
+ return 0;
91
+ }
92
+ const panelPath = args._[1];
93
+ if (!panelPath) { err('citation watch needs a panel file (or --list / --remove)'); return 2; }
94
+ const cadence = args.cadence ?? 'weekly';
95
+ try {
96
+ const result = await registerWatch({ panelPath, cadence });
97
+ out(`citation watch registered: ${result.panelPath}`);
98
+ out(`cadence: ${result.cadence} (${result.next})`);
99
+ out(`cron: ${result.cronSpec}`);
100
+ return 0;
101
+ } catch (cause) {
102
+ err(cause.message);
103
+ return 2;
104
+ }
105
+ }
106
+ if (command === 'report') {
107
+ const panelPath = args._[1];
108
+ if (!panelPath) { err('citation report needs a panel file'); return 2; }
109
+ const dir = resolve(cwd, args.dir ?? '.agentlinkops');
110
+ const { generateReport } = await import('../src/citations/report.js');
111
+ const { readFile, writeFile: wf } = await import('node:fs/promises');
112
+ // Find the latest epoch for this panel by reading the epochs file
113
+ let latestEpochId = null;
114
+ try {
115
+ const epochsText = await readFile(join(dir, 'citations-epochs.jsonl'), 'utf8');
116
+ const rows = epochsText.split('\n').filter((l) => l.trim()).map((l) => JSON.parse(l));
117
+ if (rows.length > 0) latestEpochId = rows[rows.length - 1].epoch_id;
118
+ } catch { /* no epochs yet */ }
119
+ if (!latestEpochId) { err('no epochs found — run an epoch first'); return 2; }
120
+ const panel = JSON.parse(await readFile(resolve(cwd, panelPath), 'utf8'));
121
+ const html = await generateReport({ dir, epochId: latestEpochId, panel });
122
+ const outPath = args.out ? resolve(cwd, args.out) : join(dir, 'citations', `report-${latestEpochId.slice(0, 15)}.html`);
123
+ const { dirname, join: j } = await import('node:path');
124
+ const { mkdir } = await import('node:fs/promises');
125
+ await mkdir(dirname(outPath), { recursive: true });
126
+ await wf(outPath, html, 'utf8');
127
+ out(`report written: ${outPath} (${Math.round(html.length / 1024)} KB, self-contained)`);
128
+ return 0;
129
+ }
40
130
  if (command !== 'run') { err(`unknown citation command: ${command}`); err(USAGE); return 2; }
41
131
 
42
132
  const panelPath = args._[1];
@@ -53,6 +143,15 @@ export async function citationMain(argv = [], { cwd = process.cwd(), out = conso
53
143
  const dir = resolve(cwd, args.dir ?? '.agentlinkops');
54
144
  if (args.samples) panel.samples = Number(args.samples);
55
145
  if (args['max-usd']) panel.maxUsd = Number(args['max-usd']);
146
+ // --engine NAME[:PROVIDER] overrides the panel's engine list. The provider
147
+ // suffix picks the surface: chatgpt:web-own-browser measures the consumer
148
+ // UI through Camoufox; chatgpt:api would measure the API. Never averaged.
149
+ if (args.engine) {
150
+ const [engineName, providerTag] = String(args.engine).split(':');
151
+ const via = providerTag === 'web-own-browser' ? 'browser' : 'api';
152
+ panel.engines = [{ engine: engineName, via }];
153
+ err(`engine override: ${engineIdentity(panel.engines[0])}`);
154
+ }
56
155
  // Overrides are stated, never silent: a number the panel did not ask for is a
57
156
  // configuration decision the operator should see in the receipt.
58
157
  if (args.samples || args['max-usd']) {
@@ -63,8 +162,31 @@ export async function citationMain(argv = [], { cwd = process.cwd(), out = conso
63
162
  for (const spec of panel.engines) {
64
163
  const identity = engineIdentity(spec);
65
164
  if (engines.has(identity)) continue;
165
+ if (spec.via === 'browser') {
166
+ const { createBrowserEngine } = await import('../src/citations/browser/engine.js');
167
+ engines.set(identity, createBrowserEngine({ engineName: spec.engine, env }));
168
+ continue;
169
+ }
66
170
  if (spec.engine === 'mock') {
67
171
  engines.set(identity, createMockEngine({ fixtures: panel.mockFixtures ?? {} }));
172
+ } else if (spec.engine === 'google-aio') {
173
+ // Fixture-first: runs against the recorded response with no credentials.
174
+ // Activates live the moment DATAFORSEO_LOGIN + DATAFORSEO_PASSWORD exist.
175
+ const { createGoogleAioEngine } = await import('../src/citations/adapter.js');
176
+ const creds = env.DATAFORSEO_LOGIN && env.DATAFORSEO_PASSWORD
177
+ ? { login: env.DATAFORSEO_LOGIN, password: env.DATAFORSEO_PASSWORD }
178
+ : null;
179
+ engines.set(identity, createGoogleAioEngine({ credentials: creds }));
180
+ if (!creds) err(`google-aio: fixture mode (set DATAFORSEO_LOGIN + DATAFORSEO_PASSWORD for live)`);
181
+ } else if (spec.engine === 'duck') {
182
+ // Self-hosted Duck2api on the VPS: the whole Claude family plus GPT-5.6
183
+ // anonymously through duck.ai. Localhost endpoint, no key.
184
+ const { createOpenAiCompatibleEngine } = await import('../src/citations/adapter.js');
185
+ engines.set(identity, createOpenAiCompatibleEngine({
186
+ baseUrl: env.DUCK2API_URL ?? 'http://127.0.0.1:8090/v1',
187
+ model: spec.model ?? 'claude-haiku-4-5',
188
+ providerLabel: 'duck-anon',
189
+ }));
68
190
  } else if (spec.engine === 'perplexity') {
69
191
  // Presence only, never the value — the same rule doctor applies to tokens.
70
192
  if (!env.PERPLEXITY_API_KEY) {
@@ -79,7 +201,7 @@ export async function citationMain(argv = [], { cwd = process.cwd(), out = conso
79
201
  }
80
202
 
81
203
  try {
82
- const result = await runEpoch(panel, { dir, engines, delayMs: 250 });
204
+ const result = await runEpoch(panel, { dir, engines, delayMs: 250, err });
83
205
  if (args.json) {
84
206
  out(JSON.stringify(result, null, 2));
85
207
  } else {
package/cli/client.js CHANGED
@@ -2,7 +2,7 @@ import {restResponseContract} from '../shared/response-contract.js';
2
2
  // A small HTTP client for the cloud. Scoped keys only; no human session ever reaches here.
3
3
  // DP-0036-T13: the CLI names itself so the usage counters can tell it from other REST callers.
4
4
  // The version follows the plugin manifests; bump it with them.
5
- export const CLI_VERSION = '0.5.0';
5
+ export const CLI_VERSION = '0.5.1';
6
6
  export const CLI_USER_AGENT = `agentlinkops-cli/${CLI_VERSION}`;
7
7
  export class CloudError extends Error {
8
8
  constructor(code, status, details = null) {
package/cli/commands.js CHANGED
File without changes
package/cli/config.js CHANGED
File without changes
package/cli/connection.js CHANGED
File without changes
File without changes
package/cli/context.js CHANGED
File without changes
package/cli/delivery.js CHANGED
File without changes
package/cli/doctor.js CHANGED
@@ -261,6 +261,25 @@ export async function doctorMain(argv = [], { cwd = process.cwd(), out = console
261
261
  checks.push({ status: 'skip', name: 'citations', detail: 'no engine credential set; mock citation runs work, live runs need PERPLEXITY_API_KEY' });
262
262
  }
263
263
 
264
+ // Browser engines: the Camoufox stack. Each missing piece names its fix; none
265
+ // of it fails doctor, because API and mock engines do not need it.
266
+ try {
267
+ await import('playwright-core');
268
+ const { execFile } = await import('node:child_process');
269
+ const camoufoxOk = await new Promise((resolve) => {
270
+ // `camoufox path` resolves the fetched browser and exits 0; the launcher
271
+ // has no --version flag and exits nonzero on it.
272
+ execFile('camoufox', ['path'], { timeout: 8000 }, (error) => resolve(!error));
273
+ });
274
+ if (camoufoxOk) {
275
+ checks.push({ status: 'ok', name: 'browser', detail: 'playwright-core + camoufox present; browser engines available' });
276
+ } else {
277
+ checks.push({ status: 'skip', name: 'browser', detail: 'playwright-core ok but camoufox missing', fix: 'python3 -m pip install cloverlabs-camoufox[geoip] && python3 -m camoufox fetch' });
278
+ }
279
+ } catch {
280
+ checks.push({ status: 'skip', name: 'browser', detail: 'browser engines unavailable: playwright-core not installed', fix: 'npm install playwright-core (API and mock engines are unaffected)' });
281
+ }
282
+
264
283
  out(`agentlinkops doctor — ${config.dir}`);
265
284
 
266
285
  // Record the probe outcomes beside the cursors they protect — BEFORE rendering, so the
package/cli/env.js CHANGED
File without changes
package/cli/fleet.js CHANGED
File without changes
File without changes
package/cli/import.js CHANGED
File without changes
package/cli/ledger.js CHANGED
File without changes
package/cli/linktrail.mjs CHANGED
File without changes
File without changes
package/cli/main.js CHANGED
File without changes
package/cli/mirror.js CHANGED
File without changes
package/cli/mix.js CHANGED
File without changes
package/cli/platforms.js CHANGED
File without changes
File without changes
package/cli/receipts.js CHANGED
File without changes
package/cli/report.js CHANGED
File without changes
package/cli/setup-plan.js CHANGED
File without changes
package/cli/skill.js CHANGED
File without changes
package/cli/state.js CHANGED
File without changes
package/cli/status.js CHANGED
File without changes
package/cli/sync.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agentlinkops",
3
- "version": "0.5.0",
4
- "description": "A backlink ledger that lives in your repository: local link checks with dated evidence, supplier-export imports and optional sync to the hosted AgentLinkOps service.",
3
+ "version": "0.6.0",
4
+ "description": "A backlink and AI citation ledger that lives in your repository: local link and citation checks with dated evidence, supplier-export imports and optional sync to the hosted AgentLinkOps service.",
5
5
  "author": "AgentLinkOps (https://agentlinkops.com)",
6
6
  "license": "Apache-2.0",
7
7
  "type": "module",
@@ -21,6 +21,7 @@
21
21
  "templates",
22
22
  "references",
23
23
  "scripts",
24
+ ".mcp.json",
24
25
  ".claude-plugin",
25
26
  ".codex-plugin",
26
27
  "README.md",
@@ -45,15 +46,25 @@
45
46
  "mcp",
46
47
  "backlinks",
47
48
  "seo",
49
+ "citation-monitoring",
50
+ "ai-citations",
48
51
  "agent",
49
52
  "cli",
50
53
  "link-monitoring",
54
+ "citation-watch",
51
55
  "ledger",
52
56
  "jsonl"
53
57
  ],
54
58
  "dependencies": {
55
59
  "@modelcontextprotocol/server": "2.0.0",
56
60
  "parse5": "8.0.1",
61
+ "turndown": "^7.2.4",
57
62
  "zod": "4.6.0"
63
+ },
64
+ "devDependencies": {
65
+ "esbuild": "^0.28.2"
66
+ },
67
+ "optionalDependencies": {
68
+ "playwright-core": "^1.63.0"
58
69
  }
59
- }
70
+ }
package/references/cli.md CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/scripts/report.py CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes