agentlinkops 0.6.2 → 0.6.3

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.
@@ -0,0 +1,25 @@
1
+ # Sourced linkable asset
2
+
3
+ ID: `sourced-linkable-asset` | Version: `1.0.0` | Goal: `build-content`
4
+
5
+ Use this recipe for a reference, guide, comparison, dataset, calculator or template that helps a named reader. Modes: local, hosted, external. Required inputs: the site/context brief, reader task, candidate asset or existing page, source material and intended output destination.
6
+
7
+ ## Capabilities and ownership
8
+
9
+ The agent can write a sourced draft without an AgentLinkOps account. Fetching sources and editing a website depend on the user's available tools. Hosted backlink research is optional and requires actual access. The user's site or content system owns the asset; local CRM storage is optional. For page work, preserve the site's rendering and routing conventions and inspect nearby pages before editing. This recipe contains the writing and review sequence; continue below without loading another asset router.
10
+
11
+ ## Steps and checkpoints
12
+
13
+ 1. Read the site's content instructions and inspect existing pages for the same reader task. Define the useful addition before creating another URL. Start with the site's content skill when present, then use the packaged passes below. If a required review resource is unavailable, identify it before marking the asset reviewed.
14
+ 2. Write through [source-cited-content-builder](../../skills/source-cited-content-builder/SKILL.md). Preserve source title, publisher, publication date when available, URL and the evidence supporting each factual claim. Mark original calculations and assumptions. Do not manufacture experience or results.
15
+ 3. Run [source-cited-humanizer](../../skills/source-cited-humanizer/SKILL.md) as a separate editorial pass, then [content-defingerprinting](../../skills/content-defingerprinting/SKILL.md) on the final authored text. Save the actual checks and findings.
16
+ 4. Apply [internal-linking-optimizer](../../skills/internal-linking-optimizer/SKILL.md) to the chosen format. For owned pages, verify destinations and inbound discovery at release. A document or email handoff needs relevant links without webpage quotas. Use the site's image workflow when the asset calls for images.
17
+ 5. Run [pre-publish-review](../../skills/pre-publish-review/SKILL.md). Checkpoint the draft revision, intended URL, source records, review receipts and unresolved findings. Read back the artifact from its chosen destination.
18
+
19
+ ## Recovery and completion
20
+
21
+ Missing sources leave claims unverified; remove or qualify them before review. Missing review tools leave the corresponding pass incomplete. Preserve an existing page and customer edits when resuming. A draft, green build or copied command does not prove publication. If release is authorized separately, verify the actual public content after release and attach that receipt.
22
+
23
+ Complete this recipe with a reviewed draft and explicit publication state, or an unfinished draft with the precise remaining check. Continue to [campaign handoff](qualified-campaign-handoff.md) only with an honest asset state; a pitch must not describe an unpublished asset as live.
24
+
25
+ Tested client/tool versions: none recorded. Status: authored; end-to-end execution and vendor connections untested. [Catalog](catalog.json) owns machine-readable status and source references.
@@ -0,0 +1,16 @@
1
+ import {z} from 'zod';
2
+ const id=z.string().min(1).max(200),kind=z.enum(['reject_domain','reject_url_pattern','require_pattern','domain_cap']);
3
+ export const admissionRuleInput=z.object({projectId:id,kind,value:z.string().min(1).max(300),enabled:z.boolean().optional(),note:z.string().max(1000).optional()}).strict();
4
+ export const admissionUpdateInput=z.object({projectId:id,ruleId:id,revision:z.number().int().positive(),value:z.string().min(1).max(300).optional(),enabled:z.boolean().optional(),note:z.string().max(1000).optional()}).strict();
5
+ export const admissionDeleteInput=z.object({projectId:id,ruleId:id,revision:z.number().int().positive()}).strict();
6
+ export const admissionListInput=z.object({projectId:id,limit:z.number().int().min(1).max(100).optional(),cursor:z.string().max(4096).optional()}).strict();
7
+ export const admissionEvaluateInput=z.object({projectId:id,urls:z.array(z.string().max(2048)).min(1).max(200),context:z.enum(['point','import']).optional()}).strict();
8
+ export const admissionRecordInput=z.object({projectId:id,operationId:id,context:z.enum(['import','candidate','candidate_batch','serve']),subjects:z.array(z.object({key:id,url:z.string().max(2048)}).strict()).min(1).max(1000)}).strict();
9
+ export const admissionRule=z.object({id,workspace_id:id,project_id:id,kind,value:z.string(),source:z.literal('manual'),enabled:z.boolean(),note:z.string(),revision:z.number().int().positive(),created_by:id,created_at:z.string(),updated_at:z.string()}).strict();
10
+ export const admissionDecision=z.object({index:z.number().int().nonnegative(),url:z.string(),normalized_url:z.string().nullable(),host:z.string().nullable(),decision:z.enum(['admitted','rejected']),reason:z.string(),rule_kind:z.string().nullable(),rule_id:id.nullable(),rule_value:z.string().nullable(),cap:z.object({limit:z.number().int().positive(),used:z.number().int().nonnegative()}).strict().nullable()}).strict();
11
+ export const admissionSummary=z.object({total:z.number().int(),admitted:z.number().int(),rejected:z.number().int(),by_rule:z.record(z.string(),z.number().int())}).strict();
12
+ export const admissionListOutput=z.object({rules:z.array(admissionRule),revision:z.number().int().nonnegative(),managed_disavow_count:z.number().int().nonnegative(),cursor:z.string().nullable()}).strict();
13
+ export const admissionMutationOutput=z.object({rule:admissionRule,revision:z.number().int().nonnegative(),created:z.boolean()}).strict();
14
+ export const admissionDeleteOutput=z.object({deleted:z.literal(true),rule_id:id,revision:z.number().int().nonnegative()}).strict();
15
+ export const admissionEvaluateOutput=z.object({decisions:z.array(admissionDecision),summary:admissionSummary,policy_revision:z.number().int().nonnegative(),disavow_revision:z.number().int().nonnegative(),context:z.enum(['point','import']),recorded:z.literal(false)}).strict();
16
+ export const admissionRecordedOutput=z.object({operation_id:id,project_id:id,context:z.enum(['import','candidate','candidate_batch','serve']),decisions:z.array(admissionDecision.extend({subject_key:id})),summary:admissionSummary,policy_revision:z.number().int().nonnegative(),disavow_revision:z.number().int().nonnegative(),replayed:z.boolean()}).strict();
@@ -0,0 +1,8 @@
1
+ import {z} from 'zod';
2
+ import {admissionDecision,admissionSummary,admissionRecordedOutput} from './admission-contract.js';
3
+ const id=z.string().min(1).max(200);
4
+ export const admissionReevaluateInput=z.object({projectId:id,source:z.enum(['watches','candidates']),limit:z.number().int().min(1).max(100).optional(),cursor:z.string().max(1024).optional()}).strict();
5
+ export const admissionReevaluateOutput=z.object({project_id:id,source:z.enum(['watches','candidates']),rows:z.array(z.object({subject_id:id,decision:admissionDecision}).strict()),page_summary:admissionSummary,policy_revision:z.number().int().nonnegative(),disavow_revision:z.number().int().nonnegative(),next_cursor:z.string().nullable(),has_more:z.boolean(),report_only:z.literal(true),snapshot:z.literal(false),domain_cap_applied:z.literal(false)}).strict();
6
+ export const admissionReceiptInput=z.object({projectId:id,operationId:id}).strict();
7
+ export const admissionReceiptOutput=admissionRecordedOutput;
8
+ export const admissionReportResponses={reevaluate_admission:admissionReevaluateOutput.passthrough(),get_admission_decisions:admissionReceiptOutput.passthrough()};
@@ -0,0 +1,18 @@
1
+ import {z} from 'zod';
2
+ export const DISAVOW_LIMITS=Object.freeze({projectRules:500,ruleBytes:3072,importRules:100,importBytes:163840,comments:8,commentChars:120});
3
+ const id=z.string().min(1).max(200),revision=z.number().int().positive();
4
+ const comments=z.array(z.string().max(120).startsWith('#')).max(8);
5
+ export const disavowListInput=z.object({projectId:id,status:z.enum(['proposed','active','rejected']).optional(),limit:z.number().int().min(1).max(100).optional(),cursor:z.string().max(4096).optional()}).strict();
6
+ export const disavowHistoryInput=z.object({projectId:id,limit:z.number().int().min(1).max(100).optional(),cursor:z.string().max(4096).optional()}).strict();
7
+ export const disavowProposeInput=z.object({projectId:id,kind:z.enum(['domain','url']),value:z.string().min(1).max(2048),comments:comments.optional(),source:z.enum(['manual','agent_proposal']).optional(),status:z.enum(['proposed','active']).optional()}).strict();
8
+ export const disavowImportInput=z.object({projectId:id,text:z.string().max(163840)}).strict();
9
+ export const disavowReviewInput=z.object({projectId:id,ruleId:id,revision,decision:z.enum(['approve','reject'])}).strict();
10
+ export const disavowDeleteInput=z.object({projectId:id,ruleId:id,revision}).strict();
11
+ export const disavowProjectInput=z.object({projectId:id}).strict();
12
+ export const disavowRule=z.object({id,workspace_id:id,project_id:id,kind:z.enum(['domain','url']),value:z.string(),identity:z.string(),source:z.enum(['manual','import','agent_proposal']),status:z.enum(['proposed','active','rejected']),comments,revision,created_by:id,approved_by:id.nullable(),created_at:z.string(),updated_at:z.string()}).strict();
13
+ export const disavowListOutput=z.object({rules:z.array(disavowRule),revision:z.number().int().nonnegative(),cursor:z.string().nullable()}).strict();
14
+ export const disavowMutationOutput=z.object({rules:z.array(disavowRule),created:z.number().int().nonnegative(),unchanged:z.number().int().nonnegative(),revision:z.number().int().nonnegative()}).strict();
15
+ export const disavowDeleteOutput=z.object({deleted:z.literal(true),rule_id:id,revision:z.number().int().nonnegative()}).strict();
16
+ export const disavowExportReceipt=z.object({id,workspace_id:id,project_id:id,format_version:z.literal(1),sha256:z.string().regex(/^[a-f0-9]{64}$/),stored_active_count:z.number().int().nonnegative(),effective_count:z.number().int().nonnegative(),byte_length:z.number().int().nonnegative(),collection_revision:z.number().int().nonnegative(),source_revisions:z.array(z.object({id,revision}).strict()),created_by:id,created_at:z.string()}).strict();
17
+ export const disavowExportOutput=z.object({filename:z.literal('disavow.txt'),content_type:z.literal('text/plain; charset=utf-8'),text:z.string(),receipt:disavowExportReceipt}).strict();
18
+ export const disavowHistoryOutput=z.object({receipts:z.array(disavowExportReceipt),cursor:z.string().nullable()}).strict();
@@ -0,0 +1,22 @@
1
+ import {z} from 'zod';
2
+ export const LIFECYCLE_LIMITS=Object.freeze({costMinor:1_000_000_000_000,reportRows:1000,renewalRows:100,eventsPerWatch:100});
3
+ // Supported ISO 4217 subset; unsupported currencies are refused, never guessed or converted.
4
+ export const LIFECYCLE_CURRENCIES=Object.freeze(['USD','EUR','GBP','JPY','CAD','AUD','NZD','CHF','CNY','HKD','SGD','INR','KRW','BRL','MXN','SEK','NOK','DKK','PLN','CZK','HUF','ZAR','AED','SAR','ILS','THB','TRY','IDR','MYR','PHP','VND','KWD','BHD','OMR']);
5
+ export function validCalendarDate(value){return typeof value==='string'&&/^(?!0000)\d{4}-\d{2}-\d{2}$/.test(value)&&Number.isFinite(Date.parse(value+'T00:00:00Z'))&&new Date(value+'T00:00:00Z').toISOString().slice(0,10)===value;}
6
+ export const lifecycleDate=z.string().refine(validCalendarDate,'Expected a real YYYY-MM-DD calendar date');
7
+ const id=z.string().min(1).max(200),clean=max=>z.string().max(max).refine(value=>!/[\u0000-\u001f\u007f]/.test(value),'Control characters are not allowed');
8
+ export const lifecycleDeal=z.object({costMinor:z.number().int().min(0).max(LIFECYCLE_LIMITS.costMinor).nullable(),currency:z.enum(LIFECYCLE_CURRENCIES).nullable(),acquiredOn:lifecycleDate.nullable(),expiresOn:lifecycleDate.nullable(),renewalWindowDays:z.number().int().min(1).max(365).nullable(),contactRef:z.string().regex(/^[A-Za-z][A-Za-z0-9_-]{0,119}$/).nullable(),contactLabel:clean(120).refine(v=>!/@|https?:\/\/|\+?\d[\d\s().-]{7,}\d/.test(v),'Use a display label, not contact details').nullable(),dealNote:clean(1000).nullable()}).strict().refine(d=>(d.costMinor===null)===(d.currency===null),'Cost and currency travel together').refine(d=>d.contactLabel===null||d.contactRef!==null,'A contact label needs an opaque contact reference').refine(d=>!d.acquiredOn||!d.expiresOn||d.acquiredOn<=d.expiresOn,'Expiry cannot precede the supplied acquisition date');
9
+ export const lifecyclePatch=z.object(lifecycleDeal.shape).partial().strict();
10
+ export const lifecycleUpdateInput=z.object({projectId:id,watchId:id,revision:z.number().int().nonnegative(),deal:lifecyclePatch}).strict().refine(v=>Object.keys(v.deal).length>0,'A metadata patch is required');
11
+ export const lifecycleGetInput=z.object({projectId:id,watchId:id}).strict();
12
+ export const lifecycleReportInput=z.object({projectId:id,from:lifecycleDate.optional(),to:lifecycleDate.optional()}).strict().refine(v=>!v.from||!v.to||v.from<=v.to,'Date range is reversed');
13
+ export const lifecycleRenewInput=z.object({projectId:id,limit:z.number().int().min(1).max(LIFECYCLE_LIMITS.renewalRows).default(100)}).strict();
14
+ export const lifecycleOutput=z.object({watchId:id,projectId:id,revision:z.number().int().nonnegative(),deal:lifecycleDeal,createdAt:z.string().nullable(),updatedAt:z.string().nullable()}).strict();
15
+ export function emptyDeal(){return {costMinor:null,currency:null,acquiredOn:null,expiresOn:null,renewalWindowDays:null,contactRef:null,contactLabel:null,dealNote:null};}
16
+ export function mergeDeal(previous,patch){
17
+ if(('costMinor' in patch)!==('currency' in patch))throw new Error('Set or clear costMinor and currency together');
18
+ return lifecycleDeal.parse({...previous,...patch});
19
+ }
20
+
21
+ export const lifecycleRenewOutput=z.object({emitted:z.number().int().nonnegative(),evaluated:z.number().int().nonnegative(),more:z.boolean(),asOf:lifecycleDate,delivery:z.literal('retained_only')}).strict();
22
+ export const lifecycleReportOutput=z.object({projectId:id,periodBasis:z.literal('customer_supplied_acquired_on'),buckets:z.array(z.object({website:z.string(),state:z.string(),period:z.string(),currency:z.enum(LIFECYCLE_CURRENCIES),placements:z.number().int().nonnegative(),costMinor:z.number().int().nonnegative(),confirmedMissingCostMinor:z.number().int().nonnegative(),lostBeforeExpiryCostMinor:z.number().int().nonnegative()}).strict()),noCost:z.number().int().nonnegative(),excludedUndated:z.number().int().nonnegative(),scope:z.string()}).strict();
@@ -0,0 +1,24 @@
1
+ import {LIFECYCLE_LIMITS} from './lifecycle-contract.js';
2
+ // SIX ISO 4217 List One, published2026-09-17, retrieved2026-09-20.
3
+ // https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/lists/list-one.xml
4
+ export const CURRENCY_MINOR_DIGITS=Object.freeze({"AED": 2, "AUD": 2, "BHD": 3, "BRL": 2, "CAD": 2, "CHF": 2, "CNY": 2, "CZK": 2, "DKK": 2, "EUR": 2, "GBP": 2, "HKD": 2, "HUF": 2, "IDR": 2, "ILS": 2, "INR": 2, "JPY": 0, "KRW": 0, "KWD": 3, "MXN": 2, "MYR": 2, "NOK": 2, "NZD": 2, "OMR": 3, "PHP": 2, "PLN": 2, "SAR": 2, "SEK": 2, "SGD": 2, "THB": 2, "TRY": 2, "USD": 2, "VND": 0, "ZAR": 2});
5
+ export function decimalCostToMinor(amount,currency){
6
+ const scale=CURRENCY_MINOR_DIGITS[currency];
7
+ if(scale===undefined||typeof amount!=='string'||amount.length>40||!/^(0|[1-9][0-9]*)(\.[0-9]+)?$/.test(amount))throw new Error('Cost requires an unsigned decimal string and supported currency');
8
+ const [whole,fraction='']=amount.split('.');
9
+ if(fraction.slice(scale).replaceAll('0',''))throw new Error('Cost has precision smaller than the currency minor unit');
10
+ const minor=BigInt(whole)*10n**BigInt(scale)+BigInt(fraction.slice(0,scale).padEnd(scale,'0')||'0');
11
+ if(minor>BigInt(LIFECYCLE_LIMITS.costMinor))throw new Error('Cost exceeds the lifecycle amount limit');
12
+ return Number(minor);
13
+ }
14
+ export function minorCostToDecimal(amount,currency){
15
+ const scale=CURRENCY_MINOR_DIGITS[currency];
16
+ if(scale===undefined||!Number.isSafeInteger(amount)||amount<0||amount>LIFECYCLE_LIMITS.costMinor)throw new Error('Invalid minor-unit cost');
17
+ const digits=String(amount).padStart(scale+1,'0');return scale?digits.slice(0,-scale)+'.'+digits.slice(-scale):digits;
18
+ }
19
+ export function legacyCostPatch(input){
20
+ const {cost_amount,cost_currency,...deal}=input;
21
+ if('costMinor' in deal||'currency' in deal)throw new Error('Do not combine legacy and minor-unit cost fields');
22
+ if(cost_amount===null&&cost_currency===null)return {...deal,costMinor:null,currency:null};
23
+ return {...deal,costMinor:decimalCostToMinor(cost_amount,cost_currency),currency:cost_currency};
24
+ }
@@ -1,3 +1,4 @@
1
+ import {lifecycleOutput} from './lifecycle-contract.js';
1
2
  import {searchConnectionsResponse,searchRowsResponse,searchSyncResponse,searchPropertiesResponse,searchStartResponse,searchConnection,searchImportResponse} from './search-connection-contract.js';
