feat(harness): 用戶 CC harness(acr install-harness)+ 公開 repo 只留對外
讓「用 arcrun 開發」的用戶,他的 CC 自動載入 arcrun 防護、不退回自寫 Python。 新增 user-cc-harness(SDD: .agents/specs/user-cc-harness,本機): - acr install-harness:冪等裝進用戶當前專案(新/舊專案皆可),acr init 末尾也順便裝 - CLAUDE.md arcrun 區塊(標記包夾,append 不破壞既有) - .claude/skills/arcrun-mindset(世界觀 + 資源去哪取 acr parts/auth-recipe) - .claude/commands/arcrun.md(/arcrun slash command) - .claude/hooks/arcrun-guard.sh(python→提醒不硬擋、暴露→exit 2、每條含正路) - settings.json 合併 hook(不覆蓋用戶既有 hooks/設定) - llms.txt + README「給 AI」段:第一接觸點(用戶丟連結,CC 讀了知道第一步 install-harness) 含 CF 憑證白話照抄式引導(不對用戶講 KV/Worker/R2 術語) - harness 素材內嵌 npm 套件(cli/harness/,files 帶上),不依賴用戶有 arcrun repo - 實測:空目錄/冪等/既有檔合併皆通過,tsc exit 0,npm pack 含 harness 5 檔 公開 repo 清理(richblack:用戶要用不要開發 arcrun): - git rm --cached 移除開發痕跡 + 思考過程出公開 repo(本機保留供 richblack 開發): .claude/CLAUDE.md/AGENTS.md/.agents/docs/DECISIONS/BACKLOG/landing/.github - .gitignore 防回流;補 MIT LICENSE MCP(P7)納入 install-harness/update 的接點已設計,實作待 MCP 對齊(BACKLOG 另一條線)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,128 +0,0 @@
|
||||
const ApiKeys = ({ onNav }) => {
|
||||
const [newKeyCopied, setNewKeyCopied] = React.useState(false);
|
||||
const [keys, setKeys] = React.useState([
|
||||
{ id: 'k_dev', name: 'Local Development', prefix: 'ar_dev_', created: 'Mar 12, 2026', lastUsed: '2 min ago', active: true },
|
||||
{ id: 'k_prod', name: 'Production — Northwind API', prefix: 'ar_live_', created: 'Feb 3, 2026', lastUsed: '12 sec ago', active: true },
|
||||
{ id: 'k_staging', name: 'Staging — Vercel', prefix: 'ar_test_', created: 'Jan 28, 2026', lastUsed: '4 hours ago', active: true },
|
||||
{ id: 'k_ci', name: 'CI/CD (GitHub Actions)', prefix: 'ar_live_', created: 'Jan 10, 2026', lastUsed: 'Yesterday', active: false },
|
||||
{ id: 'k_old', name: 'Legacy — Zapier import', prefix: 'ar_live_', created: 'Nov 4, 2025', lastUsed: '3 weeks ago', active: false, revoked: true },
|
||||
]);
|
||||
|
||||
const newKey = 'ar_live_sk_7x9Qf2vLm8nR4TpW6ZjKc3bEhN1aSyU5oP0dI';
|
||||
|
||||
const copyKey = () => {
|
||||
setNewKeyCopied(true);
|
||||
setTimeout(() => setNewKeyCopied(false), 1800);
|
||||
};
|
||||
|
||||
const toggleKey = (id) => {
|
||||
setKeys(keys.map(k => k.id === id ? { ...k, active: !k.active } : k));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="shell">
|
||||
<Sidebar current="keys" onNav={onNav} />
|
||||
<div className="main">
|
||||
<div className="main-head">
|
||||
<div>
|
||||
<div className="crumb">
|
||||
<span>Workspace</span>
|
||||
<span className="sep"><Icon name="chevron_right" size={11} /></span>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
<h1>API Keys</h1>
|
||||
<div className="sub">Scoped credentials for calling the Arcrun API from your code and CI.</div>
|
||||
</div>
|
||||
<div className="flex gap-8">
|
||||
<button className="btn btn-secondary"><Icon name="book" size={14} /> API docs</button>
|
||||
<button className="btn btn-primary"><Icon name="plus" size={14} /> Create new key</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="main-body" style={{maxWidth: 1080}}>
|
||||
<div className="new-key-box">
|
||||
<div className="warn-row">
|
||||
<span className="warn-icon"><Icon name="warn" size={12} /></span>
|
||||
<span><strong style={{color: '#FBBF24'}}>Save this key now.</strong> For security, we won't show it again — if you lose it, you'll need to create a new one.</span>
|
||||
</div>
|
||||
<h3>Your new API key</h3>
|
||||
<p className="desc">Key named <strong style={{color: 'var(--text)'}}>"Production — Northwind API"</strong> · created just now · all scopes</p>
|
||||
<div className="key-display">
|
||||
<span className="key-val">{newKey}</span>
|
||||
<button className={`copy-btn ${newKeyCopied ? 'copied' : ''}`} onClick={copyKey}>
|
||||
<Icon name={newKeyCopied ? 'check' : 'copy'} size={12} />
|
||||
{newKeyCopied ? 'Copied' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
<div style={{marginTop: 14, display: 'flex', gap: 16, fontSize: 12, color: 'var(--text-mute)', alignItems: 'center'}}>
|
||||
<span className="flex gap-6" style={{alignItems: 'center'}}><Icon name="check" size={12} /> Full workspace access</span>
|
||||
<span className="flex gap-6" style={{alignItems: 'center'}}><Icon name="clock" size={12} /> Never expires</span>
|
||||
<span style={{marginLeft: 'auto'}}><span className="link">Add expiry or restrict scopes →</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="section-head">
|
||||
<div>
|
||||
<h2>All keys</h2>
|
||||
<div className="subtle" style={{marginTop: 2}}>{keys.filter(k => !k.revoked).length} active · {keys.filter(k => k.revoked).length} revoked</div>
|
||||
</div>
|
||||
<div className="flex gap-8">
|
||||
<button className="btn btn-secondary btn-sm"><Icon name="filter" size={12} /> Filter</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="table-wrap">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{width: '32%'}}>Name</th>
|
||||
<th>Key</th>
|
||||
<th>Created</th>
|
||||
<th>Last used</th>
|
||||
<th>Status</th>
|
||||
<th style={{width: 60, textAlign: 'right'}}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{keys.map(k => (
|
||||
<tr key={k.id}>
|
||||
<td>
|
||||
<div style={{fontWeight: 500, fontSize: 13.5}}>{k.name}</div>
|
||||
</td>
|
||||
<td className="mono">{k.prefix}••••{k.id.slice(-4)}</td>
|
||||
<td className="dim" style={{fontSize: 12.5}}>{k.created}</td>
|
||||
<td className="dim" style={{fontSize: 12.5}}>{k.lastUsed}</td>
|
||||
<td>
|
||||
{k.revoked ? (
|
||||
<span className="pill revoked"><span className="pdot" /> Revoked</span>
|
||||
) : (
|
||||
<div className="flex gap-8" style={{alignItems: 'center'}}>
|
||||
<span className={`toggle ${k.active ? 'on' : ''}`} onClick={() => toggleKey(k.id)} />
|
||||
<span className={`pill ${k.active ? 'active' : 'idle'}`}>
|
||||
<span className="pdot" /> {k.active ? 'Active' : 'Paused'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td style={{textAlign: 'right'}}>
|
||||
{!k.revoked && (
|
||||
<button className="btn btn-danger-ghost btn-sm"><Icon name="trash" size={12} /></button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div style={{marginTop: 18, fontSize: 12, color: 'var(--text-mute)', display: 'flex', alignItems: 'center', gap: 8}}>
|
||||
<Icon name="warn" size={12} />
|
||||
<span>Revoking a key stops all in-flight requests within 60 seconds. This cannot be undone.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
window.ApiKeys = ApiKeys;
|
||||
Reference in New Issue
Block a user