Skip to content

notes: Anthropic Commerce Agents 蓝图卡 - #148

Open
joyehuang wants to merge 2 commits into
mainfrom
0903-claude-commerce-agents
Open

notes: Anthropic Commerce Agents 蓝图卡#148
joyehuang wants to merge 2 commits into
mainfrom
0903-claude-commerce-agents

Conversation

@joyehuang

@joyehuang joyehuang commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Anthropic 2026-09-02 开源的 commerce-agents 蓝图完整拆解:shopping/merchant 双角色、定义与运行时三跑法分离、safety.md 代码级安全分层全清单(fencing/provenance gates/grounding/staging/host approval/memory 边界)、五 skills 即业务流程、四垂直 demo 复用方式。含对 MaxInsights 企业 agent 场景的参考点。全文 12.2KB,讲解类足量标准。


Summary by cubic

Adds a reference note dissecting Anthropic's commerce-agents blueprint — dual shopping/merchant roles, definition/runtime separation, and the code-level safety layering from docs/safety.md — plus GSC scripts for querying Search Console data.

New Features

  • Adds OAuth authorization flows and a Search Analytics query CLI under scripts/gsc.
  • Stores GSC credentials and tokens in ~/.config/gsc, outside the repo.

Written for commit 7413b9e. Summary will update on new commits.

Review in cubic

@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
blog Ready Ready Preview Sep 3, 2026 3:13am UTC

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

12 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/gsc/package.json">

<violation number="1" location="scripts/gsc/package.json:5">
P3: This package.json carries unedited `npm init -y` boilerplate. `main: index.js` points to a file that doesn't exist in scripts/gsc/, and the `test` script always exits 1 so any `npm test` here fails. Since the real entrypoints are the `.mjs` scripts, drop the stale `main` and replace the placeholder test with a real check (or remove it) so the manifest reflects the folder's actual content.</violation>
</file>

<file name="scripts/gsc/gsc-query.mjs">

<violation number="1" location="scripts/gsc/gsc-query.mjs:25">
P2: When invoked without arguments, `process.exit(0)` prevents the documented default 28-day query after listing sites. Remove this early exit and supply the default dates before querying.</violation>
</file>

<file name="src/content/notes/0903-claude-commerce-agents.md">

<violation number="1" location="src/content/notes/0903-claude-commerce-agents.md:3">
P3: The description frontmatter names the internal agent system "atypica", but the rest of the note (核心内容, 参考价值 sections) consistently refers to that same system as "MaxInsights". "atypica" never appears in the body and appears to be a leftover/typo, creating an inconsistency that will confuse readers searching for the reference value section. Align the description with the body's naming.</violation>
</file>

<file name="scripts/gsc/flow.mjs">

<violation number="1" location="scripts/gsc/flow.mjs:8">
P3: `DIR` is computed from `fileURLToPath(import.meta.url)` but never referenced in this file; paths are derived from `process.env.HOME`. Remove the unused declaration (and its unused `fileURLToPath` import) to avoid dead code.</violation>

<violation number="2" location="scripts/gsc/flow.mjs:17">
P2: Add a random OAuth `state` value to the authorization URL and reject callbacks whose state does not match this run; otherwise an unsolicited authorization response can be consumed as this run's token.</violation>

<violation number="3" location="scripts/gsc/flow.mjs:28">
P2: When authorization is denied or the callback is malformed, close the server and exit non-zero instead of returning from the handler while the listener remains open.</violation>

<violation number="4" location="scripts/gsc/flow.mjs:36">
P2: Treat a missing `refresh_token` as authorization failure, or preserve a valid existing refresh token; otherwise this flow reports success while saving only a short-lived credential.</violation>

<violation number="5" location="scripts/gsc/flow.mjs:38">
P2: Preserve a non-zero exit code for the catch path; otherwise automation can treat a failed token exchange or token write as successful.</violation>

<violation number="6" location="scripts/gsc/flow.mjs:46">
P2: Bind this OAuth callback explicitly to loopback; the current wildcard listener exposes the token-exchange endpoint to other network interfaces. Use `127.0.0.1` or `::1` and a matching redirect URI.</violation>
</file>

<file name="scripts/gsc/auth.mjs">

<violation number="1" location="scripts/gsc/auth.mjs:9">
P2: When `HOME` is unset, `path.join` throws during module initialization, so this authorization script never starts. Use `os.homedir()` as `gsc-query.mjs` does.</violation>