2
3
  import {z} from 'zod';
3
4
  export const objectResponse=z.looseObject({});
@@ -29,6 +30,7 @@ export const candidateVerificationBatchResponse=z.looseObject({
29
30
  replayed:z.boolean().optional(),
30
31
  });
31
32
  export const candidateMonitoringResponse=z.looseObject({
33
+ lifecycle:lifecycleOutput.optional(),
32
34
  watch:z.looseObject({id:z.string(),source_url:z.string(),target_url:z.string(),status:z.enum(['active','paused']),observation_state:objectResponse}),verification_job_id:z.string(),candidate_id:z.string(),run_id:z.string(),
33
35
  local_reference:z.string().nullable(),replayed:z.boolean(),recurring_monitoring_created:z.literal(true),
34
36
  });
@@ -11,3 +11,5 @@ Preserve the project's rendering and routing conventions. Inspect nearby pages b
11
11
  Write plainly and specifically. Remove repetition and generic filler without deleting necessary evidence. Add internal links only where the destination answers the reader's next relevant question; inspect the actual routes before linking. Check for an existing competing page before creating a new URL.
12
12
 
13
13
  Use the site's existing validation commands appropriate to the change. Record the final target URL, reader benefit and evidence in the AgentLinkOps product/campaign record using [the CRM contract](../../references/cli.md). A completed local edit does not establish that the page is deployed or that a publisher accepted it.
