agentlinkops 0.6.11 → 0.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/.mcp.json +0 -0
  4. package/CHANGELOG.md +12 -0
  5. package/LICENSE +0 -0
  6. package/NOTICE +0 -0
  7. package/README.md +0 -0
  8. package/bin/agentlinkops.mjs +0 -0
  9. package/bin/linktrail.mjs +0 -0
  10. package/cli/adapters/csv.js +0 -0
  11. package/cli/adapters/sqlite-crm.js +0 -0
  12. package/cli/adapters/suppliers.js +0 -0
  13. package/cli/admission.js +0 -0
  14. package/cli/agent-setup.js +0 -0
  15. package/cli/agentlinkops.mjs +0 -0
  16. package/cli/args.js +0 -0
  17. package/cli/catalog.js +4435 -175
  18. package/cli/check.js +0 -0
  19. package/cli/chronology.js +39 -0
  20. package/cli/citation.js +0 -0
  21. package/cli/client.js +1 -1
  22. package/cli/cloud-error.js +0 -0
  23. package/cli/commands.js +0 -0
  24. package/cli/config.js +0 -0
  25. package/cli/connection.js +0 -0
  26. package/cli/context-mcp.js +0 -0
  27. package/cli/context.js +0 -0
  28. package/cli/delivery.js +0 -0
  29. package/cli/disavow.js +0 -0
  30. package/cli/doctor.js +0 -0
  31. package/cli/env.js +0 -0
  32. package/cli/fleet.js +30 -19
  33. package/cli/gsc-browser.js +0 -0
  34. package/cli/import.js +0 -0
  35. package/cli/index-observations.js +0 -0
  36. package/cli/index-submissions.js +0 -0
  37. package/cli/ledger.js +0 -0
  38. package/cli/lifecycle.js +0 -0
  39. package/cli/linktrail.mjs +0 -0
  40. package/cli/local-result.js +0 -0
  41. package/cli/main.js +34 -8
  42. package/cli/mcp-client.js +2 -2
  43. package/cli/mirror.js +0 -0
  44. package/cli/mix.js +0 -0
  45. package/cli/native-lock.js +0 -0
  46. package/cli/oauth-credentials.js +0 -0
  47. package/cli/oauth-flow.js +0 -0
  48. package/cli/placement-projection.js +106 -0
  49. package/cli/platforms.js +0 -0
  50. package/cli/receipt-performance.js +0 -0
  51. package/cli/receipts.js +0 -0
  52. package/cli/report.js +51 -19
  53. package/cli/setup-plan.js +0 -0
  54. package/cli/skill.js +0 -0
  55. package/cli/state.js +62 -29
  56. package/cli/status.js +14 -18
  57. package/cli/sync-lock.js +0 -0
  58. package/cli/sync.js +22 -3
  59. package/package.json +1 -1
  60. package/references/citation-panels.md +0 -0
  61. package/references/cli.md +0 -0
  62. package/references/disavow.md +0 -0
  63. package/references/discovery-handoff.md +0 -0
  64. package/references/local-crm-draft-handoff.md +0 -0
  65. package/references/portable-ledger-handoff.md +0 -0
  66. package/references/recipes/catalog.json +0 -0
  67. package/references/recipes/command-notes.md +0 -0
  68. package/references/recipes/placement-reconciliation.md +0 -0
  69. package/references/recipes/qualified-campaign-handoff.md +0 -0
  70. package/references/recipes/site-context-brief.md +0 -0
  71. package/references/recipes/sourced-linkable-asset.md +0 -0
  72. package/references/reports.md +0 -0
  73. package/scripts/agentlinkops.py +0 -0
  74. package/scripts/disavow.py +0 -0
  75. package/scripts/linktrail.py +0 -0
  76. package/scripts/migrations/001_crm.sql +0 -0
  77. package/scripts/migrations/002_cloud_events.sql +0 -0
  78. package/scripts/migrations/003_disavow.sql +0 -0
  79. package/scripts/migrations/004_placement_costs.sql +0 -0
  80. package/scripts/outreach-crm-draft.py +0 -0
  81. package/scripts/portable-handoff.py +0 -0
  82. package/scripts/report.py +0 -0
  83. package/shared/admission-contract.js +0 -0
  84. package/shared/admission-report-contract.js +0 -0
  85. package/shared/agent-clients.js +0 -0
  86. package/shared/citation-evidence-contract.js +0 -0
  87. package/shared/disavow-contract.js +0 -0
  88. package/shared/exec-grammar.js +0 -0
  89. package/shared/index-observation-contract.js +2 -2
  90. package/shared/lifecycle-contract.js +0 -0
  91. package/shared/lifecycle-money.js +0 -0
  92. package/shared/monitoring-chronology-export.js +57 -0
  93. package/shared/monitoring-read-contract.js +41 -0
  94. package/shared/observation-event.js +0 -0
  95. package/shared/opportunity-record.js +0 -0
  96. package/shared/response-contract.js +3 -0
  97. package/shared/search-connection-contract.js +0 -0
  98. package/skills/agentlinkops-assets/SKILL.md +0 -0
  99. package/skills/agentlinkops-assets/agents/openai.yaml +0 -0
  100. package/skills/agentlinkops-campaigns/SKILL.md +0 -0
  101. package/skills/agentlinkops-campaigns/agents/openai.yaml +0 -0
  102. package/skills/agentlinkops-connect/SKILL.md +0 -0
  103. package/skills/agentlinkops-connect/agents/openai.yaml +0 -0
  104. package/skills/agentlinkops-connect/references/agentlinkops.md +4 -3
  105. package/skills/agentlinkops-crm/SKILL.md +0 -0
  106. package/skills/agentlinkops-crm/agents/openai.yaml +0 -0
  107. package/skills/agentlinkops-discovery/SKILL.md +0 -0
  108. package/skills/content-defingerprinting/SKILL.md +0 -0
  109. package/skills/content-defingerprinting/scripts/watermarks/README.md +0 -0
  110. package/skills/content-defingerprinting/scripts/watermarks/UPSTREAM.json +0 -0
  111. package/skills/content-defingerprinting/scripts/watermarks/revendor.sh +0 -0
  112. package/skills/content-defingerprinting/scripts/watermarks/vendor/LICENSE.upstream +0 -0
  113. package/skills/content-defingerprinting/scripts/watermarks/vendor/clean_text.py +0 -0
  114. package/skills/content-defingerprinting/scripts/watermarks/vendor/common.py +0 -0
  115. package/skills/content-defingerprinting/scripts/watermarks/vendor/inspect_text.py +0 -0
  116. package/skills/content-defingerprinting/scripts/watermarks/vendor/score_stylometry.py +0 -0
  117. package/skills/content-defingerprinting/scripts/watermarks/vendor/text_unicode.py +0 -0
  118. package/skills/content-defingerprinting/scripts/watermarks/verify-vendor.py +0 -0
  119. package/skills/content-defingerprinting/scripts/watermarks/wm-scan.py +0 -0
  120. package/skills/internal-linking-optimizer/SKILL.md +0 -0
  121. package/skills/internal-linking-optimizer/references/source-prompt.md +0 -0
  122. package/skills/pre-publish-review/SKILL.md +0 -0
  123. package/skills/source-cited-content-builder/SKILL.md +0 -0
  124. package/skills/source-cited-content-builder/references/source-prompt.md +0 -0
  125. package/skills/source-cited-humanizer/SKILL.md +0 -0
  126. package/src/citations/adapter.js +0 -0
  127. package/src/citations/alert.js +0 -0
  128. package/src/citations/browser/chatgpt-citations.js +0 -0
  129. package/src/citations/browser/engine.js +0 -0
  130. package/src/citations/browser/gen/core/providers/_shared/rawSourcesDom.js +0 -0
  131. package/src/citations/browser/gen/core/providers/_shared/resetProviderPage.js +0 -0
  132. package/src/citations/browser/gen/core/providers/_shared/sourceUtils.js +0 -0
  133. package/src/citations/browser/gen/core/providers/ai-overview/index.js +0 -0
  134. package/src/citations/browser/gen/core/providers/ai-overview/lib/expand.js +0 -0
  135. package/src/citations/browser/gen/core/providers/ai-overview/lib/extractSources.js +0 -0
  136. package/src/citations/browser/gen/core/providers/ai-overview/lib/pageLifecycle.js +0 -0
  137. package/src/citations/browser/gen/core/providers/ai-overview/lib/session.js +0 -0
  138. package/src/citations/browser/gen/core/providers/chatgpt/index.js +0 -0
  139. package/src/citations/browser/gen/core/providers/chatgpt/lib/dismissAuthModal.js +0 -0
  140. package/src/citations/browser/gen/core/providers/chatgpt/lib/extractSources.js +0 -0
  141. package/src/citations/browser/gen/core/providers/chatgpt/lib/pageLifecycle.js +0 -0
  142. package/src/citations/browser/gen/core/providers/claude/index.js +0 -0
  143. package/src/citations/browser/gen/core/providers/claude/lib/extractSources.js +0 -0
  144. package/src/citations/browser/gen/core/providers/claude/lib/pageLifecycle.js +0 -0
  145. package/src/citations/browser/gen/core/providers/gemini/index.js +0 -0
  146. package/src/citations/browser/gen/core/providers/gemini/lib/extractSources.js +0 -0
  147. package/src/citations/browser/gen/core/providers/gemini/lib/pageLifecycle.js +0 -0
  148. package/src/citations/browser/gen/core/providers/gemini/lib/session.js +0 -0
  149. package/src/citations/browser/gen/core/providers/index.js +0 -0
  150. package/src/citations/browser/gen/core/providers/perplexity/index.js +0 -0
  151. package/src/citations/browser/gen/core/providers/perplexity/lib/extractSources.js +0 -0
  152. package/src/citations/browser/gen/core/providers/perplexity/lib/navigation.js +0 -0
  153. package/src/citations/browser/gen/lib/browser/camoufox.js +0 -0
  154. package/src/citations/browser/gen/lib/browser/display.js +0 -0
  155. package/src/citations/browser/gen/lib/browser/domOps.js +0 -0
  156. package/src/citations/browser/gen/lib/browser/humanBehavior.js +0 -0
  157. package/src/citations/browser/gen/lib/browser/navigate.js +0 -0
  158. package/src/citations/browser/gen/lib/extraction/sourceUtils.js +0 -0
  159. package/src/citations/browser/gen/lib/input/editor/clearInput.js +0 -0
  160. package/src/citations/browser/gen/lib/input/editor/findEditor.js +0 -0
  161. package/src/citations/browser/gen/lib/input/editor/promptInput.js +0 -0
  162. package/src/citations/browser/gen/lib/input/markdown/converter.js +0 -0
  163. package/src/citations/browser/gen/lib/input/markdown/toMarkdown.js +0 -0
  164. package/src/citations/browser/gen/lib/input/response/detectBotPage.js +0 -0
  165. package/src/citations/browser/gen/lib/input/response/isGenerating.js +0 -0
  166. package/src/citations/browser/gen/lib/input/response/waitForFinish.js +0 -0
  167. package/src/citations/browser/gen/lib/input/sources/findButton.js +0 -0
  168. package/src/citations/browser/gen/lib/input/sources/openPanel.js +0 -0
  169. package/src/citations/browser/gen/utils/agent-constants.js +0 -0
  170. package/src/citations/browser/providers.js +0 -0
  171. package/src/citations/browser/proxy-admission.js +0 -0
  172. package/src/citations/browser/shims/errors.js +0 -0
  173. package/src/citations/browser/shims/types.js +0 -0
  174. package/src/citations/browser/shims/utils.js +0 -0
  175. package/src/citations/contract.js +0 -0
  176. package/src/citations/egress-receipts.js +0 -0
  177. package/src/citations/evidence-integrity.js +0 -0
  178. package/src/citations/inventory.js +0 -0
  179. package/src/citations/login.js +0 -0
  180. package/src/citations/match.js +0 -0
  181. package/src/citations/panel-builder.js +0 -0
  182. package/src/citations/report.js +0 -0
  183. package/src/citations/runner.js +0 -0
  184. package/src/citations/stats.js +0 -0
  185. package/src/citations/sync.js +0 -0
  186. package/src/citations/watch.js +0 -0
  187. package/src/competitors/contract.js +0 -0
  188. package/src/competitors/domain-mix.js +0 -0
  189. package/src/context/ga4.js +0 -0
  190. package/src/context/gsc.js +0 -0
  191. package/src/context/site-profile.js +0 -0
  192. package/src/context/util.js +0 -0
  193. package/src/context-tool-registry.js +0 -0
  194. package/src/corpus/acquire.js +0 -0
  195. package/src/corpus/seeds.js +0 -0
  196. package/src/discovery/contract.js +0 -0
  197. package/src/discovery/import.js +0 -0
  198. package/src/evidence-reference.js +0 -0
  199. package/src/index-observation.js +0 -0
  200. package/src/locator.js +0 -0
  201. package/src/response-shaping.js +0 -0
  202. package/src/tool-views.js +5 -4
  203. package/src/verifier/fetch.js +0 -0
  204. package/src/verifier/index.js +0 -0
  205. package/src/verifier/observation-kind.js +0 -0
  206. package/src/verifier/parser.js +0 -0
  207. package/src/verifier/readiness.js +0 -0
  208. package/src/verifier/render-evidence.js +0 -0
  209. package/src/verifier/robots.js +0 -0
  210. package/src/verifier/transition.js +0 -0
  211. package/src/verifier/url.js +0 -0
  212. package/templates/SITE.template.md +0 -0
  213. package/templates/campaigns.v1.json +0 -0
