agentlinkops 0.6.6 → 0.6.7

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlinkops",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
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.6.6",
3
+ "version": "0.6.7",
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/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
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.6.7 (2026-09-20)
7
+
8
+ - Default browser citation measurement to accountless mode. Saved session files are ignored unless `AGENTLINKOPS_BROWSER_AUTH=saved-session` is explicitly selected.
9
+ - Reject custom launch, environment, extension and Firefox-preference overrides in accountless mode. Proxy admission remains required; direct diagnostics still need explicit authorization and configuration.
10
+ - Keep anonymous login walls, blocked pages and failed answers unknown. Accountless mode does not guarantee that a provider will answer.
11
+
6
12
  ## 0.6.6 (2026-09-20)
7
13
 
8
14
  - Retain supplied browser usage receipts in a bounded local journal after each attempt and cleanup. Missing meter values remain unknown; failed writes stop the run.
package/cli/citation.js CHANGED
@@ -28,8 +28,8 @@ const USAGE = `agentlinkops citation — AI citation watches in this repository
28
28
  tallies land under .agentlinkops/citations/
29
29
  agentlinkops citation run PANEL.json --engine chatgpt:web-own-browser
30
30
  measure the consumer UI through Camoufox
31
- (dedicated account, personal cadence; evidence
32
- includes a screenshot of every answer)
31
+ (accountless by default; saved browser sessions
32
+ are not loaded)
33
33
  agentlinkops citation sweep PANEL.json --max-usd USD --out INVENTORY.json [--dir DIR]
34
34
  run a bounded epoch and freeze its retained evidence
35
35
  agentlinkops citation inventory PANEL.json --out INVENTORY.json [--epoch ID] [--dir DIR]
@@ -52,8 +52,8 @@ const USAGE = `agentlinkops citation — AI citation watches in this repository
52
52
  agentlinkops citation alert PANEL.json [--webhook URL --report REPORT.html]
53
53
  check the latest epoch for declines;
54
54
  logs an alert line if any cell declined
55
- agentlinkops citation login ENGINE open a browser, log in once, save the session
56
- (the engine adapter uses it on every run)
55
+ agentlinkops citation login ENGINE optional legacy saved-session setup
56
+ (not needed for accountless measurement)
57
57
  agentlinkops citation logout ENGINE remove a saved session
58
58
  agentlinkops citation sessions list saved engine sessions
59
59
  agentlinkops citation sync [--dir DIR] [--evidence] push local epoch rows to the hosted