14
+
15
+ Read the [sourced asset recipe](../../references/recipes/sourced-linkable-asset.md) when preparing a draft with review receipts and a publication state. Use the [site brief](../../references/recipes/site-context-brief.md) if the audience or evidence needs clarification.
@@ -17,3 +17,5 @@ If the user authorizes outreach, use their existing email tool and honor their s
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
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.
20
+
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.
@@ -35,3 +35,7 @@ On the CLI the same three verbs are `agentlinkops tools [TOOLSET]`, `agentlinkop
35
35
  ## Installed skill set
36
36
 
37
37
  Run `agentlinkops skill --list` for the versioned set installed with this CLI. It includes connect (access and evidence rules), discovery (sourced prospect research), campaigns (campaign judgment), CRM (local records) and assets (a specific linkable resource). Read an individual skill with its full name, for example `agentlinkops skill agentlinkops-discovery`. Research and local work do not require buying a supplier subscription.
38
+
39
+ ## Choose the task
40
+
41
+ Use `agentlinkops setup --plan --goal GOAL --mode MODE` to inspect requirements without changing records. Goals include `build-content`, `prepare-campaign` and `verify-links`; modes are `local`, `hosted` and `external`. Read the [site and context brief](../../references/recipes/site-context-brief.md) when the task needs a site brief. Route sourced drafts to `agentlinkops-assets`, campaign handoffs to `agentlinkops-campaigns`, and placement reconciliation to `agentlinkops-crm`. The [recipe catalog](../../references/recipes/catalog.json) records versions, required inputs and untested capabilities.
@@ -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` for any command's exact schema.
10
10
 