package/cli/check.js CHANGED
File without changes
@@ -0,0 +1,39 @@
1
+ import {readFile,writeFile,mkdir,copyFile,unlink} from 'node:fs/promises';
2
+ import {constants} from 'node:fs';
3
+ import {dirname,resolve} from 'node:path';
4
+ import {loadConfig,ConfigError} from './config.js';
5
+ import {cloudConnection} from './connection.js';
6
+ import {createClient} from './client.js';
7
+ import {freezeChronology,normalizeLocalSubmissions} from '../shared/monitoring-chronology-export.js';
8
+ const help='agentlinkops chronology pull --watch-id ID [--limit 50] [--cursor TOKEN] [--all] [--out NEW_FILE] | export --pages FILE [--submissions FILE] [--out NEW_FILE]';
9
+ async function output(value,args,{cwd,out}){
10
+ const text=JSON.stringify(value,null,2)+'\n';
11
+ if(!args.out){out(text.trimEnd());return;}
12
+ if(typeof args.out!=='string'||!args.out.trim())throw new ConfigError('--out requires a file');
13
+ const path=resolve(cwd,args.out),temp=`${path}.${crypto.randomUUID()}.tmp`;
14
+ await mkdir(dirname(path),{recursive:true});
15
+ try{await writeFile(temp,text,{flag:'wx',mode:0o600});await copyFile(temp,path,constants.COPYFILE_EXCL);}finally{await unlink(temp).catch(e=>{if(e.code!=='ENOENT')throw e;});}
16
+ out(`wrote ${path}`);
17
+ }
18
+ export async function chronologyMain(args,{cwd=process.cwd(),env=process.env,fetchImpl=globalThis.fetch,out=console.log}={}){
19
+ const command=args._[1],allowed=new Set(['_','tag','watch-id','limit','cursor','all','out','pages','submissions']);
20
+ if(args._.length!==2||args.tag?.length||Object.keys(args).some(key=>!allowed.has(key)))throw new ConfigError(help);
21
+ if(command==='export'){
22
+ if(typeof args.pages!=='string'||args['watch-id']!==undefined||args.limit!==undefined||args.cursor!==undefined||args.all!==undefined)throw new ConfigError(help);
23
+ const input=JSON.parse(await readFile(resolve(cwd,args.pages),'utf8')),pages=Array.isArray(input)?input:[input];
24
+ const localItems=args.submissions?await normalizeLocalSubmissions(JSON.parse(await readFile(resolve(cwd,args.submissions),'utf8')),pages[0]?.identity):[];
25
+ await output(await freezeChronology(pages,{localItems}),args,{cwd,out});return 0;
26
+ }
27
+ if(command!=='pull'||typeof args['watch-id']!=='string'||args.pages!==undefined||args.submissions!==undefined||args.all!==undefined&&args.all!==true)throw new ConfigError(help);
28
+ const limit=Number(args.limit??50);if(!Number.isInteger(limit)||limit<1||limit>100||args.cursor===true)throw new ConfigError(help);
29
+ const config=await loadConfig({cwd}),connection=cloudConnection(config,env);
30
+ if(!connection.projectId)throw new ConfigError('Connect this project before reading hosted chronology.');
31
+ const client=createClient({...connection,fetchImpl}),pages=[];let cursor=args.cursor;
32
+ do{
33
+ const page=await client.callCommand('get_placement_chronology',{watchId:args['watch-id'],limit,...(cursor?{cursor}: {})});
34
+ if(page.identity?.projectId!==connection.projectId||page.identity?.watchId!==args['watch-id']||connection.workspaceId&&page.identity?.workspaceId!==connection.workspaceId)throw new ConfigError('Chronology identity does not match the connected project and selected watch.');
35
+ pages.push(page);cursor=page.nextCursor;
36
+ if(pages.length===100&&args.all&&cursor)throw new ConfigError('Chronology exceeds the 100-page read bound. Pull explicit pages and export their qualified partial dataset.');
37
+ }while(args.all&&cursor);
38
+ await output(args.all?await freezeChronology(pages):pages[0],args,{cwd,out});return 0;
39
+ }
package/cli/citation.js CHANGED
File without changes
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.11';
8
+ export const CLI_VERSION = '0.6.13';
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;
File without changes
package/cli/commands.js CHANGED
File without changes
package/cli/config.js CHANGED
File without changes
package/cli/connection.js CHANGED
File without changes
File without changes
package/cli/context.js CHANGED
File without changes
package/cli/delivery.js CHANGED
File without changes
package/cli/disavow.js CHANGED
File without changes
package/cli/doctor.js CHANGED
File without changes
package/cli/env.js CHANGED
File without changes
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 the ledger contract, not a status column: an entry is earned when its
10
- // latest OBSERVATION says present. A project with no observation file has earned nothing and
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, latestByEntry } from './mirror.js';
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 latest = latestByEntry(rows);
26
- let earned = 0, observed = 0;
27
- for (const [id, row] of latest) {
28
- if (!ledger.entries.some(entry => entry.id === id)) continue; // observations from another ledger's ids
29
- observed++;
30
- if (row.state === 'present') earned++;
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: ledger.entries.filter(entry => entry.intent !== 'retired').length - observed,
39
- last_observation: lastCheck,
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: 1, projects: rows, totals,
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
  }
