agentlinkops 0.6.10 → 0.6.11

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.10",
3
+ "version": "0.6.11",
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.10",
3
+ "version": "0.6.11",
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,13 @@
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.11 (2026-10-06)
7
+
8
+ - Keep `check --json` stdout limited to observation rows. Send empty-ledger, not-due and filter notices to stderr.
9
+ - Reject malformed ledger rows with line numbers and exit with status 2 before fetching or updating customer records.
10
+ - Apply `--fail-on-unknown` to JSON and human output. Unknown observations remain nonfailing by default.
11
+ - Detect duplicate placements across import batches and retain their original file line numbers.
12
+
6
13
  ## 0.6.10 (2026-10-06)
7
14
 
8
15
  Inspect the installed version's help before using selectors; older published versions
package/README.md CHANGED
@@ -98,10 +98,10 @@ agentlinkops status
98
98
  the source page and appends an observation to the mirror. `status` names every entry whose
99
99
  observations disagree with its intent and proposes the edit; applying it is a commit you make.
100
100
 
101
- For ledger checks, exit codes are `0` when every expected link is present, `1` only
102
- when an expected link is observed absent with complete evidence, and `2` for a usage,
103
- configuration or ledger error. An unknown never fails the run.
104
- Pass `--fail-on-unknown` when your CI wants it to.
101
+ Ledger checks return `0` when no expected placement is conclusively absent and `1` when
102
+ an expected placement is observed absent with complete evidence. Unknown observations do
103
+ not fail a run by default. Pass `--fail-on-unknown` to return `1` for an unknown result
104
+ in text or JSON output. Usage, configuration and malformed-ledger errors return `2`.
105
105
 
106
106
  `agentlinkops doctor` verifies the ledger, the verifier self-test, cloud reachability and the
107
107
  token, and prints a fix line for anything that fails.
package/cli/client.js CHANGED
@@ -5,7 +5,7 @@ export {CloudError} from './cloud-error.js';
5
5
  // A small HTTP client for the cloud. Scoped keys only; no human session ever reaches here.
6
6
  // DP-0036-T13: the CLI names itself so the usage counters can tell it from other REST callers.
7
7
  // The version follows the plugin manifests; bump it with them.
8
- export const CLI_VERSION = '0.6.10';
8
+ export const CLI_VERSION = '0.6.11';
9
9
  export const CLI_USER_AGENT = `agentlinkops-cli/${CLI_VERSION}`;
10
10
  const MAX_ATTEMPTS = 3;
11
11
  const MAX_RETRY_WAIT_MS = 5_000;