11
11
  <!-- generated:skill-version begin (npm run generate:tools) -->
12
- Pack version 0.6.2. 100 commands in 9 toolsets.
12
+ Pack version 0.6.3. 125 commands in 13 toolsets.
13
13
  <!-- generated:skill-version end -->
14
14
 
15
15
  ## Install and connect (once per machine)
@@ -235,19 +235,23 @@ The human-readable version is
235
235
  ## Tool reference
236
236
 
237
237
  <!-- generated:skill-tools begin (npm run generate:tools; source: src/tool-registry.js) -->
238
- 100 commands in 9 toolsets, generated from `src/tool-registry.js` (`npm run generate:tools`). Core on every view: `get_workspace`, `list_projects`, `list_link_watches`, `monitor_link`, `list_events`. The index verbs list the rest by name; `describe` is the source of truth for input schemas. Admission-gated today: request_competitor_inventory, request_domain_overview, discover_backlinks; these refuse before any persistence.
238
+ 125 commands in 13 toolsets, generated from `src/tool-registry.js` (`npm run generate:tools`). Core on every view: `get_workspace`, `list_projects`, `list_link_watches`, `monitor_link`, `list_events`. The index verbs list the rest by name; `describe` is the source of truth for input schemas. Admission-gated today: request_competitor_inventory, request_domain_overview, discover_backlinks; these refuse before any persistence.
239
239
 