File without changes
package/cli/import.js CHANGED
File without changes
File without changes
File without changes
package/cli/ledger.js CHANGED
File without changes
package/cli/lifecycle.js CHANGED
File without changes
package/cli/linktrail.mjs CHANGED
File without changes
File without changes
package/cli/main.js CHANGED
@@ -1,3 +1,5 @@
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';
3
5
  import {admissionMain} from './admission.js';
@@ -52,6 +54,8 @@ const USAGE = `agentlinkops — a backlink ledger that lives in your repository
52
54
  [--mode local|hosted|external] read-only setup plan; no account required
53
55
  agentlinkops add --source URL --target URL [--intent wanted|expected] [--scope …]
54
56
  [--anchor TEXT] [--rel a,b] [--ref TEXT] [--tag t --tag t] [--note TEXT]
57
+ agentlinkops chronology pull --watch-id ID [--limit N] [--cursor TOKEN] [--all] [--out FILE]
58
+ agentlinkops chronology export --pages FILE [--submissions FILE] [--out FILE]
55
59
  agentlinkops index pull --watch-id ID [--limit N] [--before ID]
56
60
  agentlinkops lifecycle get|update|clear|renew|report
57
61
  agentlinkops admission list|add|update|remove|evaluate|reevaluate|receipt --project ID
@@ -63,7 +67,7 @@ const USAGE = `agentlinkops — a backlink ledger that lives in your repository
63
67
  agentlinkops import FILE --target DOMAIN [--from SUPPLIER] [--map source=COL,target=COL]
64
68
  [--exact-url] [--no-subdomains] [--generated-at ISO] [--json]
65
69
  agentlinkops adopt CRM.sqlite [--scope exact|domain] [--write]
66
- agentlinkops fleet --project NAME=LEDGER [--project …] [--observations-of NAME=FILE] [--json]
70
+ agentlinkops fleet --project NAME=LEDGER [--project …] [--observations-of NAME=FILE] [--state-of NAME=FILE] [--json]
67
71
  agentlinkops platforms [HOST] [--observations FILE]… [--benchmark FILE]… [--seed FILE]…
68
72
  [--for-candidates FILE] [--json]
69
73
  agentlinkops context COMMAND… first-party search context and site profile
@@ -100,7 +104,7 @@ const USAGE = `agentlinkops — a backlink ledger that lives in your repository
100
104
  agentlinkops adopt-result FILE [--intent wanted|expected] save result to local ledger
101
105
  agentlinkops receipt add --file CLAIM.json project.site declares public site hosts
102
106
  agentlinkops receipt history [--performance FILE.json] claims, history and optional dated context
103
- agentlinkops status [--json]
107
+ agentlinkops status [--all] [--json]
104
108
  agentlinkops diff [--json]
105
109
  agentlinkops report [--out FILE] [--title T] [--brand B] [--as-of ISO]
106
110
  [--include-notes] [--include-retired] [--digest-only] [--json]
@@ -122,7 +126,7 @@ evidence · 2 usage, configuration or ledger error. Unknown is nonfailing by def
122
126
  Names: \`linktrail\` still runs this CLI, LINKTRAIL_* variables are still read, and an existing
123
127
  .linktrail/ directory is still used, each with a one-line notice, until the DP-0029 cutover.`;
124
128
 
125
- const parseArgs = argv => parseArguments(argv,["tag", "project", "observations-of", "observations", "benchmark", "seed"],{tag:[]});
129
+ const parseArgs = argv => parseArguments(argv,["tag", "project", "observations-of", "state-of", "observations", "benchmark", "seed"],{tag:[]});
126
130
 
127
131
  const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
128
132
 
@@ -154,6 +158,7 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
154
158
  if (command === 'lifecycle') return await lifecycleMain(args,{cwd,env,fetchImpl,out,err});
155
159
  if (command === 'admission') { const config=await loadConfig({cwd}); return await admissionMain(argv.slice(1),{client:createClient({...cloudConnection(config,env),fetchImpl}),out,err}); }
156
160
  if (command === 'disavow') return await disavowMain(args,{cwd,env,fetchImpl,out});
161
+ if (command === 'chronology') return await chronologyMain(args,{cwd,env,fetchImpl,out});
157
162
  if (command === 'index') return await indexMain(args,{cwd,env,fetchImpl,out});
158
163
  if (command === 'setup') return await setupPlanMain(argv, { cwd, out });
159
164
  if (command === 'tools' || command === 'describe' || command === 'schema' || command === 'call') return await commandsMain(argv, { cwd, out, err, env, fetchImpl });