<violation number="2" location="scripts/gsc/auth.mjs:25">
P3: The console prompt on this line contains garbled text (`��` replacement characters) in the middle of `授权后浏览器??跳转到`. The message is meant to instruct the user that the browser will redirect, so the malformed characters should be replaced (e.g. `授权后浏览器会跳转到`).</violation>

<violation number="3" location="scripts/gsc/auth.mjs:33">
P1: When `token.json` already exists with permissive permissions, `{ mode: 0o600 }` does not tighten it, leaving the refresh token readable by other local users. Enforce `0600` after writing, including for existing files.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread scripts/gsc/auth.mjs
try {
const { tokens } = await oauth2.getToken(decodeURIComponent(code.trim()));
fs.mkdirSync(CRED_DIR, { recursive: true, mode: 0o700 });
fs.writeFileSync(TOKEN_FILE, JSON.stringify(tokens, null, 2), { mode: 0o600 });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When token.json already exists with permissive permissions, { mode: 0o600 } does not tighten it, leaving the refresh token readable by other local users. Enforce 0600 after writing, including for existing files.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gsc/auth.mjs, line 33:

<comment>When `token.json` already exists with permissive permissions, `{ mode: 0o600 }` does not tighten it, leaving the refresh token readable by other local users. Enforce `0600` after writing, including for existing files.</comment>

<file context>
@@ -0,0 +1,39 @@
+  try {
+    const { tokens } = await oauth2.getToken(decodeURIComponent(code.trim()));
+    fs.mkdirSync(CRED_DIR, { recursive: true, mode: 0o700 });
+    fs.writeFileSync(TOKEN_FILE, JSON.stringify(tokens, null, 2), { mode: 0o600 });
+    console.log('✅ token 已保存到', TOKEN_FILE);
+  } catch (e) {
</file context>
Suggested change
fs.writeFileSync(TOKEN_FILE, JSON.stringify(tokens, null, 2), { mode: 0o600 });
fs.writeFileSync(TOKEN_FILE, JSON.stringify(tokens, null, 2), { mode: 0o600 });
fs.chmodSync(TOKEN_FILE, 0o600);

Comment thread scripts/gsc/gsc-query.mjs
const { data } = await sc.sites.list();
const sites = (data.siteEntry || []).map(s => `${s.permissionLevel} ${s.siteUrl}`);
console.log(sites.join('\n'));
process.exit(0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When invoked without arguments, process.exit(0) prevents the documented default 28-day query after listing sites. Remove this early exit and supply the default dates before querying.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gsc/gsc-query.mjs, line 25:

<comment>When invoked without arguments, `process.exit(0)` prevents the documented default 28-day query after listing sites. Remove this early exit and supply the default dates before querying.</comment>

<file context>
@@ -0,0 +1,50 @@
+  const { data } = await sc.sites.list();
+  const sites = (data.siteEntry || []).map(s => `${s.permissionLevel}  ${s.siteUrl}`);
+  console.log(sites.join('\n'));
+  process.exit(0);
+}
+
</file context>

Comment thread scripts/gsc/flow.mjs
const { tokens } = await oauth2.getToken(code);
fs.mkdirSync(CRED_DIR, { recursive: true, mode: 0o700 });
fs.writeFileSync(TOKEN_FILE, JSON.stringify(tokens, null, 2), { mode: 0o600 });
console.log('OAUTH_OK refresh=' + (tokens.refresh_token ? 'yes' : 'MISSING'));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Treat a missing refresh_token as authorization failure, or preserve a valid existing refresh token; otherwise this flow reports success while saving only a short-lived credential.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gsc/flow.mjs, line 36:

<comment>Treat a missing `refresh_token` as authorization failure, or preserve a valid existing refresh token; otherwise this flow reports success while saving only a short-lived credential.</comment>

<file context>
@@ -0,0 +1,48 @@
+    const { tokens } = await oauth2.getToken(code);
+    fs.mkdirSync(CRED_DIR, { recursive: true, mode: 0o700 });
+    fs.writeFileSync(TOKEN_FILE, JSON.stringify(tokens, null, 2), { mode: 0o600 });
+    console.log('OAUTH_OK refresh=' + (tokens.refresh_token ? 'yes' : 'MISSING'));
+    res.writeHead(200); res.end('<h1>授权成功,可以关闭此页面</h1>');
+  } catch (e) {
</file context>

Comment thread scripts/gsc/flow.mjs
Comment on lines +28 to +32
if (err || !code) {
console.log('OAUTH_ERROR: ' + (err || 'no code'));
res.writeHead(200); res.end('授权失败: ' + (err || 'no code')); return;
}
try {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When authorization is denied or the callback is malformed, close the server and exit non-zero instead of returning from the handler while the listener remains open.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gsc/flow.mjs, line 28:

<comment>When authorization is denied or the callback is malformed, close the server and exit non-zero instead of returning from the handler while the listener remains open.</comment>

<file context>
@@ -0,0 +1,48 @@
+  if (u.pathname !== '/oauth2callback') { res.writeHead(404); res.end(); return; }
+  const code = u.searchParams.get('code');
+  const err = u.searchParams.get('error');
+  if (err || !code) {
+    console.log('OAUTH_ERROR: ' + (err || 'no code'));
+    res.writeHead(200); res.end('授权失败: ' + (err || 'no code')); return;
</file context>
Suggested change
if (err || !code) {
console.log('OAUTH_ERROR: ' + (err || 'no code'));
res.writeHead(200); res.end('授权失败: ' + (err || 'no code')); return;
}
try {
if (err || !code) {
console.log('OAUTH_ERROR: ' + (err || 'no code'));
res.writeHead(400);
res.end('授权失败: ' + (err || 'no code'));
server.close();
setTimeout(() => process.exit(1), 500);
return;
}

Comment thread scripts/gsc/flow.mjs
Comment on lines +38 to +43
} catch (e) {
console.log('OAUTH_EXCHANGE_FAIL: ' + e.message);
res.writeHead(500); res.end('换 token 失败: ' + e.message);
}
server.close();
setTimeout(() => process.exit(0), 500);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Preserve a non-zero exit code for the catch path; otherwise automation can treat a failed token exchange or token write as successful.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gsc/flow.mjs, line 38:

<comment>Preserve a non-zero exit code for the catch path; otherwise automation can treat a failed token exchange or token write as successful.</comment>

<file context>
@@ -0,0 +1,48 @@
+    fs.writeFileSync(TOKEN_FILE, JSON.stringify(tokens, null, 2), { mode: 0o600 });
+    console.log('OAUTH_OK refresh=' + (tokens.refresh_token ? 'yes' : 'MISSING'));
+    res.writeHead(200); res.end('<h1>授权成功,可以关闭此页面</h1>');
+  } catch (e) {
+    console.log('OAUTH_EXCHANGE_FAIL: ' + e.message);
+    res.writeHead(500); res.end('换 token 失败: ' + e.message);
</file context>
Suggested change
} catch (e) {
console.log('OAUTH_EXCHANGE_FAIL: ' + e.message);
res.writeHead(500); res.end('换 token 失败: ' + e.message);
}
server.close();
setTimeout(() => process.exit(0), 500);
} catch (e) {
console.log('OAUTH_EXCHANGE_FAIL: ' + e.message);
res.writeHead(500); res.end('换 token 失败: ' + e.message);
process.exitCode = 1;
}
server.close();
setTimeout(() => process.exit(), 500);

Comment thread scripts/gsc/auth.mjs
import { fileURLToPath } from 'node:url';

const DIR = path.dirname(fileURLToPath(import.meta.url));
const CRED_DIR = path.join(process.env.HOME, '.config', 'gsc');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When HOME is unset, path.join throws during module initialization, so this authorization script never starts. Use os.homedir() as gsc-query.mjs does.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gsc/auth.mjs, line 9:

<comment>When `HOME` is unset, `path.join` throws during module initialization, so this authorization script never starts. Use `os.homedir()` as `gsc-query.mjs` does.</comment>

<file context>
@@ -0,0 +1,39 @@
+import { fileURLToPath } from 'node:url';
+
+const DIR = path.dirname(fileURLToPath(import.meta.url));
+const CRED_DIR = path.join(process.env.HOME, '.config', 'gsc');
+const CLIENT_FILE = path.join(CRED_DIR, 'client_secret.json');
+const TOKEN_FILE = path.join(CRED_DIR, 'token.json');
</file context>

Comment thread scripts/gsc/package.json
"name": "gsc",
"version": "1.0.0",
"description": "",
"main": "index.js",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This package.json carries unedited npm init -y boilerplate. main: index.js points to a file that doesn't exist in scripts/gsc/, and the test script always exits 1 so any npm test here fails. Since the real entrypoints are the .mjs scripts, drop the stale main and replace the placeholder test with a real check (or remove it) so the manifest reflects the folder's actual content.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gsc/package.json, line 5:

<comment>This package.json carries unedited `npm init -y` boilerplate. `main: index.js` points to a file that doesn't exist in scripts/gsc/, and the `test` script always exits 1 so any `npm test` here fails. Since the real entrypoints are the `.mjs` scripts, drop the stale `main` and replace the placeholder test with a real check (or remove it) so the manifest reflects the folder's actual content.</comment>

<file context>
@@ -0,0 +1,16 @@
+  "name": "gsc",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
</file context>

@@ -0,0 +1,99 @@
---
title: Anthropic Commerce Agents 蓝图:电商 Agent 的官方参考实现与安全分层
description: Anthropic 2026-09-02 开源的 commerce-agents 蓝图完整拆解——shopping agent(顾客侧)与 merchant agent(店员侧)双角色设计、定义与运行时分离(Messages API / Agent SDK / Managed Agents 三跑法)、provenance gates / staging / host approval 的代码级安全分层(docs/safety.md 逐条)、skills 即业务流程的组织方式,以及对 atypica 和企业 agent 建设的直接参考价值。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The description frontmatter names the internal agent system "atypica", but the rest of the note (核心内容, 参考价值 sections) consistently refers to that same system as "MaxInsights". "atypica" never appears in the body and appears to be a leftover/typo, creating an inconsistency that will confuse readers searching for the reference value section. Align the description with the body's naming.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/content/notes/0903-claude-commerce-agents.md, line 3:

<comment>The description frontmatter names the internal agent system "atypica", but the rest of the note (核心内容, 参考价值 sections) consistently refers to that same system as "MaxInsights". "atypica" never appears in the body and appears to be a leftover/typo, creating an inconsistency that will confuse readers searching for the reference value section. Align the description with the body's naming.</comment>

<file context>
@@ -0,0 +1,99 @@
+---
+title: Anthropic Commerce Agents 蓝图:电商 Agent 的官方参考实现与安全分层
+description: Anthropic 2026-09-02 开源的 commerce-agents 蓝图完整拆解——shopping agent(顾客侧)与 merchant agent(店员侧)双角色设计、定义与运行时分离(Messages API / Agent SDK / Managed Agents 三跑法)、provenance gates / staging / host approval 的代码级安全分层(docs/safety.md 逐条)、skills 即业务流程的组织方式,以及对 atypica 和企业 agent 建设的直接参考价值。
+date: 2026-09-03
+updatedDate: 2026-09-03
</file context>

Comment thread scripts/gsc/flow.mjs
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const DIR = path.dirname(fileURLToPath(import.meta.url));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: DIR is computed from fileURLToPath(import.meta.url) but never referenced in this file; paths are derived from process.env.HOME. Remove the unused declaration (and its unused fileURLToPath import) to avoid dead code.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gsc/flow.mjs, line 8:

<comment>`DIR` is computed from `fileURLToPath(import.meta.url)` but never referenced in this file; paths are derived from `process.env.HOME`. Remove the unused declaration (and its unused `fileURLToPath` import) to avoid dead code.</comment>

<file context>
@@ -0,0 +1,48 @@
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const DIR = path.dirname(fileURLToPath(import.meta.url));
+const CRED_DIR = path.join(process.env.HOME, '.config', 'gsc');
+const CLIENT_FILE = path.join(CRED_DIR, 'client_secret.json');
</file context>

Comment thread scripts/gsc/auth.mjs

console.log('打开以下 URL 完成授权:\n');
console.log(url);
console.log('\n授权后浏览器��跳转到 localhost:3456(页面会打不开,没关系),把地址栏里的 code= 后面的值粘贴到这里:');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The console prompt on this line contains garbled text (�� replacement characters) in the middle of 授权后浏览器??跳转到. The message is meant to instruct the user that the browser will redirect, so the malformed characters should be replaced (e.g. 授权后浏览器会跳转到).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gsc/auth.mjs, line 25:

<comment>The console prompt on this line contains garbled text (`��` replacement characters) in the middle of `授权后浏览器??跳转到`. The message is meant to instruct the user that the browser will redirect, so the malformed characters should be replaced (e.g. `授权后浏览器会跳转到`).</comment>

<file context>
@@ -0,0 +1,39 @@
+
+console.log('打开以下 URL 完成授权:\n');
+console.log(url);
+console.log('\n授权后浏览器��跳转到 localhost:3456(页面会打不开,没关系),把地址栏里的 code= 后面的值粘贴到这里:');
+
+const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant