agentlinkops 0.6.11 → 0.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +2 -2
- package/.mcp.json +0 -0
- package/CHANGELOG.md +35 -0
- package/LICENSE +0 -0
- package/NOTICE +0 -0
- package/PACK-ARTIFACT.json +16 -0
- package/PACKS.json +591 -0
- package/README.md +34 -48
- 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 +1 -1
- package/cli/admission.js +0 -0
- package/cli/agent-setup.js +101 -17
- package/cli/agentlinkops.mjs +0 -0
- package/cli/args.js +0 -0
- package/cli/campaign-bridge.js +325 -0
- package/cli/campaign-outcomes.js +107 -0
- package/cli/catalog.js +47686 -16398
- package/cli/check.js +40 -10
- package/cli/chronology.js +39 -0
- package/cli/citation.js +0 -0
- package/cli/client.js +2 -1
- package/cli/cloud-error.js +0 -0
- package/cli/commands.js +17 -8
- package/cli/config.js +0 -0
- package/cli/connection.js +92 -18
- package/cli/contact-adoption.js +91 -0
- package/cli/context-mcp.js +0 -0
- package/cli/context.js +13 -2
- package/cli/delivery.js +0 -0
- package/cli/disavow.js +0 -0
- package/cli/doctor.js +15 -4
- package/cli/env.js +0 -0
- package/cli/fleet.js +30 -19
- package/cli/gsc-browser.js +0 -0
- package/cli/import.js +7 -1
- package/cli/index-observations.js +0 -0
- package/cli/index-submissions.js +0 -0
- package/cli/key-store.js +115 -0
- package/cli/ledger.js +1 -0
- package/cli/lifecycle.js +0 -0
- package/cli/linktrail.mjs +0 -0
- package/cli/local-result.js +41 -6
- package/cli/main.js +85 -20
- package/cli/mcp-client.js +2 -2
- package/cli/migration.js +308 -0
- package/cli/mirror.js +0 -0
- package/cli/mix.js +0 -0
- package/cli/native-lock.js +0 -0
- package/cli/oauth-credentials.js +0 -0
- package/cli/oauth-flow.js +0 -0
- package/cli/placement-projection.js +106 -0
- package/cli/platforms.js +0 -0
- package/cli/prompt.js +25 -0
- package/cli/public-http.js +168 -0
- package/cli/receipt-performance.js +0 -0
- package/cli/receipts.js +0 -0
- package/cli/report.js +51 -19
- package/cli/setup-plan.js +0 -0
- package/cli/skill.js +0 -0
- package/cli/state.js +76 -29
- package/cli/status.js +14 -18
- package/cli/sync-lock.js +0 -0
- package/cli/sync.js +23 -4
- package/package.json +7 -8
- package/references/citation-panels.md +2 -0
- package/references/cli.md +2 -2
- package/references/disavow.md +1 -1
- package/references/discovery-handoff.md +1 -1
- package/references/local-crm-draft-handoff.md +1 -1
- package/references/portable-ledger-handoff.md +11 -10
- package/references/recipes/catalog.json +263 -7
- package/references/recipes/command-notes.md +5 -3
- package/references/recipes/internal-link-improvement.md +25 -0
- package/references/recipes/placement-reconciliation.md +53 -1
- package/references/recipes/qualified-campaign-handoff.md +0 -0
- package/references/recipes/site-context-brief.md +0 -0
- package/references/recipes/sourced-linkable-asset.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/outreach-crm-draft.py +0 -0
- package/scripts/portable-handoff.py +17 -4
- package/scripts/report.py +0 -0
- package/shared/admission-contract.js +3 -2
- package/shared/admission-report-contract.js +3 -2
- package/shared/agent-clients.js +0 -0
- package/shared/analytics-history-contract.js +15 -0
- package/shared/campaign-outcome-contract.js +12 -0
- package/shared/campaign-prospect-contract.js +23 -0
- package/shared/citation-evidence-contract.js +0 -0
- package/shared/classification-contract.js +54 -0
- package/shared/contact-record-contract.js +27 -0
- package/shared/disavow-contract.js +37 -9
- package/shared/exec-grammar.js +0 -0
- package/shared/index-observation-contract.js +5 -4
- package/shared/internal-links-contract.js +143 -0
- package/shared/lifecycle-contract.js +0 -0
- package/shared/lifecycle-money.js +0 -0
- package/shared/link-category-contract.js +35 -0
- package/shared/link-category.js +138 -0
- package/shared/link-labels.js +26 -0
- package/shared/link-summary-contract.js +17 -0
- package/shared/monitoring-chronology-export.js +57 -0
- package/shared/monitoring-read-contract.js +41 -0
- package/shared/observation-event.js +1 -1
- package/shared/onboarding.js +20 -0
- package/shared/opportunity-record.js +0 -0
- package/shared/response-contract.js +41 -5
- package/shared/search-connection-contract.js +2 -1
- package/shared/table-page-contract.js +13 -0
- package/skills/agentlinkops-assets/SKILL-DEPS.json +290 -0
- package/skills/agentlinkops-assets/SKILL.md +3 -1
- package/skills/agentlinkops-assets/agents/openai.yaml +1 -1
- package/skills/agentlinkops-assets/references/portable/references/cli.md +220 -0
- package/skills/agentlinkops-assets/references/portable/references/disavow.md +73 -0
- package/skills/agentlinkops-assets/references/portable/references/reports.md +53 -0
- package/skills/agentlinkops-assets/references/portable/scripts/agentlinkops.py +676 -0
- package/skills/agentlinkops-assets/references/portable/scripts/disavow.py +207 -0
- package/skills/agentlinkops-assets/references/portable/scripts/migrations/001_crm.sql +45 -0
- package/skills/agentlinkops-assets/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
- package/skills/agentlinkops-assets/references/portable/scripts/migrations/003_disavow.sql +20 -0
- package/skills/agentlinkops-assets/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
- package/skills/agentlinkops-assets/references/portable/scripts/report.py +113 -0
- package/skills/agentlinkops-assets/references/portable/templates/campaigns.v1.json +65 -0
- package/skills/agentlinkops-campaigns/SKILL-DEPS.json +336 -0
- package/skills/agentlinkops-campaigns/SKILL.md +7 -5
- package/skills/agentlinkops-campaigns/agents/openai.yaml +1 -1
- package/skills/agentlinkops-campaigns/references/first-campaign.md +31 -0
- package/skills/agentlinkops-campaigns/references/mailbox-campaign.md +46 -0
- package/skills/agentlinkops-campaigns/references/portable/references/cli.md +220 -0
- package/skills/agentlinkops-campaigns/references/portable/references/disavow.md +73 -0
- package/skills/agentlinkops-campaigns/references/portable/references/reports.md +53 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/agentlinkops.py +676 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/disavow.py +207 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/001_crm.sql +45 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/003_disavow.sql +20 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
- package/skills/agentlinkops-campaigns/references/portable/scripts/report.py +113 -0
- package/skills/agentlinkops-campaigns/references/portable/templates/campaigns.v1.json +65 -0
- package/skills/agentlinkops-connect/SKILL-DEPS.json +104 -0
- package/skills/agentlinkops-connect/SKILL.md +2 -0
- package/skills/agentlinkops-connect/agents/openai.yaml +1 -1
- package/skills/agentlinkops-connect/references/agentlinkops.md +164 -61
- package/skills/agentlinkops-connect/references/placement-workflows.md +19 -0
- package/skills/agentlinkops-crm/SKILL-DEPS.json +381 -0
- package/skills/agentlinkops-crm/SKILL.md +26 -9
- package/skills/agentlinkops-crm/agents/openai.yaml +1 -1
- package/skills/agentlinkops-crm/references/portable/references/cli.md +220 -0
- package/skills/agentlinkops-crm/references/portable/references/disavow.md +73 -0
- package/skills/agentlinkops-crm/references/portable/references/local-crm-draft-handoff.md +76 -0
- package/skills/agentlinkops-crm/references/portable/references/portable-ledger-handoff.md +51 -0
- package/skills/agentlinkops-crm/references/portable/references/reports.md +53 -0
- package/skills/agentlinkops-crm/references/portable/scripts/agentlinkops.py +676 -0
- package/skills/agentlinkops-crm/references/portable/scripts/disavow.py +207 -0
- package/skills/agentlinkops-crm/references/portable/scripts/migrations/001_crm.sql +45 -0
- package/skills/agentlinkops-crm/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
- package/skills/agentlinkops-crm/references/portable/scripts/migrations/003_disavow.sql +20 -0
- package/skills/agentlinkops-crm/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
- package/skills/agentlinkops-crm/references/portable/scripts/outreach-crm-draft.py +121 -0
- package/skills/agentlinkops-crm/references/portable/scripts/portable-handoff.py +197 -0
- package/skills/agentlinkops-crm/references/portable/scripts/report.py +113 -0
- package/skills/agentlinkops-crm/references/portable/templates/campaigns.v1.json +65 -0
- package/skills/agentlinkops-discovery/SKILL-DEPS.json +301 -0
- package/skills/agentlinkops-discovery/SKILL.md +4 -2
- package/skills/agentlinkops-discovery/references/portable/references/cli.md +220 -0
- package/skills/agentlinkops-discovery/references/portable/references/disavow.md +73 -0
- package/skills/agentlinkops-discovery/references/portable/references/discovery-handoff.md +32 -0
- package/skills/agentlinkops-discovery/references/portable/references/reports.md +53 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/agentlinkops.py +676 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/disavow.py +207 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/migrations/001_crm.sql +45 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/migrations/003_disavow.sql +20 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
- package/skills/agentlinkops-discovery/references/portable/scripts/report.py +113 -0
- package/skills/agentlinkops-discovery/references/portable/templates/campaigns.v1.json +65 -0
- package/skills/agentlinkops-discovery/references/saved-backlink-workflows.md +23 -0
- package/src/citations/adapter.js +0 -0
- package/src/citations/alert.js +0 -0
- package/src/citations/browser/chatgpt-citations.js +0 -0
- package/src/citations/browser/engine.js +0 -0
- package/src/citations/browser/gen/core/providers/_shared/rawSourcesDom.js +0 -0
- package/src/citations/browser/gen/core/providers/_shared/resetProviderPage.js +0 -0
- package/src/citations/browser/gen/core/providers/_shared/sourceUtils.js +0 -0
- package/src/citations/browser/gen/core/providers/ai-overview/index.js +0 -0
- package/src/citations/browser/gen/core/providers/ai-overview/lib/expand.js +0 -0
- package/src/citations/browser/gen/core/providers/ai-overview/lib/extractSources.js +0 -0
- package/src/citations/browser/gen/core/providers/ai-overview/lib/pageLifecycle.js +0 -0
- package/src/citations/browser/gen/core/providers/ai-overview/lib/session.js +0 -0
- package/src/citations/browser/gen/core/providers/chatgpt/index.js +0 -0
- package/src/citations/browser/gen/core/providers/chatgpt/lib/dismissAuthModal.js +0 -0
- package/src/citations/browser/gen/core/providers/chatgpt/lib/extractSources.js +0 -0
- package/src/citations/browser/gen/core/providers/chatgpt/lib/pageLifecycle.js +0 -0
- package/src/citations/browser/gen/core/providers/claude/index.js +0 -0
- package/src/citations/browser/gen/core/providers/claude/lib/extractSources.js +0 -0
- package/src/citations/browser/gen/core/providers/claude/lib/pageLifecycle.js +0 -0
- package/src/citations/browser/gen/core/providers/gemini/index.js +0 -0
- package/src/citations/browser/gen/core/providers/gemini/lib/extractSources.js +0 -0
- package/src/citations/browser/gen/core/providers/gemini/lib/pageLifecycle.js +0 -0
- package/src/citations/browser/gen/core/providers/gemini/lib/session.js +0 -0
- package/src/citations/browser/gen/core/providers/index.js +0 -0
- package/src/citations/browser/gen/core/providers/perplexity/index.js +0 -0
- package/src/citations/browser/gen/core/providers/perplexity/lib/extractSources.js +0 -0
- package/src/citations/browser/gen/core/providers/perplexity/lib/navigation.js +0 -0
- package/src/citations/browser/gen/lib/browser/camoufox.js +0 -0
- package/src/citations/browser/gen/lib/browser/display.js +0 -0
- package/src/citations/browser/gen/lib/browser/domOps.js +0 -0
- package/src/citations/browser/gen/lib/browser/humanBehavior.js +0 -0
- package/src/citations/browser/gen/lib/browser/navigate.js +0 -0
- package/src/citations/browser/gen/lib/extraction/sourceUtils.js +0 -0
- package/src/citations/browser/gen/lib/input/editor/clearInput.js +0 -0
- package/src/citations/browser/gen/lib/input/editor/findEditor.js +0 -0
- package/src/citations/browser/gen/lib/input/editor/promptInput.js +0 -0
- package/src/citations/browser/gen/lib/input/markdown/converter.js +0 -0
- package/src/citations/browser/gen/lib/input/markdown/toMarkdown.js +0 -0
- package/src/citations/browser/gen/lib/input/response/detectBotPage.js +0 -0
- package/src/citations/browser/gen/lib/input/response/isGenerating.js +0 -0
- package/src/citations/browser/gen/lib/input/response/waitForFinish.js +0 -0
- package/src/citations/browser/gen/lib/input/sources/findButton.js +0 -0
- package/src/citations/browser/gen/lib/input/sources/openPanel.js +0 -0
- package/src/citations/browser/gen/utils/agent-constants.js +0 -0
- package/src/citations/browser/providers.js +0 -0
- package/src/citations/browser/proxy-admission.js +0 -0
- package/src/citations/browser/shims/errors.js +0 -0
- package/src/citations/browser/shims/types.js +0 -0
- package/src/citations/browser/shims/utils.js +0 -0
- package/src/citations/contract.js +0 -0
- package/src/citations/egress-receipts.js +0 -0
- package/src/citations/evidence-integrity.js +0 -0
- package/src/citations/inventory.js +0 -0
- package/src/citations/login.js +0 -0
- package/src/citations/match.js +0 -0
- package/src/citations/panel-builder.js +0 -0
- package/src/citations/report.js +0 -0
- package/src/citations/runner.js +0 -0
- package/src/citations/stats.js +0 -0
- package/src/citations/sync.js +0 -0
- package/src/citations/watch.js +0 -0
- package/src/competitors/contract.js +17 -1
- 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 +34 -121
- package/src/context/util.js +0 -0
- package/src/context-tool-registry.js +0 -0
- package/src/corpus/acquire.js +68 -32
- package/src/corpus/extract.js +147 -0
- package/src/corpus/frontier.js +151 -0
- package/src/corpus/seeds.js +0 -0
- package/src/discovery/contract.js +8 -0
- package/src/discovery/import.js +13 -1
- package/src/evidence-reference.js +0 -0
- package/src/index-observation.js +0 -0
- package/src/locator.js +0 -0
- package/src/mentions.js +193 -0
- package/src/page-acquisition/acquire.js +50 -0
- package/src/page-acquisition/bytes.js +138 -0
- package/src/page-acquisition/capture.js +11 -0
- package/src/page-acquisition/contracts.js +67 -0
- package/src/page-acquisition/documents.js +166 -0
- package/src/page-acquisition/http.js +307 -0
- package/src/page-acquisition/policy.js +20 -0
- package/src/page-acquisition/recovery.js +164 -0
- package/src/page-acquisition/render-failure.js +82 -0
- package/src/page-extraction/html.js +359 -0
- package/src/page-extraction/index.js +30 -0
- package/src/page-extraction/metadata.js +16 -0
- package/src/response-shaping.js +4 -3
- package/src/tool-views.js +23 -12
- package/src/verifier/fetch.js +4 -339
- package/src/verifier/index.js +81 -23
- package/src/verifier/observation-kind.js +0 -0
- package/src/verifier/page-quality.js +14 -0
- package/src/verifier/parser.js +2 -241
- package/src/verifier/readiness.js +64 -13
- package/src/verifier/render-evidence.js +25 -2
- package/src/verifier/robots.js +0 -0
- package/src/verifier/target.js +201 -0
- package/src/verifier/transition.js +18 -3
- package/src/verifier/url.js +31 -0
- package/src/watch-crawlability.js +144 -0
- package/templates/SITE.template.md +0 -0
- package/templates/campaigns.v1.json +0 -0
- package/skills/content-defingerprinting/SKILL.md +0 -29
- package/skills/content-defingerprinting/scripts/watermarks/README.md +0 -102
- package/skills/content-defingerprinting/scripts/watermarks/UPSTREAM.json +0 -26
- package/skills/content-defingerprinting/scripts/watermarks/revendor.sh +0 -52
- package/skills/content-defingerprinting/scripts/watermarks/vendor/LICENSE.upstream +0 -21
- package/skills/content-defingerprinting/scripts/watermarks/vendor/clean_text.py +0 -96
- package/skills/content-defingerprinting/scripts/watermarks/vendor/common.py +0 -432
- package/skills/content-defingerprinting/scripts/watermarks/vendor/inspect_text.py +0 -86
- package/skills/content-defingerprinting/scripts/watermarks/vendor/score_stylometry.py +0 -460
- package/skills/content-defingerprinting/scripts/watermarks/vendor/text_unicode.py +0 -651
- package/skills/content-defingerprinting/scripts/watermarks/verify-vendor.py +0 -27
- package/skills/content-defingerprinting/scripts/watermarks/wm-scan.py +0 -212
- package/skills/internal-linking-optimizer/SKILL.md +0 -56
- package/skills/internal-linking-optimizer/references/source-prompt.md +0 -76
- package/skills/pre-publish-review/SKILL.md +0 -179
- package/skills/source-cited-content-builder/SKILL.md +0 -56
- package/skills/source-cited-content-builder/references/source-prompt.md +0 -19
- package/skills/source-cited-humanizer/SKILL.md +0 -20
package/cli/fleet.js
CHANGED
|
@@ -6,54 +6,62 @@
|
|
|
6
6
|
// and a summary that deduplicated them would silently pick one. So every number below is
|
|
7
7
|
// per project, the only cross-project arithmetic is a sum of counts, and the output says so.
|
|
8
8
|
//
|
|
9
|
-
// "Earned" follows
|
|
10
|
-
//
|
|
11
|
-
// is reported as never checked rather than as zero links.
|
|
9
|
+
// "Earned" follows known conclusive presence; a later inconclusive attempt is disclosed
|
|
10
|
+
// separately. Activity is supplied explicitly per project, never discovered in another repo.
|
|
12
11
|
import { readLedger } from './ledger.js';
|
|
13
|
-
import { readObservations
|
|
12
|
+
import { readObservations } from './mirror.js';
|
|
13
|
+
import { readState } from './state.js';
|
|
14
|
+
import { projectPlacements } from './placement-projection.js';
|
|
14
15
|
|
|
15
16
|
const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
16
17
|
|
|
17
18
|
/** One project's row. Everything is computed inside the project; nothing crosses the boundary. */
|
|
18
|
-
export async function fleetProject(name, ledgerPath, observationsPath) {
|
|
19
|
+
export async function fleetProject(name, ledgerPath, observationsPath, statePath = null) {
|
|
19
20
|
const ledger = await readLedger(ledgerPath);
|
|
20
21
|
if (ledger.missing) { const error = new Error(`no ledger at ${ledgerPath}`); error.code = 'NO_LEDGER'; throw error; }
|
|
21
22
|
const byIntent = { wanted: 0, expected: 0, retired: 0 };
|
|
22
23
|
for (const entry of ledger.entries) byIntent[entry.intent] = (byIntent[entry.intent] ?? 0) + 1;
|
|
23
24
|
|
|
24
25
|
const { rows } = observationsPath ? await readObservations(observationsPath) : { rows: [] };
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
const lastCheck = rows.length ? rows.map(row => row.checked_at).sort().at(-1) : null;
|
|
26
|
+
const state = statePath ? await readState(statePath) : null;
|
|
27
|
+
const eligible = ledger.entries.filter(entry => entry.intent !== 'retired');
|
|
28
|
+
const projected = projectPlacements(eligible, rows, { state });
|
|
29
|
+
const observed = projected.filter(item => item.latest_attempt).length;
|
|
30
|
+
const earned = projected.filter(item => item.current_state === 'present').length;
|
|
31
|
+
const newest = values => values.filter(Boolean).sort((a, b) => Date.parse(a) - Date.parse(b)).at(-1) ?? null;
|
|
33
32
|
return {
|
|
34
33
|
project: name, ledger: ledgerPath,
|
|
35
34
|
entries: ledger.entries.length, ...byIntent,
|
|
36
35
|
problems: ledger.problems.length,
|
|
37
36
|
observed_entries: observed, earned,
|
|
38
|
-
never_checked:
|
|
39
|
-
last_observation:
|
|
37
|
+
never_checked: eligible.length - observed,
|
|
38
|
+
last_observation: newest(projected.map(item => item.evidence_observed_at)),
|
|
39
|
+
last_attempt_at: newest(projected.map(item => item.latest_attempt?.checked_at)),
|
|
40
|
+
last_success_at: newest(projected.map(item => item.last_successful_observation?.checked_at)),
|
|
41
|
+
last_link_verification_at: newest(projected.map(item => item.last_link_verification?.checked_at)),
|
|
42
|
+
uncertain_entries: projected.filter(item => item.uncertain).length,
|
|
43
|
+
activity_available: projected.some(item => item.activity_available),
|
|
44
|
+
activity_entries: projected.filter(item => item.activity_available).length,
|
|
45
|
+
coverage: { denominator: 'non_retired_ledger_entries', eligible_entries: eligible.length,
|
|
46
|
+
identity_unmatched_entries: projected.filter(item => item.identity_status === 'not_matched').length,
|
|
47
|
+
activity_input: statePath ? 'explicit_state_file' : 'not_supplied' },
|
|
40
48
|
// Due volume, the same arithmetic the dogfood records use to price a fleet.
|
|
41
49
|
implied_weekly_checks: byIntent.wanted + byIntent.expected * 7,
|
|
42
50
|
};
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
/** Summons the fleet. Same numbers per project, one sum of counts, no merged intent. */
|
|
46
|
-
export async function fleetSummary(projects, { observationsOf = {} } = {}) {
|
|
54
|
+
export async function fleetSummary(projects, { observationsOf = {}, statesOf = {} } = {}) {
|
|
47
55
|
const rows = [];
|
|
48
|
-
for (const { name, ledger } of projects) rows.push(await fleetProject(name, ledger, observationsOf[name] ?? null));
|
|
56
|
+
for (const { name, ledger } of projects) rows.push(await fleetProject(name, ledger, observationsOf[name] ?? null, statesOf[name] ?? null));
|
|
49
57
|
const totals = rows.reduce((acc, row) => {
|
|
50
|
-
for (const key of ['entries', 'wanted', 'expected', 'retired', 'earned', 'observed_entries', 'never_checked', 'implied_weekly_checks']) {
|
|
58
|
+
for (const key of ['entries', 'wanted', 'expected', 'retired', 'earned', 'observed_entries', 'never_checked', 'uncertain_entries', 'activity_entries', 'implied_weekly_checks']) {
|
|
51
59
|
acc[key] = (acc[key] ?? 0) + row[key];
|
|
52
60
|
}
|
|
53
61
|
return acc;
|
|
54
62
|
}, {});
|
|
55
63
|
return {
|
|
56
|
-
v:
|
|
64
|
+
v: 2, projects: rows, totals,
|
|
57
65
|
// The sum answers "how big is the operation". It is not a merged view: the same platform
|
|
58
66
|
// counted in two projects is two intents, and only a human can say which one they meant.
|
|
59
67
|
intent_merged_across_repos: false,
|
|
@@ -75,6 +83,9 @@ export function renderFleet(summary, out = console.log) {
|
|
|
75
83
|
+ `${String(t.never_checked).padStart(13)} (sum of counts)`);
|
|
76
84
|
out('');
|
|
77
85
|
out(`implied weekly check volume across the fleet: ${t.implied_weekly_checks}`);
|
|
86
|
+
for (const row of summary.projects) {
|
|
87
|
+
out(`${row.project}: latest attempt ${row.last_attempt_at ?? 'not recorded'}; last conclusive check ${row.last_success_at ?? 'not recorded'}; last link verification ${row.last_link_verification_at ?? 'not recorded'}; activity ${row.activity_available ? `${row.activity_entries} entr(ies)` : 'not available'}; uncertain ${row.uncertain_entries}`);
|
|
88
|
+
}
|
|
78
89
|
if (summary.projects.some(row => row.problems)) {
|
|
79
90
|
out(`ledger problems: ${summary.projects.map(row => `${row.project}=${row.problems}`).filter(s => !s.endsWith('=0')).join(', ')}`);
|
|
80
91
|
}
|
package/cli/gsc-browser.js
CHANGED
|
File without changes
|
package/cli/import.js
CHANGED
|
@@ -54,6 +54,7 @@ export async function readImport(path, {
|
|
|
54
54
|
|
|
55
55
|
const resolved = resolveMapping(table.header, { supplier, overrides: map, targetProvided: Boolean(target) });
|
|
56
56
|
if (resolved.error) return { error: resolved.error, header: table.header };
|
|
57
|
+
const targetExpectation = supplier === 'google_search_console' && !resolved.mapping.target && Boolean(target);
|
|
57
58
|
|
|
58
59
|
const unreadable = table.records.filter(record => record.error).map(record => ({ row: record.line, reason: record.error }));
|
|
59
60
|
const readable = table.records.filter(record => record.values);
|
|
@@ -63,7 +64,12 @@ export async function readImport(path, {
|
|
|
63
64
|
// means. Nothing is inferred — without --target the resolver above refuses the file.
|
|
64
65
|
if (!resolved.mapping.target) {
|
|
65
66
|
const targetUrl = targetKind === 'domain' ? `https://${target}/` : target;
|
|
66
|
-
for (const row of rows)
|
|
67
|
+
for (const row of rows) {
|
|
68
|
+
row.target_url = targetUrl;
|
|
69
|
+
// This is the caller's target expectation, not a destination observed by Google.
|
|
70
|
+
// The verifier can use the saved run scope without asserting an exact root-page link.
|
|
71
|
+
if (targetExpectation) row.target_basis = 'user_supplied_target_expectation';
|
|
72
|
+
}
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
const targetUrl = targetKind === 'domain' ? `https://${target}/` : target;
|
|
File without changes
|
package/cli/index-submissions.js
CHANGED
|
File without changes
|
package/cli/key-store.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Where `agentlinkops connect` keeps the API key a person pastes, so cloud commands work in any
|
|
2
|
+
// folder without environment variables. Never inside a repository:
|
|
3
|
+
//
|
|
4
|
+
// macOS the login Keychain, through /usr/bin/security. `security -i` takes the key on stdin,
|
|
5
|
+
// so it never appears in a process listing.
|
|
6
|
+
// Linux ~/.agentlinkops/keys/, one file per cloud origin, mode 600 in a mode 700 directory
|
|
7
|
+
// owned by the person; anything looser is ignored rather than trusted.
|
|
8
|
+
// other nothing is saved; AGENTLINKOPS_API_KEY keeps working everywhere.
|
|
9
|
+
//
|
|
10
|
+
// One key per cloud origin. Reads are synchronous because cloudConnection() is.
|
|
11
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
12
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
13
|
+
import { lstatSync, readFileSync } from 'node:fs';
|
|
14
|
+
import * as fs from 'node:fs/promises';
|
|
15
|
+
import { homedir } from 'node:os';
|
|
16
|
+
import { isAbsolute, join } from 'node:path';
|
|
17
|
+
import { ConfigError } from './config.js';
|
|
18
|
+
|
|
19
|
+
export const API_KEY_PATTERN = /^lt_[0-9a-f]{64}$/;
|
|
20
|
+
// main() attaches the reader to the environment object it hands every command, under this
|
|
21
|
+
// symbol: it never reaches a child process, a JSON dump or a test that passes its own env.
|
|
22
|
+
export const STORED_KEY = Symbol.for('agentlinkops.storedKey');
|
|
23
|
+
|
|
24
|
+
const SECURITY = '/usr/bin/security';
|
|
25
|
+
const SERVICE = 'agentlinkops-cli';
|
|
26
|
+
// An origin is a Keychain account name and a file-name input: canonical https only, no spaces
|
|
27
|
+
// or quotes, so it can sit unquoted in a `security -i` command line.
|
|
28
|
+
const canonical = origin => {
|
|
29
|
+
try { const url = new URL(origin); return url.protocol === 'https:' && url.origin === origin && !/[\s"'\\]/.test(origin); }
|
|
30
|
+
catch { return false; }
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const runSync = (file, args) => execFileSync(file, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 10000 });
|
|
34
|
+
const run = (file, args, input) => new Promise((resolve, reject) => {
|
|
35
|
+
const child = execFile(file, args, { encoding: 'utf8', timeout: 10000 }, (error, stdout) => error ? reject(error) : resolve(stdout));
|
|
36
|
+
child.stdin.end(input);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
function keychainStore({ exec = runSync, execInput = run }) {
|
|
40
|
+
const store = {
|
|
41
|
+
where: 'your macOS Keychain',
|
|
42
|
+
readSync(origin) {
|
|
43
|
+
if (!canonical(origin)) return null;
|
|
44
|
+
try {
|
|
45
|
+
const key = exec(SECURITY, ['find-generic-password', '-s', SERVICE, '-a', origin, '-w']).trim();
|
|
46
|
+
return API_KEY_PATTERN.test(key) ? key : null;
|
|
47
|
+
} catch { return null; } // 44: no such item. Anything else reads as no saved key.
|
|
48
|
+
},
|
|
49
|
+
async save(origin, key) {
|
|
50
|
+
if (!canonical(origin) || !API_KEY_PATTERN.test(key)) throw new ConfigError('Only a complete API key for an https origin can be saved.');
|
|
51
|
+
await execInput(SECURITY, ['-i'], `add-generic-password -U -s ${SERVICE} -a ${origin} -w ${key}\n`).catch(() => {});
|
|
52
|
+
// `security -i` reports per-command failures on its own output, so read the key back.
|
|
53
|
+
if (store.readSync(origin) !== key) throw new ConfigError('The key could not be saved in your macOS Keychain. Set AGENTLINKOPS_API_KEY instead.');
|
|
54
|
+
},
|
|
55
|
+
async remove(origin) {
|
|
56
|
+
if (!canonical(origin)) return false;
|
|
57
|
+
try { exec(SECURITY, ['delete-generic-password', '-s', SERVICE, '-a', origin]); return true; } catch { return false; }
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
return store;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function fileStore({ home, uid = process.getuid?.() }) {
|
|
64
|
+
const dir = join(home, '.agentlinkops', 'keys');
|
|
65
|
+
const fileFor = origin => join(dir, `${createHash('sha256').update(origin).digest('hex').slice(0, 32)}.json`);
|
|
66
|
+
const own = info => uid !== undefined && info.uid === uid;
|
|
67
|
+
const privateDir = info => info.isDirectory() && own(info) && (info.mode & 0o777) === 0o700;
|
|
68
|
+
const privateFile = info => info.isFile() && info.nlink === 1 && own(info) && (info.mode & 0o777) === 0o600;
|
|
69
|
+
return {
|
|
70
|
+
where: '~/.agentlinkops/keys (readable only by you)',
|
|
71
|
+
readSync(origin) {
|
|
72
|
+
if (!canonical(origin)) return null;
|
|
73
|
+
try {
|
|
74
|
+
if (!privateDir(lstatSync(dir)) || !privateFile(lstatSync(fileFor(origin)))) return null;
|
|
75
|
+
const record = JSON.parse(readFileSync(fileFor(origin), 'utf8'));
|
|
76
|
+
return record?.origin === origin && API_KEY_PATTERN.test(record.key) ? record.key : null;
|
|
77
|
+
} catch { return null; }
|
|
78
|
+
},
|
|
79
|
+
async save(origin, key) {
|
|
80
|
+
if (!canonical(origin) || !API_KEY_PATTERN.test(key)) throw new ConfigError('Only a complete API key for an https origin can be saved.');
|
|
81
|
+
for (const path of [join(home, '.agentlinkops'), dir]) {
|
|
82
|
+
try { await fs.mkdir(path, { mode: 0o700 }); } catch (error) { if (error.code !== 'EEXIST') throw error; }
|
|
83
|
+
}
|
|
84
|
+
if (!privateDir(await fs.lstat(dir))) throw new ConfigError(`${dir} must be a directory only you can open (mode 700). Fix it, or set AGENTLINKOPS_API_KEY instead.`);
|
|
85
|
+
const path = fileFor(origin), temporary = `${path}.${randomBytes(8).toString('hex')}.tmp`;
|
|
86
|
+
try {
|
|
87
|
+
await fs.writeFile(temporary, `${JSON.stringify({ origin, key })}\n`, { flag: 'wx', mode: 0o600 });
|
|
88
|
+
await fs.rename(temporary, path);
|
|
89
|
+
} finally { await fs.unlink(temporary).catch(() => {}); }
|
|
90
|
+
},
|
|
91
|
+
async remove(origin) {
|
|
92
|
+
if (!canonical(origin)) return false;
|
|
93
|
+
try { await fs.unlink(fileFor(origin)); return true; } catch (error) { if (error.code === 'ENOENT') return false; throw error; }
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const unsupported = {
|
|
99
|
+
where: null,
|
|
100
|
+
readSync: () => null,
|
|
101
|
+
async save() { throw new ConfigError('This platform has no supported place to save a key yet. Set AGENTLINKOPS_API_KEY instead.'); },
|
|
102
|
+
async remove() { return false; },
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export function createKeyStore({ platform = process.platform, home = homedir(), exec, execInput, uid } = {}) {
|
|
106
|
+
if (platform === 'darwin') return keychainStore({ exec, execInput });
|
|
107
|
+
if (platform === 'linux' && typeof home === 'string' && isAbsolute(home)) return fileStore({ home, uid });
|
|
108
|
+
return unsupported;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The environment a command sees, plus a reader for keys saved by `agentlinkops connect`. */
|
|
112
|
+
export function withStoredKeys(env, store) {
|
|
113
|
+
if (!store) return env;
|
|
114
|
+
return { ...env, [STORED_KEY]: origin => { const key = store.readSync(origin); return key ? { key, where: store.where } : null; } };
|
|
115
|
+
}
|
package/cli/ledger.js
CHANGED
|
@@ -214,6 +214,7 @@ export function toWatchInput(entry) {
|
|
|
214
214
|
// caller can say so rather than discovering it when a reference comes back short.
|
|
215
215
|
const reference = entry.ref ? `${entry.id} ${entry.ref}` : entry.id;
|
|
216
216
|
return {
|
|
217
|
+
intent: ({wanted:'prospect',expected:'earned',retired:'retired'})[entry.intent],
|
|
217
218
|
sourceUrl: entry.source, targetUrl: entry.target, targetScope: entry.scope,
|
|
218
219
|
cadenceSeconds: cadenceSeconds(entry.cadence ?? DEFAULT_CADENCE[entry.intent] ?? 'daily'),
|
|
219
220
|
expectedAnchor: entry.expect?.anchor ?? null,
|
package/cli/lifecycle.js
CHANGED
|
File without changes
|
package/cli/linktrail.mjs
CHANGED
|
File without changes
|
package/cli/local-result.js
CHANGED
|
@@ -15,11 +15,26 @@ export const LOCAL_RESULT_SCHEMA = 'agentlinkops.local-check.v1';
|
|
|
15
15
|
|
|
16
16
|
const timestamp = z.string().refine(value => Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value);
|
|
17
17
|
const strings = z.array(z.string());
|
|
18
|
+
const digest = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
19
|
+
const capturePin = z.string().min(1).max(128).regex(/^[^\x00-\x20\x7f]+$/u);
|
|
20
|
+
const bodyDiagnosticSchema = z.strictObject({ version: z.literal(1), bytes: z.number().int().min(0).max(64 * 1024),
|
|
21
|
+
complete: z.boolean(), truncated: z.boolean(), sha256: digest, looksLikeHtml: z.boolean() });
|
|
22
|
+
const googlebotSchema = z.strictObject({ productToken: z.literal('Googlebot'), allowed: z.boolean().nullable(),
|
|
23
|
+
reason: z.string().nullable(), matchedRule: z.string().nullable(), crawlDelaySeconds: z.number().finite().min(0).nullable() });
|
|
24
|
+
const captureSchema = z.strictObject({ version: z.literal(1), representation: z.literal('raw_html'),
|
|
25
|
+
requestedUrl: z.string(), finalUrl: z.string(), httpStatus: z.number().int().min(100).max(599), acquiredAt: timestamp,
|
|
26
|
+
body: z.strictObject({ complete: z.literal(true), truncated: z.literal(false), bytes: z.number().int().min(0).max(2 * 1024 * 1024),
|
|
27
|
+
sha256: digest, decodedSha256: digest, charset: z.string().max(64).nullable().optional() }),
|
|
28
|
+
transport: z.strictObject({ id: capturePin, version: capturePin, qualified: z.literal(true) }),
|
|
29
|
+
coverage: z.strictObject({ mainDocument: z.literal(true), iframes: z.literal(false), shadowRoots: z.literal(false) }) });
|
|
18
30
|
const redirectSchema = z.strictObject({ from: z.string(), to: z.string(), status: z.number().int() });
|
|
19
31
|
const robotsSchema = z.strictObject({ allowed: z.boolean().nullable(), reason: z.string().nullable(),
|
|
20
32
|
matchedRule: z.string().nullable(), crawlDelaySeconds: z.number().nullable(), fetched: z.string().nullable(),
|
|
21
33
|
sourceUrl: z.string().optional(), robotsUrl: z.string().optional(), httpStatus: z.number().int().nullable().optional(),
|
|
22
|
-
productToken: z.string().optional(), redirects: z.array(redirectSchema).max(5).optional(), retryAfterSeconds: z.number().optional()
|
|
34
|
+
productToken: z.string().optional(), redirects: z.array(redirectSchema).max(5).optional(), retryAfterSeconds: z.number().finite().min(0).max(86400).optional(),
|
|
35
|
+
googlebot: googlebotSchema.optional(), decisionReason: z.string().nullable().optional(),
|
|
36
|
+
policy: z.literal('customer_supplied_monitor').optional(), fetchAuthorized: z.boolean().optional(), overrideUsed: z.literal(false).optional(),
|
|
37
|
+
bodyDiagnostic: bodyDiagnosticSchema.optional() });
|
|
23
38
|
const readinessSchema = z.strictObject({ requiresRender: z.boolean(), reason: z.string().nullable(), possibleLoginWall: z.boolean(),
|
|
24
39
|
hasVisiblePassword: z.boolean().optional(), targetInScript: z.boolean().optional(), emptyAppRoot: z.boolean().optional(), emptyProfileRoot: z.boolean().optional() });
|
|
25
40
|
const occurrenceSchema = z.strictObject({ href: z.string(), targetUrl: z.string(), anchor: z.string(), rel: strings,
|
|
@@ -28,15 +43,18 @@ const observationSchema = z.strictObject({
|
|
|
28
43
|
state: z.enum(['present','absent','unknown','source_unavailable']), reason: z.string(), sourceUrl: z.string(), finalUrl: z.string(), targetUrl: z.string(),
|
|
29
44
|
targetScope: z.enum(['exact','domain','subdomain','path']), httpStatus: z.number().int().nullable(), checkedAt: timestamp,
|
|
30
45
|
occurrences: z.array(occurrenceSchema).max(1000),
|
|
31
|
-
directives: z.strictObject({ meta: z.array(z.strictObject({ name: z.string(), content: z.string() })), xRobotsTag: strings, noindex: z.boolean(), nofollow: z.boolean(), indexingStatus: z.string()
|
|
46
|
+
directives: z.strictObject({ meta: z.array(z.strictObject({ name: z.string(), content: z.string() })), xRobotsTag: strings, noindex: z.boolean(), nofollow: z.boolean(), indexingStatus: z.string(),
|
|
47
|
+
googlebot: z.strictObject({ noindex: z.boolean(), nofollow: z.boolean() }).optional() }),
|
|
32
48
|
redirects: z.array(redirectSchema),
|
|
33
49
|
evidence: z.strictObject({ method: z.string(), checkerVersion: z.string(), complete: z.boolean(), fetchedAt: timestamp.nullable(),
|
|
34
50
|
bytes: z.number().optional(), contentType: z.string().nullable().optional(), etag: z.string().nullable().optional(), lastModified: z.string().nullable().optional(),
|
|
35
|
-
baseUrl: z.string().optional(), rendered: z.
|
|
36
|
-
|
|
51
|
+
baseUrl: z.string().optional(), rendered: z.literal(false).optional(), sha256: digest.optional(), decodedSha256: digest.optional(),
|
|
52
|
+
capture: captureSchema.nullable().optional(), parseErrors: strings.optional(),
|
|
53
|
+
readiness: readinessSchema.optional(), renderEligibility: z.enum(['required', 'comparison']).optional(),
|
|
54
|
+
renderAttempt: z.strictObject({ attempted: z.literal(false), reason: z.literal('render_not_configured') }).optional() }),
|
|
37
55
|
robots: robotsSchema.optional(), robotsHistory: z.array(robotsSchema).max(6).optional(),
|
|
38
56
|
sourceResponse: z.strictObject({ url: z.string(), httpStatus: z.number().int(), contentType: z.string().max(128).nullable(),
|
|
39
|
-
retryAfterSeconds: z.number().nullable(), challenge: z.boolean() }).optional(),
|
|
57
|
+
retryAfterSeconds: z.number().nullable(), challenge: z.boolean(), bodyDiagnostic: bodyDiagnosticSchema.optional() }).optional(),
|
|
40
58
|
expectations: z.strictObject({ expectedAnchor: z.string().nullable(), expectedRel: strings.nullable(), anchorMatches: z.boolean().nullable(), relMatches: z.boolean().nullable(), satisfied: z.boolean() }).optional(),
|
|
41
59
|
linkSignature: z.string().optional(), warnings: strings.optional(), retryAfterSeconds: z.number().optional(),
|
|
42
60
|
});
|
|
@@ -88,11 +106,28 @@ export function validateLocalResult(result) {
|
|
|
88
106
|
const rejectBytes = value => {
|
|
89
107
|
if (!value || typeof value !== 'object') return;
|
|
90
108
|
for (const [key, child] of Object.entries(value)) {
|
|
91
|
-
if (['html','body','headers'].includes(key.toLowerCase())
|
|
109
|
+
if (['html','body','headers'].includes(key.toLowerCase())
|
|
110
|
+
&& !(key === 'body' && value === observation.evidence?.capture)) throw new Error('portable observations cannot contain raw HTML/body/headers');
|
|
92
111
|
rejectBytes(child);
|
|
93
112
|
}
|
|
94
113
|
};
|
|
95
114
|
rejectBytes(observation);
|
|
115
|
+
const capture = observation.evidence.capture;
|
|
116
|
+
if (capture) {
|
|
117
|
+
if (capture.requestedUrl !== input.source || capture.finalUrl !== observation.finalUrl
|
|
118
|
+
|| !validatePublicUrl(capture.finalUrl).valid || capture.httpStatus !== observation.httpStatus
|
|
119
|
+
|| capture.acquiredAt !== observation.evidence.fetchedAt
|
|
120
|
+
|| capture.body.bytes !== observation.evidence.bytes || capture.body.sha256 !== observation.evidence.sha256
|
|
121
|
+
|| capture.body.decodedSha256 !== observation.evidence.decodedSha256
|
|
122
|
+
|| observation.evidence.method !== 'http_html') throw new Error('portable capture does not match its observation');
|
|
123
|
+
if (capture.body.charset != null) {
|
|
124
|
+
try { new TextDecoder(capture.body.charset, { fatal: true }); }
|
|
125
|
+
catch { throw new Error('invalid portable capture charset'); }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (observation.evidence.renderAttempt && (observation.state !== 'unknown' || observation.evidence.complete !== false
|
|
129
|
+
|| !['possible_render_required', 'navigation_requires_browser'].includes(observation.reason)
|
|
130
|
+
|| observation.evidence.renderEligibility !== 'required')) throw new Error('invalid portable refused-render observation');
|
|
96
131
|
if (observation.occurrences.some(item => !item || typeof item.targetUrl !== 'string' || !Array.isArray(item.rel)
|
|
97
132
|
|| !matchesTarget(item.targetUrl, input.target, input.scope))) throw new Error('invalid portable occurrence');
|
|
98
133
|
if (observation.state === 'present' && (!observation.evidence.complete || !observation.occurrences.length)) throw new Error('present requires complete matching occurrences');
|
package/cli/main.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import {projectPlacements} from './placement-projection.js';
|
|
2
|
+
import {chronologyMain} from './chronology.js';
|
|
1
3
|
import { acquireSyncLock } from './sync-lock.js';
|
|
2
4
|
import {lifecycleMain} from './lifecycle.js';
|
|
5
|
+
import {campaignMain,CAMPAIGN_CLI_HELP} from './campaign-bridge.js';
|
|
3
6
|
import {admissionMain} from './admission.js';
|
|
4
7
|
import { commandsMain } from './commands.js';
|
|
5
8
|
import { disavowMain } from './disavow.js';
|
|
@@ -21,8 +24,10 @@ import { checkToFile, adoptLocalResult } from './local-result.js';
|
|
|
21
24
|
import { disagreements, transitions } from './status.js';
|
|
22
25
|
import { readImport, parseMap } from './import.js';
|
|
23
26
|
import { freezeDataset, renderReport, datasetDigest } from './report.js';
|
|
24
|
-
import { createClient, CloudError } from './client.js';
|
|
25
|
-
import { cloudConnection, connectMain, keySetup } from './connection.js';
|
|
27
|
+
import { createClient, CloudError, CLI_VERSION } from './client.js';
|
|
28
|
+
import { cloudConnection, connectKey, connectMain, keySetup } from './connection.js';
|
|
29
|
+
import { createKeyStore, withStoredKeys } from './key-store.js';
|
|
30
|
+
import { askSecret } from './prompt.js';
|
|
26
31
|
import { syncPlan, pushExpectations, pullLifecycleMirrors, pullEvents, cloudObservationRows, watchIndex, pullTargetEvents } from './sync.js';
|
|
27
32
|
import { SUPPLIER_NAMES } from './adapters/suppliers.js';
|
|
28
33
|
import { fleetSummary, renderFleet } from './fleet.js';
|
|
@@ -44,6 +49,8 @@ import { citationMain } from './citation.js';
|
|
|
44
49
|
|
|
45
50
|
const USAGE = `agentlinkops — a backlink ledger that lives in your repository
|
|
46
51
|
|
|
52
|
+
agentlinkops --version print the installed CLI version
|
|
53
|
+
|
|
47
54
|
agentlinkops skill [--url|--list [--json]|NAME] read the agent reference or a named skill
|
|
48
55
|
before the first AgentLinkOps call
|
|
49
56
|
agentlinkops init create .agentlinkops/ here
|
|
@@ -52,9 +59,12 @@ const USAGE = `agentlinkops — a backlink ledger that lives in your repository
|
|
|
52
59
|
[--mode local|hosted|external] read-only setup plan; no account required
|
|
53
60
|
agentlinkops add --source URL --target URL [--intent wanted|expected] [--scope …]
|
|
54
61
|
[--anchor TEXT] [--rel a,b] [--ref TEXT] [--tag t --tag t] [--note TEXT]
|
|
62
|
+
agentlinkops chronology pull --watch-id ID [--limit N] [--cursor TOKEN] [--all] [--out FILE]
|
|
63
|
+
agentlinkops chronology export --pages FILE [--submissions FILE] [--out FILE]
|
|
55
64
|
agentlinkops index pull --watch-id ID [--limit N] [--before ID]
|
|
65
|
+
agentlinkops campaign plan|apply|reconcile|rollback|pull-plan|pull-apply|pull-rollback|outcomes-plan|outcomes-apply
|
|
66
|
+
preview and reconcile portable campaign records; no sending or automatic checks
|
|
56
67
|
agentlinkops lifecycle get|update|clear|renew|report
|
|
57
|
-
agentlinkops admission list|add|update|remove|evaluate|reevaluate|receipt --project ID
|
|
58
68
|
agentlinkops disavow list|history|propose|import|export|approve|reject|delete
|
|
59
69
|
agentlinkops index import FILE | export [--out FILE] | csv URLS.csv [--out FILE]
|
|
60
70
|
agentlinkops index submissions-import FILE | submissions-export --project-id ID [--out FILE]
|
|
@@ -63,7 +73,7 @@ const USAGE = `agentlinkops — a backlink ledger that lives in your repository
|
|
|
63
73
|
agentlinkops import FILE --target DOMAIN [--from SUPPLIER] [--map source=COL,target=COL]
|
|
64
74
|
[--exact-url] [--no-subdomains] [--generated-at ISO] [--json]
|
|
65
75
|
agentlinkops adopt CRM.sqlite [--scope exact|domain] [--write]
|
|
66
|
-
agentlinkops fleet --project NAME=LEDGER [--project …] [--observations-of NAME=FILE] [--json]
|
|
76
|
+
agentlinkops fleet --project NAME=LEDGER [--project …] [--observations-of NAME=FILE] [--state-of NAME=FILE] [--json]
|
|
67
77
|
agentlinkops platforms [HOST] [--observations FILE]… [--benchmark FILE]… [--seed FILE]…
|
|
68
78
|
[--for-candidates FILE] [--json]
|
|
69
79
|
agentlinkops context COMMAND… first-party search context and site profile
|
|
@@ -78,7 +88,9 @@ const USAGE = `agentlinkops — a backlink ledger that lives in your repository
|
|
|
78
88
|
.agentlinkops/citations/; live engines need
|
|
79
89
|
engine-specific setup; the mock engine makes no paid calls
|
|
80
90
|
agentlinkops receive --body FILE --headers FILE signed notification; authenticated pull
|
|
81
|
-
agentlinkops connect --
|
|
91
|
+
agentlinkops connect [--project-id ID] [--workspace ID] [--origin URL] [--selection FILE] [--oauth --issuer URL --client-id ID [--credential-ref REF]]
|
|
92
|
+
paste an API key once; it is saved outside the
|
|
93
|
+
repository and the key names its workspace
|
|
82
94
|
agentlinkops tools [TOOLSET] [--json] [--refresh] cloud commands by toolset (offline snapshot;
|
|
83
95
|
--refresh reads the live catalog)
|
|
84
96
|
agentlinkops describe NAME [--examples] [--output-schema] [--json] one command's exact schema
|
|
@@ -87,7 +99,8 @@ const USAGE = `agentlinkops — a backlink ledger that lives in your repository
|
|
|
87
99
|
agentlinkops agent setup [--client ID ...] [--scope project|user] [--origin URL] [--dry-run]
|
|
88
100
|
core connection/reference and the right MCP view;
|
|
89
101
|
optional --skill ID ... / --recipe ID ... / --all-skills;
|
|
90
|
-
stores no credential
|
|
102
|
+
stores no credential unless you paste a CLI key
|
|
103
|
+
when asked; preserves customer instructions
|
|
91
104
|
agentlinkops agent status what is installed where
|
|
92
105
|
agentlinkops agent remove [--skill ID ...] [--recipe ID ...] [--scope project|user] [--apply] [--json]
|
|
93
106
|
agentlinkops agent recover [--scope project|user] [--apply] [--json]
|
|
@@ -100,7 +113,7 @@ const USAGE = `agentlinkops — a backlink ledger that lives in your repository
|
|
|
100
113
|
agentlinkops adopt-result FILE [--intent wanted|expected] save result to local ledger
|
|
101
114
|
agentlinkops receipt add --file CLAIM.json project.site declares public site hosts
|
|
102
115
|
agentlinkops receipt history [--performance FILE.json] claims, history and optional dated context
|
|
103
|
-
agentlinkops status [--json]
|
|
116
|
+
agentlinkops status [--all] [--json]
|
|
104
117
|
agentlinkops diff [--json]
|
|
105
118
|
agentlinkops report [--out FILE] [--title T] [--brand B] [--as-of ISO]
|
|
106
119
|
[--include-notes] [--include-retired] [--digest-only] [--json]
|
|
@@ -122,7 +135,25 @@ evidence · 2 usage, configuration or ledger error. Unknown is nonfailing by def
|
|
|
122
135
|
Names: \`linktrail\` still runs this CLI, LINKTRAIL_* variables are still read, and an existing
|
|
123
136
|
.linktrail/ directory is still used, each with a one-line notice, until the DP-0029 cutover.`;
|
|
124
137
|
|
|
125
|
-
|
|
138
|
+
// After `agent setup` on a terminal: the CLI half, so the folder's agent can also run
|
|
139
|
+
// `agentlinkops call`. Optional (Enter skips), and setup has already succeeded either way.
|
|
140
|
+
async function connectCliAfterSetup(origin, { env, ask, keyStore, fetchImpl, out }) {
|
|
141
|
+
let connection = null;
|
|
142
|
+
try { connection = cloudConnection({ cloud: { origin } }, env); } catch { /* Disagreeing variables: connect reports them. */ }
|
|
143
|
+
out('');
|
|
144
|
+
if (connection?.token) { out(`The agentlinkops CLI can use this account too (key in ${connection.tokenSource}).`); return; }
|
|
145
|
+
try {
|
|
146
|
+
const key = await connectKey({ origin, ask, keyStore, fetchImpl, out,
|
|
147
|
+
prompt: `To use the agentlinkops CLI as well, paste an API key from ${origin}/#/access (Create API key), or press Enter to skip: ` });
|
|
148
|
+
out(key
|
|
149
|
+
? `The CLI is connected to ${key.account.workspace.name || 'your workspace'}. Try: agentlinkops call list_tasks`
|
|
150
|
+
: 'Skipped. Run `npx -y agentlinkops connect` when you want the CLI.');
|
|
151
|
+
} catch (error) {
|
|
152
|
+
out(`The CLI wasn't connected: ${error.serverMessage ?? error.message}. Run \`npx -y agentlinkops connect\` to try again.`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const parseArgs = argv => parseArguments(argv,["tag", "project", "observations-of", "state-of", "observations", "benchmark", "seed"],{tag:[]});
|
|
126
157
|
|
|
127
158
|
const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
128
159
|
|
|
@@ -135,7 +166,16 @@ function reportProblems(problems, what, out) {
|
|
|
135
166
|
if (problems.length > 20) out(` … and ${problems.length - 20} more`);
|
|
136
167
|
}
|
|
137
168
|
|
|
138
|
-
export async function main(argv = process.argv.slice(2),
|
|
169
|
+
export async function main(argv = process.argv.slice(2), options) {
|
|
170
|
+
// The executables call main() with no options, the one caller that reads the person's saved
|
|
171
|
+
// key and asks on their terminal. Every other caller passes options and gets neither.
|
|
172
|
+
const executable = options === undefined;
|
|
173
|
+
const { cwd = process.cwd(), out = console.log, err = console.error, fetchImpl = globalThis.fetch, localFetchImpl = null, afterHistoryPersisted, afterStatePersisted, signal,
|
|
174
|
+
keyStore = executable ? createKeyStore() : null,
|
|
175
|
+
ask = executable && process.stdin.isTTY === true && process.stderr.isTTY === true ? askSecret : null } = options ?? {};
|
|
176
|
+
if (argv.length === 1 && argv[0] === '--version') { out(CLI_VERSION); return 0; }
|
|
177
|
+
if (argv[0] === 'campaign' && argv.length===2 && ['--help','help'].includes(argv[1])) { out(CAMPAIGN_CLI_HELP); return 0; }
|
|
178
|
+
const env = withStoredKeys(options?.env ?? process.env, keyStore);
|
|
139
179
|
if (argv[0] === 'gsc-links') return await gscLinksMain(argv, { cwd, out });
|
|
140
180
|
// Citation watches keep their own files under .agentlinkops/citations/ and never
|
|
141
181
|
// touch the link ledger, so they dispatch before its requirement like context does.
|
|
@@ -151,24 +191,27 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
|
|
|
151
191
|
try {
|
|
152
192
|
if (args['recover-cursors'] !== undefined && (command !== 'sync' || args['recover-cursors'] !== true || args['pull-only'] !== true || args['push-only']))
|
|
153
193
|
throw new ConfigError('Cursor recovery requires agentlinkops sync --pull-only --recover-cursors; it performs no remote writes.');
|
|
194
|
+
if (command === 'campaign') return await campaignMain(args,{cwd,env,fetchImpl,out});
|
|
154
195
|
if (command === 'lifecycle') return await lifecycleMain(args,{cwd,env,fetchImpl,out,err});
|
|
155
196
|
if (command === 'admission') { const config=await loadConfig({cwd}); return await admissionMain(argv.slice(1),{client:createClient({...cloudConnection(config,env),fetchImpl}),out,err}); }
|
|
156
197
|
if (command === 'disavow') return await disavowMain(args,{cwd,env,fetchImpl,out});
|
|
198
|
+
if (command === 'chronology') return await chronologyMain(args,{cwd,env,fetchImpl,out});
|
|
157
199
|
if (command === 'index') return await indexMain(args,{cwd,env,fetchImpl,out});
|
|
158
200
|
if (command === 'setup') return await setupPlanMain(argv, { cwd, out });
|
|
159
201
|
if (command === 'tools' || command === 'describe' || command === 'schema' || command === 'call') return await commandsMain(argv, { cwd, out, err, env, fetchImpl });
|
|
160
202
|
if (command === 'skill') return await skillMain(argv, { out });
|
|
161
|
-
if (command === 'agent') return await agentMain(argv, { cwd, out, err });
|
|
203
|
+
if (command === 'agent') return await agentMain(argv, { cwd, out, err, env, connectCli: ask && keyStore ? origin => connectCliAfterSetup(origin, { env, ask, keyStore, fetchImpl, out }) : null });
|
|
162
204
|
if (command === 'connect') {
|
|
163
|
-
if (args._.length !== 1 || args.tag.length || Object.keys(args).some(key => !['_', 'tag', 'origin', 'workspace', 'project-id', 'selection', 'oauth', 'issuer', 'client-id', 'credential-ref'].includes(key))) throw new ConfigError('connect --
|
|
164
|
-
return await connectMain(args, { cwd, out, env, fetchImpl });
|
|
205
|
+
if (args._.length !== 1 || args.tag.length || Object.keys(args).some(key => !['_', 'tag', 'origin', 'workspace', 'project-id', 'selection', 'oauth', 'issuer', 'client-id', 'credential-ref'].includes(key))) throw new ConfigError('connect [--project-id ID] [--workspace ID] [--origin URL] [--selection FILE] [--oauth --issuer URL --client-id ID [--credential-ref REF]]');
|
|
206
|
+
return await connectMain(args, { cwd, out, env, fetchImpl, keyStore, ask });
|
|
165
207
|
}
|
|
166
208
|
if (command === 'check' && (args.source !== undefined || args.target !== undefined)) {
|
|
167
209
|
if (typeof args.source !== 'string' || typeof args.target !== 'string') throw new ConfigError('check requires both --source URL and --target URL');
|
|
168
210
|
const allowed = new Set(['_', 'source', 'target', 'scope', 'json', 'out']);
|
|
169
211
|
if (Object.keys(args).some(key => key !== 'tag' && !allowed.has(key)) || args.tag.length || args._.length !== 1) throw new ConfigError('unsupported option for a single local check');
|
|
170
212
|
if (args.out !== undefined && (typeof args.out !== 'string' || !args.out.trim())) throw new ConfigError('--out requires a file path');
|
|
171
|
-
const result = await checkToFile({ source: args.source, target: args.target, scope: args.scope }, args.out ? resolve(cwd, args.out) : null
|
|
213
|
+
const result = await checkToFile({ source: args.source, target: args.target, scope: args.scope }, args.out ? resolve(cwd, args.out) : null,
|
|
214
|
+
{ fetchImpl: localFetchImpl });
|
|
172
215
|
out(args.json ? JSON.stringify(result, null, 2) : `${result.observation.state} ${result.observation.reason} ${result.checked_at}${args.out ? ` saved ${args.out}` : ''}`);
|
|
173
216
|
return 0;
|
|
174
217
|
}
|
|
@@ -306,7 +349,15 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
|
|
|
306
349
|
if (!name || !path) throw new ConfigError(`--observations-of expects NAME=FILE, got "${value}"`);
|
|
307
350
|
observationsOf[name] = path;
|
|
308
351
|
}
|
|
309
|
-
const
|
|
352
|
+
const statesOf = {};
|
|
353
|
+
for (const value of [].concat(args['state-of'] ?? [])) {
|
|
354
|
+
const separator = String(value).indexOf('=');
|
|
355
|
+
const name = String(value).slice(0, separator), path = String(value).slice(separator + 1);
|
|
356
|
+
if (separator < 1 || !path || !pairs.some(project => project.name === name)) throw new ConfigError('--state-of expects a declared NAME=FILE');
|
|
357
|
+
if (Object.hasOwn(statesOf, name)) throw new ConfigError(`Duplicate state input for ${name}`);
|
|
358
|
+
statesOf[name] = path;
|
|
359
|
+
}
|
|
360
|
+
const summary = await fleetSummary(pairs, { observationsOf, statesOf });
|
|
310
361
|
if (args.json) { out(JSON.stringify(summary, null, 1)); return 0; }
|
|
311
362
|
return renderFleet(summary, out);
|
|
312
363
|
}
|
|
@@ -320,7 +371,7 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
|
|
|
320
371
|
// DP-0017's context surface owns its config and files and needs no ledger, so it runs
|
|
321
372
|
// before the ledger check below. contextMain parses its own argv; hand it everything
|
|
322
373
|
// after the command word (found positionally, because flags may precede it).
|
|
323
|
-
return await contextMain(argv.slice(argv.indexOf(command) + 1), { cwd, out, err });
|
|
374
|
+
return await contextMain(argv.slice(argv.indexOf(command) + 1), { cwd, out, err,signal });
|
|
324
375
|
}
|
|
325
376
|
if (command === 'doctor') {
|
|
326
377
|
// Doctor must run exactly where the ledger cannot be trusted yet: its job is to name
|
|
@@ -328,7 +379,7 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
|
|
|
328
379
|
// file itself. Takes no flags and repairs nothing. Awaited on purpose: a promise returned
|
|
329
380
|
// from inside the try escapes the ConfigError handler below, and a disagreeing pair of
|
|
330
381
|
// token spellings would surface as a stack trace instead of the exit-2 message.
|
|
331
|
-
return await doctorMain(argv.slice(argv.indexOf(command) + 1), { cwd, out, err });
|
|
382
|
+
return await doctorMain(argv.slice(argv.indexOf(command) + 1), { cwd, out, err, env });
|
|
332
383
|
}
|
|
333
384
|
|
|
334
385
|
const config = await loadConfig({ cwd });
|
|
@@ -392,6 +443,7 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
|
|
|
392
443
|
// so it is unchanged by a cosmetic edit to the template and changes the moment a figure does.
|
|
393
444
|
const asOf = args['as-of'] === true ? null : (args['as-of'] ?? null);
|
|
394
445
|
const dataset = freezeDataset(ledger.entries, observations.rows, {
|
|
446
|
+
activityState: await readState(config.paths.state),
|
|
395
447
|
asOf: asOf ?? new Date().toISOString().slice(0, 10),
|
|
396
448
|
title: args.title === true ? 'Backlink report' : (args.title ?? 'Backlink report'),
|
|
397
449
|
includeNotes: args['include-notes'] === true,
|
|
@@ -500,24 +552,29 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
|
|
|
500
552
|
}
|
|
501
553
|
|
|
502
554
|
if (command === 'status' || command === 'diff') {
|
|
503
|
-
const
|
|
504
|
-
|
|
555
|
+
const activityState = command === 'status' ? await readState(config.paths.state) : null;
|
|
556
|
+
const found = command === 'status' ? disagreements(ledger.entries, observations.rows, { state: activityState }) : transitions(observations.rows);
|
|
557
|
+
const activity = command === 'status' && args.all ? projectPlacements(ledger.entries, observations.rows, { state: activityState }).map(({ entry, row, ...facts }) => ({ id: entry.id, source: entry.source, target: entry.target, scope: entry.scope, ...facts })) : null;
|
|
558
|
+
if (args.json) { out(JSON.stringify(activity ? { disagreements: found, placements: activity } : found, null, 1)); return 0; }
|
|
505
559
|
reportProblems(ledger.problems, 'ledger line(s)', out);
|
|
506
560
|
if (command === 'diff') {
|
|
507
561
|
if (!found.length) out('no state changes recorded');
|
|
508
562
|
for (const change of found.slice(0, 100)) out(` ${change.at.slice(0, 10)} ${change.from} -> ${change.to} ${change.id}`);
|
|
509
563
|
return 0;
|
|
510
564
|
}
|
|
511
|
-
const groups = { appeared: 'appeared', lost: '
|
|
565
|
+
const groups = { appeared: 'appeared', lost: 'confirmed missing', suspected_missing: 'suspected missing', cannot_say: 'cannot say', never_checked: 'never checked' };
|
|
512
566
|
for (const [kind, label] of Object.entries(groups)) {
|
|
513
567
|
const rows = found.filter(item => item.kind === kind);
|
|
514
568
|
if (!rows.length) continue;
|
|
515
569
|
out(`\n${label} (${rows.length})`);
|
|
516
570
|
for (const item of rows.slice(0, 50)) {
|
|
517
571
|
out(` ${item.entry.id} ${item.entry.source}${item.row ? ` [${item.row.reason}]` : ''}`);
|
|
572
|
+
out(` Latest attempt: ${item.activity?.latest_attempt?.checked_at ?? 'not retained'} (${item.activity?.latest_attempt?.reason ?? 'unknown'})`);
|
|
573
|
+
out(` Last conclusive: ${item.activity?.last_successful_observation?.checked_at ?? 'not retained'}; last link verification: ${item.activity?.last_link_verification?.checked_at ?? 'not retained'}`);
|
|
518
574
|
}
|
|
519
575
|
}
|
|
520
576
|
if (!found.length) out('intent and observations agree on every entry');
|
|
577
|
+
if (activity) for (const item of activity) out(` ${item.id}: latest attempt ${item.latest_attempt?.checked_at ?? 'not retained'}; last conclusive ${item.last_successful_observation?.checked_at ?? 'not retained'}; last link verification ${item.last_link_verification?.checked_at ?? 'not retained'}`);
|
|
521
578
|
return 0;
|
|
522
579
|
}
|
|
523
580
|
|
|
@@ -590,13 +647,14 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
|
|
|
590
647
|
if (ledger.problems.length) { reportProblems(ledger.problems, 'ledger line(s)', err); return 2; }
|
|
591
648
|
const state = await readState(config.paths.state);
|
|
592
649
|
const selected = selectEntries(ledger.entries, { filter: args.filter === true ? null : args.filter });
|
|
593
|
-
const entries = args.all ? selected : dueEntries(selected, lastCheckedMap(state));
|
|
650
|
+
const entries = args.all ? selected : dueEntries(selected, lastCheckedMap(state, selected));
|
|
594
651
|
if (!entries.length) { (args.json ? err : out)(`nothing due (${plural(selected.length, 'entry', 'entries')} in scope; pass --all to recheck)`); return 0; }
|
|
595
652
|
if (!args.json) err(`checking ${plural(entries.length, 'entry', 'entries')}…`);
|
|
596
653
|
const rows = await runCheck(entries, {
|
|
597
654
|
concurrency: Number(args.concurrency ?? config.defaults.concurrency ?? DEFAULTS.concurrency),
|
|
598
655
|
timeoutMs: Number(args.timeout ?? config.defaults.timeoutMs ?? DEFAULTS.timeoutMs),
|
|
599
656
|
hostDelayMs: Number(args['host-delay'] ?? config.defaults.hostDelayMs ?? DEFAULTS.hostDelayMs),
|
|
657
|
+
fetchImpl: localFetchImpl,
|
|
600
658
|
});
|
|
601
659
|
// The mirror records changes; `state.json` records activity. Appending a row that says
|
|
602
660
|
// the same thing as the one above it is 2 KB of git churn per link per run, and the only
|
|
@@ -625,6 +683,13 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
|
|
|
625
683
|
err(JSON.stringify({error: {code: error.code, status: error.status, next: 'Keep this project ledger and its history. Resolve authorized read access for the configured project, then retry agentlinkops sync --pull-only --recover-cursors. Do not broaden grants or replay remote writes.'}}));
|
|
626
684
|
return 2;
|
|
627
685
|
}
|
|
686
|
+
if (command === 'sync' && error instanceof CloudError && error.status === 0 &&
|
|
687
|
+
['CLOUD_TIMEOUT', 'CLOUD_NETWORK_ERROR'].includes(error.code)) {
|
|
688
|
+
// A local transport failure cannot establish whether a write committed.
|
|
689
|
+
// Print trusted recovery guidance, never attached exception/server text.
|
|
690
|
+
err(`agentlinkops: ${error.code}\nThe request outcome is unknown. Check the operation result before sending it again. Keep this project ledger and its history. Inspect accessible watches and their local references with agentlinkops sync --pull-only --recover-cursors. Do not broaden grants or replay remote writes.`);
|
|
691
|
+
return 2;
|
|
692
|
+
}
|
|
628
693
|
err(`agentlinkops: ${error?.message ?? error}`);
|
|
629
694
|
return 2;
|
|
630
695
|
} finally {
|
package/cli/mcp-client.js
CHANGED
|
@@ -62,7 +62,7 @@ function retryDelay(error, attempt, now) {
|
|
|
62
62
|
|
|
63
63
|
async function sdkClient({url, token, workspaceId, fetchImpl}) {
|
|
64
64
|
const {Client, StreamableHTTPClientTransport} = await import('@modelcontextprotocol/client');
|
|
65
|
-
const client = new Client({name: 'agentlinkops-cli', version: '0.6.
|
|
65
|
+
const client = new Client({name: 'agentlinkops-cli', version: '0.6.16'});
|
|
66
66
|
const destination = new URL(url);
|
|
67
67
|
const transport = new StreamableHTTPClientTransport(destination, {
|
|
68
68
|
authProvider: {token: async () => token},
|
|
@@ -71,7 +71,7 @@ async function sdkClient({url, token, workspaceId, fetchImpl}) {
|
|
|
71
71
|
if (target.href !== destination.href) fail('OAUTH_TOKEN_DESTINATION_MISMATCH');
|
|
72
72
|
const headers = new Headers(init.headers ?? (input instanceof Request ? input.headers : undefined));
|
|
73
73
|
if (workspaceId) headers.set('X-Workspace-ID', workspaceId);
|
|
74
|
-
headers.set('User-Agent', 'agentlinkops-cli/0.6.
|
|
74
|
+
headers.set('User-Agent', 'agentlinkops-cli/0.6.16');
|
|
75
75
|
const response = await fetchImpl(input, {...init, headers, redirect: 'error', signal: AbortSignal.timeout(30000)});
|
|
76
76
|
if (response.redirected || response.status >= 300 && response.status < 400
|
|
77
77
|
|| response.url && response.url !== destination.href) fail('OAUTH_TOKEN_DESTINATION_MISMATCH');
|