@@ -306,7 +311,15 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
306
311
  if (!name || !path) throw new ConfigError(`--observations-of expects NAME=FILE, got "${value}"`);
307
312
  observationsOf[name] = path;
308
313
  }
309
- const summary = await fleetSummary(pairs, { observationsOf });
314
+ const statesOf = {};
315
+ for (const value of [].concat(args['state-of'] ?? [])) {
316
+ const separator = String(value).indexOf('=');
317
+ const name = String(value).slice(0, separator), path = String(value).slice(separator + 1);
318
+ if (separator < 1 || !path || !pairs.some(project => project.name === name)) throw new ConfigError('--state-of expects a declared NAME=FILE');
319
+ if (Object.hasOwn(statesOf, name)) throw new ConfigError(`Duplicate state input for ${name}`);
320
+ statesOf[name] = path;
321
+ }
322
+ const summary = await fleetSummary(pairs, { observationsOf, statesOf });
310
323
  if (args.json) { out(JSON.stringify(summary, null, 1)); return 0; }
311
324
  return renderFleet(summary, out);
312
325
  }
@@ -392,6 +405,7 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
392
405
  // so it is unchanged by a cosmetic edit to the template and changes the moment a figure does.
393
406
  const asOf = args['as-of'] === true ? null : (args['as-of'] ?? null);
