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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.mcp.json +8 -0
- package/CHANGELOG.md +15 -1
- package/LICENSE +0 -0
- package/NOTICE +0 -0
- package/README.md +2 -1
- package/bin/agentlinkops.mjs +0 -0
- package/bin/linktrail.mjs +0 -0
- package/cli/adapters/csv.js +0 -0
- package/cli/adapters/sqlite-crm.js +0 -0
- package/cli/adapters/suppliers.js +0 -0
- package/cli/agent-setup.js +0 -0
- package/cli/agentlinkops.mjs +0 -0
- package/cli/args.js +0 -0
- package/cli/catalog.js +0 -0
- package/cli/check.js +0 -0
- package/cli/citation.js +124 -2
- package/cli/client.js +1 -1
- package/cli/commands.js +0 -0
- package/cli/config.js +0 -0
- package/cli/connection.js +0 -0
- package/cli/context-mcp.js +0 -0
- package/cli/context.js +0 -0
- package/cli/delivery.js +0 -0
- package/cli/doctor.js +19 -0
- package/cli/env.js +0 -0
- package/cli/fleet.js +0 -0
- package/cli/gsc-browser.js +0 -0
- package/cli/import.js +0 -0
- package/cli/ledger.js +0 -0
- package/cli/linktrail.mjs +0 -0
- package/cli/local-result.js +0 -0
- package/cli/main.js +0 -0
- package/cli/mirror.js +0 -0
- package/cli/mix.js +0 -0
- package/cli/platforms.js +0 -0
- package/cli/receipt-performance.js +0 -0
- package/cli/receipts.js +0 -0
- package/cli/report.js +0 -0
- package/cli/setup-plan.js +0 -0
- package/cli/skill.js +0 -0
- package/cli/state.js +0 -0
- package/cli/status.js +0 -0
- package/cli/sync.js +0 -0
- package/package.json +14 -3
- package/references/cli.md +0 -0
- package/references/disavow.md +0 -0
- package/references/reports.md +0 -0
- package/scripts/agentlinkops.py +0 -0
- package/scripts/disavow.py +0 -0
- package/scripts/linktrail.py +0 -0
- package/scripts/migrations/001_crm.sql +0 -0
- package/scripts/migrations/002_cloud_events.sql +0 -0
- package/scripts/migrations/003_disavow.sql +0 -0
- package/scripts/migrations/004_placement_costs.sql +0 -0
- package/scripts/report.py +0 -0
- package/shared/agent-clients.js +0 -0
- package/shared/observation-event.js +0 -0
- package/shared/response-contract.js +0 -0
- package/shared/search-connection-contract.js +0 -0
- package/skills/agentlinkops-assets/SKILL.md +0 -0
- package/skills/agentlinkops-assets/agents/openai.yaml +0 -0
- package/skills/agentlinkops-campaigns/SKILL.md +0 -0
- package/skills/agentlinkops-campaigns/agents/openai.yaml +0 -0
- package/skills/agentlinkops-connect/SKILL.md +0 -0
- package/skills/agentlinkops-connect/agents/openai.yaml +0 -0
- package/skills/agentlinkops-connect/references/agentlinkops.md +0 -0
- package/skills/agentlinkops-crm/SKILL.md +0 -0
- package/skills/agentlinkops-crm/agents/openai.yaml +0 -0
- package/skills/content-defingerprinting/SKILL.md +0 -0
- package/skills/content-defingerprinting/scripts/watermarks/README.md +0 -0
- package/skills/content-defingerprinting/scripts/watermarks/UPSTREAM.json +0 -0
- package/skills/content-defingerprinting/scripts/watermarks/revendor.sh +0 -0
- package/skills/content-defingerprinting/scripts/watermarks/vendor/LICENSE.upstream +0 -0
- package/skills/content-defingerprinting/scripts/watermarks/vendor/clean_text.py +0 -0
- package/skills/content-defingerprinting/scripts/watermarks/vendor/common.py +0 -0
- package/skills/content-defingerprinting/scripts/watermarks/vendor/inspect_text.py +0 -0
- package/skills/content-defingerprinting/scripts/watermarks/vendor/score_stylometry.py +0 -0
- package/skills/content-defingerprinting/scripts/watermarks/vendor/text_unicode.py +0 -0
- package/skills/content-defingerprinting/scripts/watermarks/verify-vendor.py +0 -0
- package/skills/content-defingerprinting/scripts/watermarks/wm-scan.py +0 -0
- package/skills/internal-linking-optimizer/SKILL.md +0 -0
- package/skills/internal-linking-optimizer/references/source-prompt.md +0 -0
- package/skills/pre-publish-review/SKILL.md +0 -0
- package/skills/source-cited-content-builder/SKILL.md +0 -0
- package/skills/source-cited-content-builder/references/source-prompt.md +0 -0
- package/skills/source-cited-humanizer/SKILL.md +0 -0
- package/src/citations/adapter.js +173 -0
- package/src/citations/alert.js +53 -0
- package/src/citations/browser/engine.js +267 -0
- package/src/citations/browser/gen/core/providers/_shared/google.js +4 -0
- package/src/citations/browser/gen/core/providers/_shared/rawSourcesDom.js +15 -0
- package/src/citations/browser/gen/core/providers/_shared/resetProviderPage.js +22 -0
- package/src/citations/browser/gen/core/providers/_shared/sourceUtils.js +8 -0
- package/src/citations/browser/gen/core/providers/ai-overview/index.js +70 -0
- package/src/citations/browser/gen/core/providers/ai-overview/lib/expand.js +59 -0
- package/src/citations/browser/gen/core/providers/ai-overview/lib/extractSources.js +60 -0
- package/src/citations/browser/gen/core/providers/ai-overview/lib/pageLifecycle.js +18 -0
- package/src/citations/browser/gen/core/providers/ai-overview/lib/session.js +62 -0
- package/src/citations/browser/gen/core/providers/chatgpt/index.js +28 -0
- package/src/citations/browser/gen/core/providers/chatgpt/lib/dismissAuthModal.js +39 -0
- package/src/citations/browser/gen/core/providers/chatgpt/lib/extractSources.js +66 -0
- package/src/citations/browser/gen/core/providers/chatgpt/lib/pageLifecycle.js +11 -0
- package/src/citations/browser/gen/core/providers/claude/index.js +16 -0
- package/src/citations/browser/gen/core/providers/claude/lib/extractSources.js +54 -0
- package/src/citations/browser/gen/core/providers/claude/lib/pageLifecycle.js +9 -0
- package/src/citations/browser/gen/core/providers/gemini/index.js +40 -0
- package/src/citations/browser/gen/core/providers/gemini/lib/extractSources.js +65 -0
- package/src/citations/browser/gen/core/providers/gemini/lib/pageLifecycle.js +14 -0
- package/src/citations/browser/gen/core/providers/gemini/lib/session.js +45 -0
- package/src/citations/browser/gen/core/providers/index.js +15 -0
- package/src/citations/browser/gen/core/providers/perplexity/index.js +31 -0
- package/src/citations/browser/gen/core/providers/perplexity/lib/extractSources.js +84 -0
- package/src/citations/browser/gen/core/providers/perplexity/lib/navigation.js +38 -0
- package/src/citations/browser/gen/core/providers/types.js +0 -0
- package/src/citations/browser/gen/lib/browser/camoufox.js +587 -0
- package/src/citations/browser/gen/lib/browser/display.js +147 -0
- package/src/citations/browser/gen/lib/browser/domOps.js +339 -0
- package/src/citations/browser/gen/lib/browser/humanBehavior.js +121 -0
- package/src/citations/browser/gen/lib/browser/navigate.js +46 -0
- package/src/citations/browser/gen/lib/browser/playwrightCompat.js +321 -0
- package/src/citations/browser/gen/lib/browser/proxy/forwarder.js +251 -0
- package/src/citations/browser/gen/lib/browser/runtimeTypes.js +0 -0
- package/src/citations/browser/gen/lib/extraction/sourceUtils.js +71 -0
- package/src/citations/browser/gen/lib/input/editor/assertNotBlocked.js +54 -0
- package/src/citations/browser/gen/lib/input/editor/clearInput.js +52 -0
- package/src/citations/browser/gen/lib/input/editor/findEditor.js +54 -0
- package/src/citations/browser/gen/lib/input/editor/findSendButton.js +21 -0
- package/src/citations/browser/gen/lib/input/editor/promptInput.js +117 -0
- package/src/citations/browser/gen/lib/input/markdown/converter.js +101 -0
- package/src/citations/browser/gen/lib/input/markdown/toMarkdown.js +14 -0
- package/src/citations/browser/gen/lib/input/response/detectBotPage.js +13 -0
- package/src/citations/browser/gen/lib/input/response/getText.js +10 -0
- package/src/citations/browser/gen/lib/input/response/isGenerating.js +59 -0
- package/src/citations/browser/gen/lib/input/response/waitForFinish.js +105 -0
- package/src/citations/browser/gen/lib/input/sources/findButton.js +41 -0
- package/src/citations/browser/gen/lib/input/sources/openPanel.js +11 -0
- package/src/citations/browser/gen/utils/agent-constants.js +87 -0
- package/src/citations/browser/providers.js +166 -0
- package/src/citations/browser/shims/errors.js +26 -0
- package/src/citations/browser/shims/types.js +6 -0
- package/src/citations/browser/shims/utils.js +32 -0
- package/src/citations/contract.js +19 -3
- package/src/citations/fixtures/dataforseo-aio.json +82 -0
- package/src/citations/match.js +0 -0
- package/src/citations/panel-builder.js +44 -0
- package/src/citations/report.js +165 -0
- package/src/citations/runner.js +43 -16
- package/src/citations/stats.js +63 -0
- package/src/citations/watch.js +85 -0
- package/src/competitors/contract.js +0 -0
- package/src/competitors/domain-mix.js +0 -0
- package/src/context/ga4.js +0 -0
- package/src/context/gsc.js +0 -0
- package/src/context/site-profile.js +0 -0
- package/src/context/util.js +0 -0
- package/src/context-tool-registry.js +0 -0
- package/src/corpus/acquire.js +0 -0
- package/src/corpus/seeds.js +0 -0
- package/src/discovery/contract.js +0 -0
- package/src/discovery/import.js +0 -0
- package/src/tool-views.js +0 -0
- package/src/verifier/fetch.js +0 -0
- package/src/verifier/index.js +0 -0
- package/src/verifier/observation-kind.js +0 -0
- package/src/verifier/parser.js +0 -0
- package/src/verifier/readiness.js +0 -0
- package/src/verifier/render-evidence.js +0 -0
- package/src/verifier/robots.js +0 -0
- package/src/verifier/transition.js +0 -0
- package/src/verifier/url.js +0 -0
- package/templates/SITE.template.md +0 -0
- package/templates/campaigns.v1.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlinkops",
|
|
3
|
-
"version": "0.5.
|
|
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.
|
|
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
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.
|
|
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
|
|
package/bin/agentlinkops.mjs
CHANGED
|
File without changes
|
package/bin/linktrail.mjs
CHANGED
|
File without changes
|
package/cli/adapters/csv.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/cli/agent-setup.js
CHANGED
|
File without changes
|
package/cli/agentlinkops.mjs
CHANGED
|
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.
|
|
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
|
package/cli/context-mcp.js
CHANGED
|
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
|
package/cli/gsc-browser.js
CHANGED
|
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
|
package/cli/local-result.js
CHANGED
|
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.
|
|
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
|
package/references/disavow.md
CHANGED
|
File without changes
|
package/references/reports.md
CHANGED
|
File without changes
|
package/scripts/agentlinkops.py
CHANGED
|
File without changes
|
package/scripts/disavow.py
CHANGED
|
File without changes
|
package/scripts/linktrail.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/scripts/report.py
CHANGED
|
File without changes
|
package/shared/agent-clients.js
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
|
|
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
|