240
240
  | Toolset | Commands | What it covers |
241
241
  | --- | --- | --- |
242
242
  | `monitoring` | 17 | Watch earned links and destination URLs: create, list, update, import, export, recheck. |
243
- | `evidence` | 16 | What a check observed: histories, snapshots, change feeds, check jobs, published contacts. |
243
+ | `evidence` | 17 | What a check observed: histories, snapshots, change feeds, check jobs, published contacts. |
244
244
  | `discovery` | 16 | Import candidate rows, read stored runs, verify selected candidates, enroll them. |
245
245
  | `library` | 3 | Read and export the coverage-stated opportunity library. |
246
246
  | `competitors` | 17 | Competitor sets, dated inventories, scheduled refresh, gap and domain-mix reports. |
247
- | `reports` | 3 | Profile, anchor and report summaries over the tracked dataset. |
247
+ | `reports` | 5 | Profile, anchor and report summaries over the tracked dataset. |
248
248
  | `workspace` | 16 | Workspace, projects, usage, members, invitations, scratch-resource cleanup. |
249
249
  | `notifications` | 6 | Email notification preferences, previews, tests and deliveries. |
250
250
  | `webhooks` | 6 | Webhook endpoints, state, secrets and delivery records. |
251
+ | `admission` | 7 | Project rules, URL previews and retained admission decisions. |
252
+ | `disavow` | 7 | Propose, review and export website disavow rules. |
253
+ | `lifecycle` | 4 | Placement costs, expiry, renewal events and currency reports. |
254
+ | `digests` | 4 | Own-address digest preferences, delivery history and exact events. |
251
255
  <!-- generated:skill-tools end -->