@@ -65,8 +65,11 @@ const USAGE = `agentlinkops citation — AI citation watches in this repository
65
65
 
66
66
  Panel (JSON): targets (domain or url, brand, aliases), prompts, engines
67
67
  ([{ "engine": "mock" | "perplexity", "model": "…" }]), optional samples and maxUsd.
68
- Live engines need their configured credentials or a usable browser session; the mock
69
- engine runs everything at zero cost. Every run states its estimated spend, and the
68
+ API engines need their configured credentials. Browser measurement defaults to
69
+ AGENTLINKOPS_BROWSER_AUTH=accountless and never loads saved sessions in that mode.
70
+ Optional legacy saved-session mode requires explicit AGENTLINKOPS_BROWSER_AUTH=saved-session.
71
+ Anonymous login walls or failed answers remain unknown; access is not guaranteed.
72
+ The mock engine runs everything at zero cost. Every run states its estimated spend, and the
70
73
  cap reserves estimated cost before each call. A supplier charge above that estimate
71
74
  is recorded as an overrun and stops subsequent calls. Browser measurement requires
72
75
  AGENTLINKOPS_PROXY_URL. Authorized direct diagnostics need
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.6.6';
5
+ export const CLI_VERSION = '0.6.7';
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlinkops",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
4
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",
@@ -12,7 +12,9 @@ This command defaults to the mock engine. Its suggested questions are writing pr
12
12
 
13
13
  Use `--competitors competitors.json` to add domains to check beside your own. The file is an array of objects with `domain`, `brand` and optional `aliases` fields. Each target gets separate results for each question. A target missing from one answer is only missing from that sample; it does not establish absence from the engine.
14
14
 
15
- Choose a live engine explicitly after checking access and budget. For an existing browser setup, use `--engine chatgpt:web-own-browser` or `--engine grok:web-own-browser`. `--engine google-aio` requires your configured supplier access. This workflow does not create accounts or buy services.
15
+ Choose a live engine explicitly after checking access and budget. For accountless browser measurement, use `--engine chatgpt:web-own-browser` or `--engine grok:web-own-browser`. `--engine google-aio` requires your configured supplier access. This workflow does not create accounts or buy services.
16
+
17
+ Starting with CLI 0.6.7, browser authentication defaults to `AGENTLINKOPS_BROWSER_AUTH=accountless`. This mode does not load saved browser sessions, even if session files already exist. A login wall, blocked page or failed answer remains unknown; anonymous access is not guaranteed. Use accountless measurement for this workflow. The optional legacy `AGENTLINKOPS_BROWSER_AUTH=saved-session` mode is separate and requires an explicit opt-in; logging in is not a prerequisite for the default workflow.
16
18
 
17
19
  Starting with CLI 0.6.5, browser measurement requires `AGENTLINKOPS_PROXY_URL`. Missing, failed or quarantined proxy admission never selects direct traffic. An explicitly authorized direct diagnostic requires `AGENTLINKOPS_BROWSER_EGRESS=direct-diagnostic` with no proxy configured. The standard URL proxy has no usage meter; estimated costs are not a provider invoice cap.
18
20
 
@@ -16,6 +16,6 @@ If the user authorizes outreach, use their existing email tool and honor their s
16
16
 
17
17
  When a placement is earned, save source and destination URLs and its relationship to the opportunity. Use connected monitoring tools when requested; local campaign work remains useful without cloud access. Do not promise rankings, response rates, exhaustive competitor coverage, or improvements unsupported by evidence.
18
18
 
19
- For citation panels, read [the panel authoring reference](../../references/citation-panels.md). Gather real audience questions with the user's available sources and record where each came from. `agentlinkops citation panel` drafts editable JSON with a mock engine by default. Suggested questions are not evidence of search demand. Choose a live engine only after checking the user's access and budget; keep citations, mentions, competitors and locale context separate in the results.
19
+ For citation panels, read [the panel authoring reference](../../references/citation-panels.md). Gather real audience questions with the user's available sources and record where each came from. `agentlinkops citation panel` drafts editable JSON with a mock engine by default. Suggested questions are not evidence of search demand. Choose a live engine only after checking the user's access and budget; keep citations, mentions, competitors and locale context separate in the results. Starting with CLI 0.6.7, browser citation measurement defaults to accountless and does not load saved sessions. Do not ask the user to log in as a prerequisite. Anonymous failures remain unknown. The panel reference keeps required proxy admission separate from an explicitly authorized direct diagnostic.
20
20
 
21
21
  Use the [campaign handoff recipe](../../references/recipes/qualified-campaign-handoff.md) when preparing records and reviewed drafts for the person's CRM or sender. It defines checkpoints and later readback before treating an uncertain write as complete.
@@ -61,6 +61,18 @@ export function createBrowserEngine({ engineName, env = process.env, onEvent = (
61
61
  if (!BROWSER_PROVIDERS.includes(engineName)) {
62
62
  throw new EngineError(`unknown browser engine: ${engineName} (known: ${BROWSER_PROVIDERS.join(', ')})`);
63
63
  }
64
+ const authentication = env.AGENTLINKOPS_BROWSER_AUTH ?? 'accountless';
65
+ if (!['accountless', 'saved-session'].includes(authentication)) {
66
+ throw Object.assign(new EngineError('Unsupported browser authentication policy.'), { code: 'BROWSER_AUTH_POLICY_INVALID' });
67
+ }
68
+ // The vendored resolver reads process.env directly; arbitrary launch/profile
69
+ // and extension overrides must not reintroduce ambient identity.
70
+ const assertAccountlessLaunch = () => {
71
+ if (authentication === 'accountless' && ['CAMOUFOX_EXTRA_LAUNCH_JSON', 'CAMOUFOX_ENV_JSON', 'CAMOUFOX_ADDONS', 'CAMOUFOX_ARGS', 'CAMOUFOX_FIREFOX_USER_PREFS_JSON'].some(key => env[key] || process.env[key])) {
72
+ throw Object.assign(new EngineError('Accountless measurement does not accept custom browser launch or extension overrides.'), { code: 'BROWSER_AUTH_POLICY_INVALID' });
73
+ }
74
+ };
75
+ assertAccountlessLaunch();
64
76
  const identity = { engine: engineName, provider: 'web-own-browser', model: engineName };
65
77
  const sessionDir = runtime.sessionDir ?? join(homedir(), '.agentlinkops', 'citations', 'sessions');
66
78
  const load = runtime.loadModule ?? lazy;
@@ -110,6 +122,7 @@ export function createBrowserEngine({ engineName, env = process.env, onEvent = (
110
122
  const proxy = admission.proxy;
111
123
  await event(proxy?'egress: proxy-required':'egress: direct-diagnostic');
112
124
 
125
+ assertAccountlessLaunch();
113
126
  const options = await resolveCamoufoxLaunchOptions({
114
127
  provider: engineName,
115
128
  proxy: proxy ?? undefined,
@@ -123,6 +136,7 @@ export function createBrowserEngine({ engineName, env = process.env, onEvent = (
123
136
  const launchOpts = { ...options, executablePath: options.executablePath };
124
137
  if (proxy) launchOpts.proxy = proxy;
125
138
  admission.assertReady();
139
+ assertAccountlessLaunch();
126
140
  const browser = await fw.launch(launchOpts);
127
141
 
128
142
  browserHandle = {
@@ -139,6 +153,8 @@ export function createBrowserEngine({ engineName, env = process.env, onEvent = (
139
153
  }
140
154
 
141
155
  async function loadStorageState() {
156
+ // Accountless measurement never opens session files, even when they exist.
157
+ if (authentication === 'accountless') return undefined;
142
158
  try {
143
159
  const { readFile } = await load('node:fs/promises');
144
160
  return JSON.parse(await readFile(join(sessionDir, `${engineName}.json`), 'utf8'));
@@ -362,7 +378,7 @@ export function createBrowserEngine({ engineName, env = process.env, onEvent = (
362
378
  },
363
379
  };
364
380
  const sample=engine.run.bind(engine),closeBrowser=engine.close.bind(engine);
365
- const policyCodes=new Set(['PROXY_REQUIRED','PROXY_POLICY_INVALID','PROXY_POLICY_CONFLICT','PROXY_CONFIGURATION_INVALID','PROXY_ACQUIRE_FAILED','PROXY_SUPPLIER_INVALID','PROXY_LEASE_INVALID','PROXY_LEASE_EXPIRED','PROXY_QUARANTINE_FAILED','PROXY_RELEASE_FAILED','PROXY_RECEIPT_FAILED','PROXY_CUSTODY_UNAVAILABLE','BROWSER_CLEANUP_UNCONFIRMED','BROWSER_RUN_IN_PROGRESS']);
381
+ const policyCodes=new Set(['BROWSER_AUTH_POLICY_INVALID','PROXY_REQUIRED','PROXY_POLICY_INVALID','PROXY_POLICY_CONFLICT','PROXY_CONFIGURATION_INVALID','PROXY_ACQUIRE_FAILED','PROXY_SUPPLIER_INVALID','PROXY_LEASE_INVALID','PROXY_LEASE_EXPIRED','PROXY_QUARANTINE_FAILED','PROXY_RELEASE_FAILED','PROXY_RECEIPT_FAILED','PROXY_CUSTODY_UNAVAILABLE','BROWSER_CLEANUP_UNCONFIRMED','BROWSER_RUN_IN_PROGRESS']);
366
382
  const safeError=cause=>{
367
383
  const code=policyCodes.has(cause?.code)?cause.code:'BROWSER_RUN_FAILED';
368
384
  const error=Object.assign(new EngineError(code==='BROWSER_RUN_FAILED'?'Browser sample failed.':'Browser admission or cleanup failed.',{retriable:code==='BROWSER_RUN_FAILED'&&cause?.retriable===true}),{code});
@@ -384,6 +400,7 @@ export function createBrowserEngine({ engineName, env = process.env, onEvent = (
384
400
  if(running)throw Object.assign(new EngineError('A browser sample is already running.'),{code:'BROWSER_RUN_IN_PROGRESS'});
385
401
  running=true;let result,error,reason=null,admitted=false;
386
402
  try{
403
+ assertAccountlessLaunch();
387
404
  if(admission.expired)await retire('lease_expiry');
388
405
  await admission.begin();admitted=true;
389
406
  result=await sample(input);