Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agentlinkops",
"version": "0.6.8",
"version": "0.6.9",
"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.",
"author": {
"name": "AgentLinkOps"
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agentlinkops",
"version": "0.6.8",
"version": "0.6.9",
"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.",
"author": {
"name": "AgentLinkOps"
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
All notable changes to the `agentlinkops` package are recorded here. The version follows the
plugin manifests in `.claude-plugin/` and `.codex-plugin/`.

## 0.6.9 (2026-09-25)

- Read ChatGPT citations in the current anonymous layout, where inline citations and source pills are buttons carrying their sources as JSON. 0.6.8 read only anchor links and recorded these answers as unknown. Code-copy, table-copy and entity controls are recognized as non-citations; any other unrecognized control still keeps the answer unknown.
- Verify the prompt before and after submitting: the composer must hold the whole panel prompt (one refill, otherwise a retriable unknown), and the submitted turn must match it. 0.6.8 could submit a truncated prompt and score the answer to a different question.
- Find the current anonymous ChatGPT composer (`textarea#mobile-composer-prompt`) directly instead of through the broad fallback.
- Treat a ChatGPT failure message shown as the assistant turn ("Something went wrong...") as a retriable unknown. 0.6.8 recorded it as an answer and a false not-cited observation.
- Name the kinds of unrecognized element (tag and role counts, never URLs or text) when an anonymous ChatGPT answer's citations cannot be read and the observation stays unknown.
- End the virtual display (Xvfb) when a browser measurement process is killed or crashes. A detached watchdog stops Xvfb within five seconds of the process disappearing and only signals the pid while it is still Xvfb. It stays armed if a normal close cannot be confirmed.

## 0.6.8 (2026-09-20)

- Recognize the anonymous ChatGPT response layout and use the latest visible assistant response consistently when reading answers and citations. Exclude user messages and response controls from answer text.
Expand Down
2 changes: 1 addition & 1 deletion cli/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {restResponseContract} from '../shared/response-contract.js';
// A small HTTP client for the cloud. Scoped keys only; no human session ever reaches here.
// DP-0036-T13: the CLI names itself so the usage counters can tell it from other REST callers.
// The version follows the plugin manifests; bump it with them.
export const CLI_VERSION = '0.6.8';
export const CLI_VERSION = '0.6.9';
export const CLI_USER_AGENT = `agentlinkops-cli/${CLI_VERSION}`;
export class CloudError extends Error {
constructor(code, status, details = null) {
Expand Down
25 changes: 22 additions & 3 deletions cli/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ import { parseArgs } from './args.js';
import { NUDGE } from './skill.js';
import { CATALOG_COMMANDS, CATALOG_TOOLSETS } from './catalog.js';
import { oneLiner } from '../src/tool-views.js';
import { resolveSchemaPath, listAvailablePaths } from '../shared/exec-grammar.js';

// DP-0036-T05: the CLI's three-level discovery over the cloud catalog — index (`tools`),
// describe (`describe NAME`) and call (`call NAME`). `tools` and `describe` answer from the
// bundled snapshot in cli/catalog.js with no account and no network; `--refresh` reads the
// live catalog instead. Output is JSON whenever stdout is not a terminal or --json is given,
// and one error shape ({ error: { code, message, next } }) on every failure.
const USAGE = 'tools [TOOLSET] [--json] [--refresh] | describe NAME [--examples] [--output-schema] [--json] [--refresh] | call NAME [--args JSON | --file FILE | --set PATH=VALUE ...] [--dry-run] [-y] [--json]';
const USAGE = 'tools [TOOLSET] [--json] [--refresh] | describe NAME [--examples] [--output-schema] [--json] [--refresh] | schema NAME [FIELD.PATH] [--json] | call NAME [--args JSON | --file FILE | --set PATH=VALUE ...] [--dry-run] [-y] [--json]';
const NAME = /^[a-z][a-z0-9_]*$/;
const NEXT = {
TOOL_NOT_FOUND: 'Run `agentlinkops tools` to list commands, or `agentlinkops tools TOOLSET` for one group.',
INVALID_INPUT: 'Run `agentlinkops describe NAME` for the exact input schema.',
SCHEMA_PATH_NOT_FOUND: 'Run `agentlinkops schema NAME` for the top level, or drill a listed child path.',
INSUFFICIENT_SCOPE: 'This credential lacks a scope the command needs; request it through your connection or a new project-scoped key.',
NO_CLOUD_ORIGIN: 'Run `agentlinkops connect` or set AGENTLINKOPS_API_URL.',
INVALID_CLOUD_ORIGIN: 'Cloud commands need an HTTPS origin without a path, for example https://app.agentlinkops.com.',
Expand Down Expand Up @@ -102,8 +104,9 @@ export async function commandsMain(argv, { cwd = process.cwd(), env = process.en
const [command, name] = args._;
const json = args.json === true || !isTTY;
const fail = (code, message) => { if (json) err(errorJson(code, message, explain(code))); else err(`${message}\n${explain(code)}`); return 2; };
const allowed = { tools: ['_', 'json', 'refresh'], describe: ['_', 'json', 'refresh', 'examples', 'output-schema'], call: ['_', 'args', 'file', 'set', 'dry-run', 'json'] };
if (!allowed[command] || Object.keys(args).some(key => !allowed[command].includes(key)) || (command === 'tools' && args._.length > 2) || (command !== 'tools' && (args._.length !== 2 || !NAME.test(name)))) throw new ConfigError(USAGE);
const allowed = { tools: ['_', 'json', 'refresh'], describe: ['_', 'json', 'refresh', 'examples', 'output-schema'], schema: ['_', 'json'], call: ['_', 'args', 'file', 'set', 'dry-run', 'json'] };
const positionalCount = command === 'tools' ? args._.length > 2 : command === 'schema' ? args._.length !== 2 && args._.length !== 3 : args._.length !== 2;
if (!allowed[command] || Object.keys(args).some(key => !allowed[command].includes(key)) || positionalCount || (command !== 'tools' && !NAME.test(name))) throw new ConfigError(USAGE);
if (args.set?.length && (args.set.some(v => typeof v !== 'string') || args.args !== undefined || args.file !== undefined)) throw new ConfigError('--set cannot be combined with --args or --file');
if (args.args !== undefined && args.file !== undefined) throw new ConfigError(USAGE);

Expand Down Expand Up @@ -160,6 +163,22 @@ export async function commandsMain(argv, { cwd = process.cwd(), env = process.en
out(renderDescription(found, name, { examples: args.examples === true, outputSchema: args['output-schema'] === true, json }));
return 0;
}
if (command === 'schema') {
// DP-0059: the same drill the MCP exec tool serves, over the same bundled schema, so one
// grammar holds across surfaces: `schema monitor_link scope.target` prints that field.
const found = resolveName(commands, name);
if (!found) return fail('TOOL_NOT_FOUND', `No command named ${name}.`);
const path = args._[2] ?? null;
if (!path) { out(JSON.stringify({ name: found.name, schema: found.inputSchema }, null, 2)); return 0; }
const subschema = resolveSchemaPath(found.inputSchema, path);
if (!subschema) {
const parent = resolveSchemaPath(found.inputSchema, path.split('.').slice(0, -1).join('.')) ?? found.inputSchema;
const available = listAvailablePaths(parent);
return fail('SCHEMA_PATH_NOT_FOUND', `"${path}" is not a path in ${found.name}'s schema.${available.length ? ` Available here: ${available.join(', ')}.` : ''}`);
}
out(JSON.stringify({ name: found.name, path, schema: subschema }, null, 2));
return 0;
}

// call
const found = resolveName(commands, name);
Expand Down
2 changes: 1 addition & 1 deletion cli/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export async function main(argv = process.argv.slice(2), { cwd = process.cwd(),
if (command === 'disavow') return await disavowMain(args,{cwd,env,fetchImpl,out});
if (command === 'index') return await indexMain(args,{cwd,env,fetchImpl,out});
if (command === 'setup') return await setupPlanMain(argv, { cwd, out });
if (command === 'tools' || command === 'describe' || command === 'call') return await commandsMain(argv, { cwd, out, err, env, fetchImpl });
if (command === 'tools' || command === 'describe' || command === 'schema' || command === 'call') return await commandsMain(argv, { cwd, out, err, env, fetchImpl });
if (command === 'skill') return await skillMain(argv, { out });
if (command === 'agent') return await agentMain(argv, { cwd, out, err });
if (command === 'connect') {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agentlinkops",
"version": "0.6.8",
"version": "0.6.9",
"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.",
"author": "AgentLinkOps (https://agentlinkops.com)",
"license": "Apache-2.0",
Expand Down
Loading
Loading