252
256
 
253
257
  ## What it does not do
@@ -25,3 +25,5 @@ For ledger cloud setup, follow [Connect the ledger CLI](../../references/cli.md#
25
25
  For recurring cloud changes, use the host's already connected AgentLinkOps tools when available. The standalone `sync` helper requires `AGENTLINKOPS_API_URL` and `AGENTLINKOPS_API_KEY` in its process environment (the `LINKTRAIL_` spellings are still accepted with a warning during the pilot compatibility window). Never request a key in chat or place it in command arguments, source files or CRM notes. Account connection and permissions are separate from local CRM utility. Explain unavailable access neutrally and preserve completed local work.
26
26
 
27
27
  Sync updates `cloud_state` on placements and preserves local status, notes and opportunity relationships. A fetch failure is not proof that a backlink disappeared. Keep `unknown`, `suspected_missing` and confirmed outcomes distinct. Read the new events and decide what action, if any, the user's agent should take; receipt of an event does not authorize sending an email.
28
+
29
+ Use the [placement reconciliation recipe](../../references/recipes/placement-reconciliation.md) to reconcile an exact source and destination with retained evidence, checkpoints and an optional monitoring plan.
package/src/tool-views.js CHANGED
@@ -3,12 +3,16 @@
3
3
  // script, the MCP router, the CLI index and the generators all read the same definitions.
4
4
  // Rules: docs/technical/AGENT-SURFACE-CONTRACT.md (DP-0036).
5
5
 
6
- export const TOOLSETS = ['monitoring','evidence','discovery','library','competitors','reports','workspace','notifications','webhooks'];
6
+ export const TOOLSETS = ['monitoring','evidence','discovery','library','competitors','reports','workspace','notifications','webhooks','admission','disavow','lifecycle','digests'];
7
7
 
8
8
  // One line per toolset: what the group is for. Printed by the site brief, the REST index and the
9
9
  // catalog snapshot so the same wording reaches every client; a toolset without a summary is a
10
10
  // gate failure in the measurement script.
11
11
  export const TOOLSET_SUMMARIES = {
12
+ admission: 'Project rules, URL previews and retained admission decisions.',
13
+ disavow: 'Propose, review and export website disavow rules.',
14
+ lifecycle: 'Placement costs, expiry, renewal events and currency reports.',
15
+ digests: 'Own-address digest preferences, delivery history and exact events.',
12
16
  monitoring: 'Watch earned links and destination URLs: create, list, update, import, export, recheck.',
13
17
  evidence: 'What a check observed: histories, snapshots, change feeds, check jobs, published contacts.',
14
18
  discovery: 'Import candidate rows, read stored runs, verify selected candidates, enroll them.',
@@ -51,7 +55,7 @@ export function oneLiner(description, max = 60) {
51
55
  }
52
56
 
53
57
  // entries: [{ name, description, toolset, tier, readOnly }]. Grouped, one line per command.
54
- export function manifestText(entries, { max = 52 } = {}) {
58
+ export function manifestText(entries, { max = 36 } = {}) {
55
59
  const groups = new Map(TOOLSETS.map(t => [t, []]));
56
60
  for (const entry of entries) (groups.get(entry.toolset) ?? groups.set(entry.toolset, []).get(entry.toolset)).push(entry);
57
61
  const lines = ['Commands by toolset (name: what it does). Use search_tools or describe_tools for the full schema before calling.'];