package/cli/import.js CHANGED
@@ -74,11 +74,14 @@ export async function readImport(path, {
74
74
  // chunks, so a placement repeated in two different parts of one file is still one candidate.
75
75
  const prepared = { accepted: [], rejected: [], duplicates: [] };
76
76
  const seen = new Map();
77
+ // Candidate identity includes the run. These bounded slices describe one preview, so keep
78
+ // its first-slice identity throughout the file or a repeated placement gets a new ID.
79
+ const previewRunId = `${runId}_0`;
77
80
  const chunks = [];
78
81
  for (let index = 0; index < rows.length; index += DISCOVERY_LIMITS.rows) chunks.push(index);
79
82
  for (const offset of chunks) {
80
83
  const part = await prepareImport({
81
- runId: `${runId}_${offset / DISCOVERY_LIMITS.rows}`, workspaceId, projectId, supplier,
84
+ runId: previewRunId, workspaceId, projectId, supplier,
82
85
  rows: rows.slice(offset, offset + DISCOVERY_LIMITS.rows), target: targetUrl, scope,
83
86
  taskId: 'import_preview', retrievedAt: now, generatedAt: supplierGeneratedAt,
84
87
  seen, rowOffset: offset,
package/cli/main.js CHANGED
@@ -115,8 +115,9 @@ const USAGE = `agentlinkops — a backlink ledger that lives in your repository
115
115
  Suppliers: ${SUPPLIER_NAMES.join(', ')}
116
116
  A preset is a convenience over --map, never a requirement.
117
117
 
118
- Exit codes: 0 every expected link present · 1 an expected link observed absent with complete
119
- evidence · 2 usage, configuration or ledger error. An unknown never fails.
118
+ Exit codes: 0 no expected link conclusively absent · 1 an expected link observed absent with complete
119
+ evidence · 2 usage, configuration or ledger error. Unknown is nonfailing by default;
120
+ --fail-on-unknown returns 1 when any observation is unknown.
120
121
 
121
122
  Names: \`linktrail\` still runs this CLI, LINKTRAIL_* variables are still read, and an existing
122
123
  .linktrail/ directory is still used, each with a one-line notice, until the DP-0029 cutover.`;
@@ -586,10 +587,11 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
586
587
  }
587
588
 
588
589
  if (command === 'check') {
590
+ if (ledger.problems.length) { reportProblems(ledger.problems, 'ledger line(s)', err); return 2; }
589
591
  const state = await readState(config.paths.state);
590
592
  const selected = selectEntries(ledger.entries, { filter: args.filter === true ? null : args.filter });
591
593
  const entries = args.all ? selected : dueEntries(selected, lastCheckedMap(state));
592
- if (!entries.length) { out(`nothing due (${plural(selected.length, 'entry', 'entries')} in scope; pass --all to recheck)`); return 0; }
594
+ if (!entries.length) { (args.json ? err : out)(`nothing due (${plural(selected.length, 'entry', 'entries')} in scope; pass --all to recheck)`); return 0; }
593
595
  if (!args.json) err(`checking ${plural(entries.length, 'entry', 'entries')}…`);
594
596
  const rows = await runCheck(entries, {
595
597
  concurrency: Number(args.concurrency ?? config.defaults.concurrency ?? DEFAULTS.concurrency),
@@ -603,8 +605,8 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
603
605
  await appendObservations(config.paths.observations, changed);
604
606
  await writeState(config.paths.state, applyRun(state, rows));
605
607
  const summary = summarize(entries, rows);
606
- if (args.json) { out(rows.map(row => JSON.stringify(row)).join('\n')); return summary.exitCode; }
607
- reportProblems(ledger.problems, 'ledger line(s)', out);
608
+ const exitCode = args['fail-on-unknown'] && summary.counts.unknown ? 1 : summary.exitCode;
609
+ if (args.json) { out(rows.map(row => JSON.stringify(row)).join('\n')); return exitCode; }
608
610
  for (const { entry, row } of summary.appeared) out(`APPEARED ${entry.id} ${entry.source} (${row.occurrences} occurrence(s))`);
609
611
  for (const { entry, row } of summary.failures) out(`ABSENT ${entry.id} ${entry.source} [${row.reason}]`);
610
612
  out(`\n${JSON.stringify(summary.counts)}`);
@@ -612,8 +614,7 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
612
614
  if (summary.counts.unknown) {
613
615
  out(`${plural(summary.counts.unknown, 'page')} could not be concluded. An unknown is not a lost link; run \`agentlinkops status\` for the reasons.`);
614
616
  }
615
- if (args['fail-on-unknown'] && summary.counts.unknown) return 1;
616
- return summary.exitCode;
617
+ return exitCode;
617
618
  }
618
619
 
619
620
  err(`unknown command: ${command}\n\n${USAGE}`);
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.10'});
65
+ const client = new Client({name: 'agentlinkops-cli', version: '0.6.11'});
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.10');
74
+ headers.set('User-Agent', 'agentlinkops-cli/0.6.11');
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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlinkops",
3
- "version": "0.6.10",
3
+ "version": "0.6.11",
4
4
  "description": "Backlink checks with dated evidence and portable results, optional repository ledgers, supplier-export imports and sync to AgentLinkOps.",
5
5
  "author": "AgentLinkOps (https://agentlinkops.com)",
6
6
  "license": "Apache-2.0",
@@ -9,7 +9,7 @@ records. This page is the complete agent reference: the same text prints from
9
9
  full once per session, then use `describe` (or `exec describe` on MCP) for any command's exact schema.
10
10
 
11
11
  <!-- generated:skill-version begin (npm run generate:tools) -->
12
- Pack version 0.6.10. 139 commands in 15 toolsets.
12
+ Pack version 0.6.11. 139 commands in 15 toolsets.
13
13
  <!-- generated:skill-version end -->
14
14
 
15
15
  ## Install and connect (once per machine)
@@ -6,7 +6,7 @@ allowed-tools: Read Bash(python3 ../../scripts/agentlinkops.py *) Bash(python3 .
6
6
 
7
7
  **For the optional bundled CLI, the local surface is the ledger file.** `.agentlinkops/links.jsonl` in the user's repository (or a `.linktrail/` directory from an earlier install, read until `agentlinkops migrate` renames it) declares the links they expect, want and have earned, and `agentlinkops check` runs the exact cloud verifier against it without an account. Run the CLI as `node cli/agentlinkops.mjs` from the repository or plugin-package root (`cli/linktrail.mjs` still works and prints a one-line notice); the packaged plugin ships the CLI, its pinned verifier modules and their pinned runtime dependencies inside the package, so the installed package needs no npm install; checking a publisher still requires a read-only network fetch. It needs Node 22 or newer (`adopt` reads SQLite databases through `node:sqlite`); the Python helper below needs Python 3.10+. This CRM is one optional adapter beside CSV and JSONL: it is available, tested and unchanged, and nothing requires it. When a user already keeps links here, `agentlinkops adopt <db>` reads it into ledger entries — read-only, adding no schema — and previews unless `--write` is given. Do not propose the CRM as the place a user should start keeping links; propose it when they want contacts and outreach activity beside them, or when they already have one.
8
8
 
9
- To record a link in the ledger: `agentlinkops add --source URL --target URL --intent expected [--anchor TEXT] [--ref ID] [--note TEXT]` (`--intent wanted` for a link not yet earned; `agentlinkops init` first in a repository without `.agentlinkops/`). `agentlinkops check` then verifies every expected link and exits 0 when all are present, 1 when one is observed absent with complete evidence, 2 on a usage or ledger error; an unknown never fails.
9
+ To record a link in the ledger: `agentlinkops add --source URL --target URL --intent expected [--anchor TEXT] [--ref ID] [--note TEXT]` (`--intent wanted` for a link not yet earned; `agentlinkops init` first in a repository without `.agentlinkops/`). `agentlinkops check` then verifies every expected link and returns 0 when no expected placement is conclusively absent, 1 when one is observed absent with complete evidence, 2 on a usage or ledger error. Unknown is nonfailing by default; `--fail-on-unknown` returns 1 for an unknown result in text or JSON output.
10
10
 
11
11
  Use the deterministic Python helper at `../../scripts/agentlinkops.py`, resolved relative to this skill's directory. It requires Python 3.10+ and only its standard library. Run `--help` for the command surface. Keep the database outside the plugin installation directory; updates can replace that directory.
12
12