394
407
  const dataset = freezeDataset(ledger.entries, observations.rows, {
408
+ activityState: await readState(config.paths.state),
395
409
  asOf: asOf ?? new Date().toISOString().slice(0, 10),
396
410
  title: args.title === true ? 'Backlink report' : (args.title ?? 'Backlink report'),
397
411
  includeNotes: args['include-notes'] === true,
@@ -500,24 +514,29 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
500
514
  }
501
515
 
502
516
  if (command === 'status' || command === 'diff') {
503
- const found = command === 'status' ? disagreements(ledger.entries, observations.rows) : transitions(observations.rows);
504
- if (args.json) { out(JSON.stringify(found, null, 1)); return 0; }
517
+ const activityState = command === 'status' ? await readState(config.paths.state) : null;
518
+ const found = command === 'status' ? disagreements(ledger.entries, observations.rows, { state: activityState }) : transitions(observations.rows);
519
+ 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;
520
+ if (args.json) { out(JSON.stringify(activity ? { disagreements: found, placements: activity } : found, null, 1)); return 0; }
505
521
  reportProblems(ledger.problems, 'ledger line(s)', out);
506
522
  if (command === 'diff') {
507
523
  if (!found.length) out('no state changes recorded');
508
524
  for (const change of found.slice(0, 100)) out(` ${change.at.slice(0, 10)} ${change.from} -> ${change.to} ${change.id}`);
509
525
  return 0;
510
526
  }
511
- const groups = { appeared: 'appeared', lost: 'LOST', suspected_missing: 'suspected missing', cannot_say: 'cannot say', never_checked: 'never checked' };
527
+ const groups = { appeared: 'appeared', lost: 'confirmed missing', suspected_missing: 'suspected missing', cannot_say: 'cannot say', never_checked: 'never checked' };
512
528
  for (const [kind, label] of Object.entries(groups)) {
513
529
  const rows = found.filter(item => item.kind === kind);
514
530
  if (!rows.length) continue;
515
531
  out(`\n${label} (${rows.length})`);
516
532
  for (const item of rows.slice(0, 50)) {
517
533
  out(` ${item.entry.id} ${item.entry.source}${item.row ? ` [${item.row.reason}]` : ''}`);
534
+ out(` Latest attempt: ${item.activity?.latest_attempt?.checked_at ?? 'not retained'} (${item.activity?.latest_attempt?.reason ?? 'unknown'})`);
535
+ 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
536
  }
519
537
  }
520
538
  if (!found.length) out('intent and observations agree on every entry');
539
+ 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
540
  return 0;
522
541
  }
523
542
 
@@ -590,7 +609,7 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
590
609
  if (ledger.problems.length) { reportProblems(ledger.problems, 'ledger line(s)', err); return 2; }
591
610
  const state = await readState(config.paths.state);
592
611
  const selected = selectEntries(ledger.entries, { filter: args.filter === true ? null : args.filter });
593
- const entries = args.all ? selected : dueEntries(selected, lastCheckedMap(state));
612
+ const entries = args.all ? selected : dueEntries(selected, lastCheckedMap(state, selected));
594
613
  if (!entries.length) { (args.json ? err : out)(`nothing due (${plural(selected.length, 'entry', 'entries')} in scope; pass --all to recheck)`); return 0; }
595
614
  if (!args.json) err(`checking ${plural(entries.length, 'entry', 'entries')}…`);
596
615
  const rows = await runCheck(entries, {
@@ -625,6 +644,13 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
625
644
  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
645
  return 2;
627
646
  }
647
+ if (command === 'sync' && error instanceof CloudError && error.status === 0 &&
648
+ ['CLOUD_TIMEOUT', 'CLOUD_NETWORK_ERROR'].includes(error.code)) {
649
+ // A local transport failure cannot establish whether a write committed.
650
+ // Print trusted recovery guidance, never attached exception/server text.
651
+ 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.`);
652
+ return 2;
653
+ }
628
654
  err(`agentlinkops: ${error?.message ?? error}`);
629
655
  return 2;
630
656
  } 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.11'});
65
+ const client = new Client({name: 'agentlinkops-cli', version: '0.6.13'});
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.11');
74
+ headers.set('User-Agent', 'agentlinkops-cli/0.6.13');
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/cli/mirror.js CHANGED
File without changes
package/cli/mix.js CHANGED
File without changes
File without changes
File without changes
package/cli/oauth-flow.js CHANGED
File without changes
@@ -0,0 +1,106 @@
1
+ // One identity-bound projection for local activity and retained observations. A change-only
2
+ // mirror is not a complete probe log, so local absence remains provisional. Hosted watch
3
+ // state is carried independently from the raw check; this module never confirms loss.
4
+ import { mirrorEvidenceReference } from '../src/evidence-reference.js';
5
+
6
+ const conclusive = new Set(['present', 'absent', 'source_unavailable']);
7
+ const watchStates = new Set(['present', 'suspected_missing', 'confirmed_missing', 'source_unavailable']);
8
+ export const validTime = value => typeof value === 'string' && Number.isFinite(Date.parse(value));
9
+
10
+ export function placementIdentity(value) {
11
+ const record = value?.result ?? value;
12
+ const source = record?.sourceUrl ?? record?.source_url ?? record?.source;
13
+ const target = record?.targetUrl ?? record?.target_url ?? record?.target;
14
+ const scope = record?.targetScope ?? record?.target_scope ?? record?.scope ?? 'exact';
15
+ return typeof source === 'string' && typeof target === 'string'
16
+ ? JSON.stringify([source, target, scope]) : null;
17
+ }
18
+
19
+ export function attemptSummary(row, { retained = false } = {}) {
20
+ if (!validTime(row?.checked_at)) return null;
21
+ const cloud = row.source === 'cloud';
22
+ const rawState = cloud && conclusive.has(row.result?.checkState) ? row.result.checkState
23
+ : ['suspected_missing', 'confirmed_missing'].includes(row.state) ? 'absent' : row.state;
24
+ const method = row.result?.evidence?.method ?? null;
25
+ return {
26
+ checked_at: row.checked_at, placement: placementIdentity(row),
27
+ state: rawState, reason: row.reason ?? null, complete: row.complete === true,
28
+ origin: row.source ?? 'local',
29
+ watch_state: cloud && watchStates.has(row.result?.watchState) ? row.result.watchState
30
+ : ['suspected_missing', 'confirmed_missing'].includes(row.state) ? row.state : null,
31
+ link_signature: row.result?.linkSignature ?? null,
32
+ method, checker_version: row.checker_version ?? row.result?.evidence?.checkerVersion ?? null,
33
+ content_hash: row.result?.evidence?.sha256 ?? null,
34
+ evidence_reference: retained ? mirrorEvidenceReference({ entryId: row.id, checkedAt: row.checked_at, origin: row.source ?? 'local' }) : null,
35
+ retained,
36
+ };
37
+ }
38
+
39
+ export const isConclusive = attempt => attempt?.complete === true && conclusive.has(attempt.state);
40
+ export const isLinkVerification = attempt => isConclusive(attempt) && ['present', 'absent'].includes(attempt.state);
41
+
42
+ // Equal timestamps never qualify another probe. Stable tie-breaking makes frozen exports
43
+ // independent of input ordering; an activity summary cannot replace a richer retained receipt.
44
+ const compareText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
45
+ function latest(items) {
46
+ return items.filter(item => item && validTime(item.checked_at)).sort((a, b) =>
47
+ Date.parse(b.checked_at) - Date.parse(a.checked_at)
48
+ || Number(b.retained === true) - Number(a.retained === true)
49
+ || compareText(JSON.stringify(a), JSON.stringify(b)))[0] ?? null;
50
+ }
51
+
52
+ function activityAttempt(record, identity) {
53
+ if (record?.last_placement !== identity) return null;
54
+ if (record.last_attempt?.placement === identity && record.last_attempt.checked_at === record.last_checked
55
+ && validTime(record.last_attempt.checked_at)) return record.last_attempt;
56
+ // Legacy activity can prove its last conclusive time, but cannot invent a method/hash or
57
+ // reconstruct the overwritten successful check after a later unknown attempt.
58
+ if (!validTime(record.last_checked)) return null;
59
+ return { checked_at: record.last_checked, placement: identity, state: record.last_state ?? 'unknown',
60
+ reason: record.last_reason ?? null, complete: record.last_complete === true, origin: 'local',
61
+ watch_state: null, link_signature: record.last_signature ?? null, method: null,
62
+ checker_version: null, content_hash: null, evidence_reference: null, retained: false };
63
+ }
64
+
65
+ export function projectPlacement(entry, observations, { state = null } = {}) {
66
+ const identity = placementIdentity(entry);
67
+ const history = identity ? observations.filter(row => row.id === entry.id && placementIdentity(row) === identity && validTime(row.checked_at)) : [];
68
+ const attempts = history.map(row => attemptSummary(row, { retained: true }));
69
+ const record = state?.entries?.[entry.id];
70
+ const locate = item => item?.retained === true && !attempts.some(receipt => receipt.checked_at === item.checked_at
71
+ && receipt.origin === item.origin && receipt.content_hash === item.content_hash)
72
+ ? { ...item, retained: false, evidence_reference: null } : item;
73
+ const activity = locate(identity ? activityAttempt(record, identity) : null);
74
+ const saved = key => locate(identity && record?.last_placement === identity && record[key]?.placement === identity ? record[key] : null);
75
+ const latestAttempt = latest([...attempts, activity]);
76
+ const savedSuccess = saved('last_successful_observation'), savedLink = saved('last_link_verification');
77
+ const lastSuccessful = latest([...attempts.filter(isConclusive), isConclusive(savedSuccess) ? savedSuccess : null, isConclusive(activity) ? activity : null]);
78
+ const lastLink = latest([...attempts.filter(isLinkVerification), isLinkVerification(savedLink) ? savedLink : null, isLinkVerification(activity) ? activity : null]);
79
+ const retained = latest(attempts);
80
+ const row = retained ? history.filter(item => JSON.stringify(attemptSummary(item, { retained: true })) === JSON.stringify(retained))
81
+ .sort((a, b) => compareText(JSON.stringify(a), JSON.stringify(b)))[0] ?? null : null;
82
+ const firstDates = attempts.filter(item => isConclusive(item) && item.state === 'present').map(item => item.checked_at);
83
+ const first = saved('first_present_observation');
84
+ if (isConclusive(first) && first.state === 'present' && validTime(first.checked_at)) firstDates.push(first.checked_at);
85
+ const firstPresent = firstDates.sort((a, b) => Date.parse(a) - Date.parse(b))[0] ?? null;
86
+ const current = latestAttempt?.watch_state ?? lastSuccessful?.watch_state ?? (lastSuccessful?.state === 'absent' ? 'suspected_missing'
87
+ : lastSuccessful?.state ?? (latestAttempt ? 'unknown' : 'unchecked'));
88
+ return {
89
+ entry, row, placement: identity, current_state: current,
90
+ uncertain: !!latestAttempt && !isConclusive(latestAttempt),
91
+ latest_attempt: latestAttempt, last_successful_observation: lastSuccessful,
92
+ last_link_verification: lastLink, first_present: firstPresent,
93
+ evidence_observed_at: retained?.checked_at ?? null,
94
+ activity_available: activity !== null,
95
+ identity_status: history.length || activity ? 'matched' : observations.some(item => item.id === entry.id) || record ? 'not_matched' : 'not_observed',
96
+ };
97
+ }
98
+
99
+ export function projectPlacements(entries, observations, options = {}) {
100
+ const byId = new Map();
101
+ for (const row of observations) {
102
+ if (!byId.has(row.id)) byId.set(row.id, []);
103
+ byId.get(row.id).push(row);
104
+ }
105
+ return entries.map(entry => projectPlacement(entry, byId.get(entry.id) ?? [], options));
106
+ }
package/cli/platforms.js CHANGED
File without changes
File without changes
package/cli/receipts.js CHANGED
File without changes