@agent-native/core 0.34.0 → 0.35.0

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 (130) hide show
  1. package/dist/agent/context-xray/actions/context-evict.d.ts +3 -0
  2. package/dist/agent/context-xray/actions/context-evict.d.ts.map +1 -0
  3. package/dist/agent/context-xray/actions/context-evict.js +35 -0
  4. package/dist/agent/context-xray/actions/context-evict.js.map +1 -0
  5. package/dist/agent/context-xray/actions/context-manifest-get.d.ts +3 -0
  6. package/dist/agent/context-xray/actions/context-manifest-get.d.ts.map +1 -0
  7. package/dist/agent/context-xray/actions/context-manifest-get.js +67 -0
  8. package/dist/agent/context-xray/actions/context-manifest-get.js.map +1 -0
  9. package/dist/agent/context-xray/actions/context-pin.d.ts +3 -0
  10. package/dist/agent/context-xray/actions/context-pin.d.ts.map +1 -0
  11. package/dist/agent/context-xray/actions/context-pin.js +35 -0
  12. package/dist/agent/context-xray/actions/context-pin.js.map +1 -0
  13. package/dist/agent/context-xray/actions/context-report.d.ts +3 -0
  14. package/dist/agent/context-xray/actions/context-report.d.ts.map +1 -0
  15. package/dist/agent/context-xray/actions/context-report.js +59 -0
  16. package/dist/agent/context-xray/actions/context-report.js.map +1 -0
  17. package/dist/agent/context-xray/actions/context-restore.d.ts +3 -0
  18. package/dist/agent/context-xray/actions/context-restore.d.ts.map +1 -0
  19. package/dist/agent/context-xray/actions/context-restore.js +33 -0
  20. package/dist/agent/context-xray/actions/context-restore.js.map +1 -0
  21. package/dist/agent/context-xray/actions/errors.d.ts +7 -0
  22. package/dist/agent/context-xray/actions/errors.d.ts.map +1 -0
  23. package/dist/agent/context-xray/actions/errors.js +15 -0
  24. package/dist/agent/context-xray/actions/errors.js.map +1 -0
  25. package/dist/agent/context-xray/apply-directives.d.ts +10 -0
  26. package/dist/agent/context-xray/apply-directives.d.ts.map +1 -0
  27. package/dist/agent/context-xray/apply-directives.js +100 -0
  28. package/dist/agent/context-xray/apply-directives.js.map +1 -0
  29. package/dist/agent/context-xray/directives-store.d.ts +26 -0
  30. package/dist/agent/context-xray/directives-store.d.ts.map +1 -0
  31. package/dist/agent/context-xray/directives-store.js +131 -0
  32. package/dist/agent/context-xray/directives-store.js.map +1 -0
  33. package/dist/agent/context-xray/identity.d.ts +5 -0
  34. package/dist/agent/context-xray/identity.d.ts.map +1 -0
  35. package/dist/agent/context-xray/identity.js +33 -0
  36. package/dist/agent/context-xray/identity.js.map +1 -0
  37. package/dist/agent/context-xray/manifest.d.ts +19 -0
  38. package/dist/agent/context-xray/manifest.d.ts.map +1 -0
  39. package/dist/agent/context-xray/manifest.js +115 -0
  40. package/dist/agent/context-xray/manifest.js.map +1 -0
  41. package/dist/agent/context-xray/migrations.d.ts +3 -0
  42. package/dist/agent/context-xray/migrations.d.ts.map +1 -0
  43. package/dist/agent/context-xray/migrations.js +31 -0
  44. package/dist/agent/context-xray/migrations.js.map +1 -0
  45. package/dist/agent/context-xray/plugin.d.ts +5 -0
  46. package/dist/agent/context-xray/plugin.d.ts.map +1 -0
  47. package/dist/agent/context-xray/plugin.js +15 -0
  48. package/dist/agent/context-xray/plugin.js.map +1 -0
  49. package/dist/agent/context-xray/schema.d.ts +249 -0
  50. package/dist/agent/context-xray/schema.d.ts.map +1 -0
  51. package/dist/agent/context-xray/schema.js +17 -0
  52. package/dist/agent/context-xray/schema.js.map +1 -0
  53. package/dist/agent/context-xray/segments.d.ts +19 -0
  54. package/dist/agent/context-xray/segments.d.ts.map +1 -0
  55. package/dist/agent/context-xray/segments.js +154 -0
  56. package/dist/agent/context-xray/segments.js.map +1 -0
  57. package/dist/agent/context-xray/tokenize.d.ts +10 -0
  58. package/dist/agent/context-xray/tokenize.d.ts.map +1 -0
  59. package/dist/agent/context-xray/tokenize.js +70 -0
  60. package/dist/agent/context-xray/tokenize.js.map +1 -0
  61. package/dist/agent/production-agent.d.ts +2 -0
  62. package/dist/agent/production-agent.d.ts.map +1 -1
  63. package/dist/agent/production-agent.js +45 -5
  64. package/dist/agent/production-agent.js.map +1 -1
  65. package/dist/cli/context-xray-local.d.ts +16 -0
  66. package/dist/cli/context-xray-local.d.ts.map +1 -0
  67. package/dist/cli/context-xray-local.js +738 -0
  68. package/dist/cli/context-xray-local.js.map +1 -0
  69. package/dist/cli/skills.d.ts +3 -0
  70. package/dist/cli/skills.d.ts.map +1 -1
  71. package/dist/cli/skills.js +200 -73
  72. package/dist/cli/skills.js.map +1 -1
  73. package/dist/cli/templates-meta.js +5 -5
  74. package/dist/cli/templates-meta.js.map +1 -1
  75. package/dist/client/AssistantChat.d.ts.map +1 -1
  76. package/dist/client/AssistantChat.js +2 -1
  77. package/dist/client/AssistantChat.js.map +1 -1
  78. package/dist/client/components/ui/sheet.d.ts +16 -0
  79. package/dist/client/components/ui/sheet.d.ts.map +1 -0
  80. package/dist/client/components/ui/sheet.js +23 -0
  81. package/dist/client/components/ui/sheet.js.map +1 -0
  82. package/dist/client/context-xray/ContextMeter.d.ts +4 -0
  83. package/dist/client/context-xray/ContextMeter.d.ts.map +1 -0
  84. package/dist/client/context-xray/ContextMeter.js +76 -0
  85. package/dist/client/context-xray/ContextMeter.js.map +1 -0
  86. package/dist/client/context-xray/ContextSegmentRow.d.ts +9 -0
  87. package/dist/client/context-xray/ContextSegmentRow.d.ts.map +1 -0
  88. package/dist/client/context-xray/ContextSegmentRow.js +22 -0
  89. package/dist/client/context-xray/ContextSegmentRow.js.map +1 -0
  90. package/dist/client/context-xray/ContextTreemap.d.ts +6 -0
  91. package/dist/client/context-xray/ContextTreemap.d.ts.map +1 -0
  92. package/dist/client/context-xray/ContextTreemap.js +49 -0
  93. package/dist/client/context-xray/ContextTreemap.js.map +1 -0
  94. package/dist/client/context-xray/ContextXRayPanel.d.ts +11 -0
  95. package/dist/client/context-xray/ContextXRayPanel.d.ts.map +1 -0
  96. package/dist/client/context-xray/ContextXRayPanel.js +87 -0
  97. package/dist/client/context-xray/ContextXRayPanel.js.map +1 -0
  98. package/dist/client/context-xray/SegmentProvenancePopover.d.ts +7 -0
  99. package/dist/client/context-xray/SegmentProvenancePopover.d.ts.map +1 -0
  100. package/dist/client/context-xray/SegmentProvenancePopover.js +7 -0
  101. package/dist/client/context-xray/SegmentProvenancePopover.js.map +1 -0
  102. package/dist/client/context-xray/format.d.ts +7 -0
  103. package/dist/client/context-xray/format.d.ts.map +1 -0
  104. package/dist/client/context-xray/format.js +47 -0
  105. package/dist/client/context-xray/format.js.map +1 -0
  106. package/dist/deploy/route-discovery.d.ts.map +1 -1
  107. package/dist/deploy/route-discovery.js +1 -0
  108. package/dist/deploy/route-discovery.js.map +1 -1
  109. package/dist/deploy/workspace-core.d.ts +1 -1
  110. package/dist/deploy/workspace-core.d.ts.map +1 -1
  111. package/dist/deploy/workspace-core.js +1 -0
  112. package/dist/deploy/workspace-core.js.map +1 -1
  113. package/dist/server/action-discovery.d.ts.map +1 -1
  114. package/dist/server/action-discovery.js +20 -0
  115. package/dist/server/action-discovery.js.map +1 -1
  116. package/dist/server/framework-request-handler.d.ts.map +1 -1
  117. package/dist/server/framework-request-handler.js +2 -0
  118. package/dist/server/framework-request-handler.js.map +1 -1
  119. package/dist/server/index.d.ts +1 -0
  120. package/dist/server/index.d.ts.map +1 -1
  121. package/dist/server/index.js +1 -0
  122. package/dist/server/index.js.map +1 -1
  123. package/dist/shared/context-xray.d.ts +58 -0
  124. package/dist/shared/context-xray.d.ts.map +1 -0
  125. package/dist/shared/context-xray.js +17 -0
  126. package/dist/shared/context-xray.js.map +1 -0
  127. package/dist/vite/action-types-plugin.d.ts.map +1 -1
  128. package/dist/vite/action-types-plugin.js +20 -0
  129. package/dist/vite/action-types-plugin.js.map +1 -1
  130. package/package.json +9 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-xray-local.js","sourceRoot":"","sources":["../../src/cli/context-xray-local.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nimport type { ClientId } from \"./mcp-config-writers.js\";\n\nexport interface InstallLocalContextXrayOptions {\n baseDir?: string;\n clients: ClientId[];\n scope: string;\n dryRun?: boolean;\n}\n\nexport interface InstallLocalContextXrayResult {\n commands: string[];\n scriptPath: string;\n written: string[];\n}\n\nconst CONTEXT_XRAY_EXECUTABLE = String.raw`#!/usr/bin/env node\n\"use strict\";\n\nconst childProcess = require(\"node:child_process\");\nconst fs = require(\"node:fs\");\nconst os = require(\"node:os\");\nconst path = require(\"node:path\");\nconst { pathToFileURL } = require(\"node:url\");\n\nconst HOME = os.homedir();\nconst CODEX_DIR = process.env.CODEX_HOME && process.env.CODEX_HOME.trim() ? process.env.CODEX_HOME.trim() : path.join(HOME, \".codex\");\nconst CLAUDE_DIR = path.join(HOME, \".claude\");\nconst OUT_DIR = path.join(CODEX_DIR, \"context-xray\");\nconst SESSION_ID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;\nconst CATEGORIES = [\"user\", \"assistant\", \"tool_call\", \"tool_output\", \"reasoning\", \"instructions\", \"attachment\", \"metadata\", \"other\"];\nconst LABELS = {\n user: \"User asks\",\n assistant: \"Assistant text\",\n tool_call: \"Tool calls\",\n tool_output: \"Tool output\",\n reasoning: \"Reasoning\",\n instructions: \"Instructions/context\",\n attachment: \"Attachments\",\n metadata: \"Metadata\",\n other: \"Other\",\n};\nconst COLORS = {\n user: \"#8ba8ff\",\n assistant: \"#55b982\",\n tool_call: \"#f0a85b\",\n tool_output: \"#e06b73\",\n reasoning: \"#a77be8\",\n instructions: \"#6ac3d5\",\n attachment: \"#d6a85a\",\n metadata: \"#9aa3ad\",\n other: \"#c3c8ce\",\n};\n\nfunction parseArgs(argv) {\n const out = {\n mode: \"current\",\n source: \"both\",\n since: \"7d\",\n last: 12,\n scanLimit: 80,\n project: process.cwd(),\n allProjects: false,\n sessionId: \"\",\n format: \"html\",\n out: \"\",\n open: false,\n port: 0,\n };\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const eat = (flag) => {\n if (arg === flag) return argv[++i] || \"\";\n if (arg.startsWith(flag + \"=\")) return arg.slice(flag.length + 1);\n return undefined;\n };\n let value;\n if (arg === \"threads\" || arg === \"--threads\") out.mode = \"threads\";\n else if (arg === \"trends\" || arg === \"--trends\") out.mode = \"trends\";\n else if (arg === \"current\" || arg === \"--current\") out.mode = \"current\";\n else if ((value = eat(\"--source\")) !== undefined) out.source = value;\n else if ((value = eat(\"--since\")) !== undefined) out.since = value;\n else if ((value = eat(\"--last\")) !== undefined) out.last = Number(value) || out.last;\n else if ((value = eat(\"--scan-limit\")) !== undefined) out.scanLimit = Number(value) || out.scanLimit;\n else if ((value = eat(\"--project\")) !== undefined) out.project = value;\n else if ((value = eat(\"--session-id\")) !== undefined) out.sessionId = value;\n else if ((value = eat(\"--format\")) !== undefined) out.format = value;\n else if ((value = eat(\"--out\")) !== undefined) out.out = value;\n else if ((value = eat(\"--port\")) !== undefined) out.port = Number(value) || 0;\n else if (arg === \"--all-projects\") out.allProjects = true;\n else if (arg === \"--open\") out.open = true;\n else if (arg === \"--json\") out.format = \"json\";\n else if (arg === \"--help\" || arg === \"-h\") out.help = true;\n }\n if (process.env.CLAUDE_CODE_SESSION_ID && !out.sessionId && out.mode === \"current\") {\n out.sessionId = process.env.CLAUDE_CODE_SESSION_ID;\n }\n if (out.mode === \"threads\") {\n out.allProjects = true;\n out.last = Math.max(out.last, 30);\n }\n if (out.mode === \"trends\") {\n out.allProjects = true;\n out.last = Math.max(out.last, 60);\n }\n if (out.mode === \"current\") {\n out.last = 1;\n }\n return out;\n}\n\nfunction help() {\n console.log([\n \"Context X-Ray\",\n \"\",\n \"Usage:\",\n \" context-xray --open Visualize the current/recent local thread\",\n \" context-xray threads --open Pick from recent Codex/Claude sessions\",\n \" context-xray trends --since 7d --open Show recent usage trends\",\n \" context-xray --session-id <id> --open Analyze one exact session\",\n \"\",\n \"Options:\",\n \" --source codex|claude|both\",\n \" --since 24h|7d|2w|ISO\",\n \" --last <n>\",\n \" --all-projects\",\n \" --format html|json\",\n \" --out <path>\",\n ].join(\"\\n\"));\n}\n\nfunction mkdirp(dir) {\n fs.mkdirSync(dir, { recursive: true });\n}\n\nfunction parseSince(value) {\n const now = Date.now();\n const match = String(value || \"7d\").trim().toLowerCase().match(/^(\\d+)([hdw])$/);\n if (match) {\n const amount = Number(match[1]);\n const unit = match[2];\n const mult = unit === \"h\" ? 3600000 : unit === \"d\" ? 86400000 : 604800000;\n return now - amount * mult;\n }\n const parsed = Date.parse(value);\n return Number.isFinite(parsed) ? parsed : now - 7 * 86400000;\n}\n\nfunction readJsonl(file) {\n try {\n return fs.readFileSync(file, \"utf8\").split(/\\r?\\n/).filter(Boolean).map((line) => {\n try {\n return JSON.parse(line);\n } catch {\n return null;\n }\n }).filter(Boolean);\n } catch {\n return [];\n }\n}\n\nfunction compact(value) {\n try {\n const text = JSON.stringify(value);\n return text.length > 250000 ? text.slice(0, 250000) : text;\n } catch {\n return String(value || \"\");\n }\n}\n\nfunction textFrom(value, depth) {\n if (depth > 8 || value == null) return \"\";\n if (typeof value === \"string\") return value.length > 250000 ? value.slice(0, 250000) : value;\n if (typeof value !== \"object\") return \"\";\n if (Array.isArray(value)) return value.map((item) => textFrom(item, depth + 1)).filter(Boolean).join(\"\\n\");\n const skip = new Set([\"encrypted_content\", \"id\", \"uuid\", \"call_id\", \"sessionId\", \"parentUuid\"]);\n const keys = new Set([\"text\", \"message\", \"output\", \"result\", \"content\", \"summary\", \"arguments\", \"args\", \"input\", \"stdout\", \"stderr\", \"attachment\"]);\n const parts = [];\n for (const key of Object.keys(value)) {\n if (skip.has(key)) continue;\n const item = value[key];\n if (keys.has(key) || typeof item === \"object\") {\n const text = textFrom(item, depth + 1);\n if (text) parts.push(text);\n }\n }\n return parts.join(\"\\n\");\n}\n\nfunction addCounter(counter, key, amount) {\n if (!key || !amount) return;\n counter[key] = (counter[key] || 0) + amount;\n}\n\nfunction mergeCounter(into, from) {\n for (const key of Object.keys(from || {})) addCounter(into, key, from[key]);\n}\n\nfunction estimateTokens(chars) {\n return chars > 0 ? Math.max(1, Math.ceil(chars / 4)) : 0;\n}\n\nfunction fmtTokens(tokens) {\n if (tokens >= 1000000) return (tokens / 1000000).toFixed(1) + \"m\";\n if (tokens >= 1000) return (tokens / 1000).toFixed(1) + \"k\";\n return String(tokens);\n}\n\nfunction pct(part, total) {\n return total > 0 ? Math.max(0, Math.min(100, (part / total) * 100)) : 0;\n}\n\nfunction pathCounts(text) {\n const out = {};\n const matches = String(text || \"\").match(/(?:(?:\\/[\\w@.+,=-]+)+|(?:[\\w.-]+\\/)+[\\w.+,=-]+)(?:\\.[A-Za-z0-9_+-]+)?/g) || [];\n for (const raw of matches) {\n const value = raw.replace(/['\",.)]+$/g, \"\");\n if (value.length > 5 && !value.startsWith(\"http\") && value.includes(\"/\")) addCounter(out, value, 1);\n }\n return out;\n}\n\nfunction codexTitle(id) {\n const index = path.join(CODEX_DIR, \"session_index.jsonl\");\n for (const record of readJsonl(index)) {\n if (record.id === id && record.thread_name) return String(record.thread_name);\n }\n return \"\";\n}\n\nfunction observedCodexTokens(payload) {\n if (!payload || payload.type !== \"token_count\" || !payload.info) return 0;\n const last = payload.info.last_token_usage;\n if (last && Number(last.total_tokens)) return Number(last.total_tokens);\n const total = payload.info.total_token_usage;\n if (total && Number(total.total_tokens)) return Number(total.total_tokens);\n if (total && typeof total === \"object\") {\n return [\"input_tokens\", \"cached_input_tokens\", \"output_tokens\", \"reasoning_output_tokens\"].reduce((sum, key) => sum + (Number(total[key]) || 0), 0);\n }\n return 0;\n}\n\nfunction claudeUsageTokens(usage) {\n if (!usage || typeof usage !== \"object\") return 0;\n return (Number(usage.input_tokens) || 0) + (Number(usage.output_tokens) || 0) + (Number(usage.cache_creation_input_tokens) || 0) + (Number(usage.cache_read_input_tokens) || 0);\n}\n\nfunction sessionIdFromPath(file) {\n const match = path.basename(file).match(SESSION_ID_RE);\n return match ? match[0] : path.basename(file, \".jsonl\");\n}\n\nfunction classifyCodex(record) {\n const top = String(record.type || \"\");\n const payload = record.payload && typeof record.payload === \"object\" ? record.payload : {};\n const ptype = String(payload.type || \"\");\n let category = \"other\";\n const tools = {};\n if (top === \"session_meta\") category = \"metadata\";\n else if (top === \"turn_context\") category = \"instructions\";\n else if (top === \"event_msg\") category = ptype === \"user_message\" ? \"user\" : \"metadata\";\n else if (top === \"response_item\") {\n if ([\"function_call\", \"custom_tool_call\", \"web_search_call\", \"tool_search_call\", \"tool_call\"].includes(ptype) || payload.name && payload.call_id) {\n category = \"tool_call\";\n if (payload.name) addCounter(tools, String(payload.name), 1);\n } else if ([\"function_call_output\", \"custom_tool_call_output\", \"tool_search_output\", \"tool_result\"].includes(ptype) || Object.prototype.hasOwnProperty.call(payload, \"output\")) category = \"tool_output\";\n else if (ptype === \"reasoning\" || payload.summary) category = \"reasoning\";\n else if (payload.role === \"assistant\") category = \"assistant\";\n else if (payload.role === \"user\" || payload.role === \"developer\") category = \"user\";\n }\n const text = textFrom(record, 0) || (category === \"metadata\" ? compact(record) : \"\");\n return { category, chars: text.length, tools, paths: pathCounts(text) };\n}\n\nfunction classifyClaude(record) {\n let category = \"other\";\n const tools = {};\n const message = record.message && typeof record.message === \"object\" ? record.message : null;\n let text = \"\";\n if (message) {\n if (message.role === \"user\") category = \"user\";\n else if (message.role === \"assistant\") category = \"assistant\";\n const content = Array.isArray(message.content) ? message.content : [message.content];\n const parts = [];\n for (const part of content) {\n if (part && typeof part === \"object\") {\n if (part.type === \"tool_use\") {\n category = \"tool_call\";\n if (part.name) addCounter(tools, String(part.name), 1);\n } else if (part.type === \"tool_result\") category = \"tool_output\";\n else if (part.type === \"thinking\") category = \"reasoning\";\n }\n parts.push(textFrom(part, 0));\n }\n text = parts.join(\"\\n\");\n } else if (record.toolUseResult) {\n category = \"tool_output\";\n text = textFrom(record.toolUseResult, 0);\n } else if (record.attachment) {\n category = \"attachment\";\n text = textFrom(record.attachment, 0);\n } else {\n text = textFrom(record, 0);\n }\n return { category, chars: text.length, tools, paths: pathCounts(text) };\n}\n\nfunction summarizeCodex(file) {\n const stat = fs.statSync(file);\n const summary = {\n source: \"codex\",\n path: file,\n sessionId: sessionIdFromPath(file),\n title: \"\",\n cwd: \"\",\n startedAt: \"\",\n updatedAt: \"\",\n categories: {},\n tools: {},\n paths: {},\n observedTokens: 0,\n bytes: stat.size,\n mtime: stat.mtimeMs,\n };\n const records = readJsonl(file);\n for (const record of records) {\n const payload = record.payload && typeof record.payload === \"object\" ? record.payload : {};\n if (record.type === \"session_meta\") {\n summary.sessionId = String(payload.id || summary.sessionId);\n summary.cwd = String(payload.cwd || summary.cwd);\n summary.startedAt = String(payload.timestamp || summary.startedAt);\n }\n if (payload.cwd && !summary.cwd) summary.cwd = String(payload.cwd);\n if (record.timestamp) summary.updatedAt = String(record.timestamp);\n summary.observedTokens = Math.max(summary.observedTokens, observedCodexTokens(payload));\n const stats = classifyCodex(record);\n addCounter(summary.categories, stats.category, stats.chars);\n mergeCounter(summary.tools, stats.tools);\n mergeCounter(summary.paths, stats.paths);\n }\n summary.title = codexTitle(summary.sessionId) || summary.sessionId;\n return finalizeSummary(summary);\n}\n\nfunction summarizeClaude(file) {\n const stat = fs.statSync(file);\n const summary = {\n source: \"claude\",\n path: file,\n sessionId: sessionIdFromPath(file),\n title: \"\",\n cwd: \"\",\n startedAt: \"\",\n updatedAt: \"\",\n categories: {},\n tools: {},\n paths: {},\n observedTokens: 0,\n bytes: stat.size,\n mtime: stat.mtimeMs,\n };\n const records = readJsonl(file);\n for (const record of records) {\n summary.sessionId = String(record.sessionId || summary.sessionId);\n summary.cwd = String(record.cwd || summary.cwd);\n if (record.timestamp) {\n summary.updatedAt = String(record.timestamp);\n if (!summary.startedAt) summary.startedAt = String(record.timestamp);\n }\n if (record.message && typeof record.message === \"object\") {\n summary.observedTokens = Math.max(summary.observedTokens, claudeUsageTokens(record.message.usage));\n if (!summary.title && record.message.role === \"user\") summary.title = cleanTitle(textFrom(record.message, 0)).slice(0, 90);\n }\n const stats = classifyClaude(record);\n addCounter(summary.categories, stats.category, stats.chars);\n mergeCounter(summary.tools, stats.tools);\n mergeCounter(summary.paths, stats.paths);\n }\n if (!summary.title) summary.title = summary.sessionId;\n return finalizeSummary(summary);\n}\n\nfunction finalizeSummary(summary) {\n const totalChars = Object.values(summary.categories).reduce((sum, value) => sum + value, 0);\n summary.totalChars = totalChars;\n summary.tokens = summary.observedTokens || estimateTokens(totalChars);\n summary.tokenMethod = summary.observedTokens ? \"observed\" : \"estimated\";\n return summary;\n}\n\nfunction walk(root) {\n const out = [];\n if (!fs.existsSync(root)) return out;\n const visit = (dir) => {\n let entries = [];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const file = path.join(dir, entry.name);\n if (entry.isDirectory()) visit(file);\n else if (entry.isFile() && entry.name.endsWith(\".jsonl\")) out.push(file);\n }\n };\n visit(root);\n return out;\n}\n\nfunction encodedProject(project) {\n return path.resolve(project).replace(/\\//g, \"-\");\n}\n\nfunction pathInsideOrEqual(value, parent) {\n const relative = path.relative(parent, value);\n return relative === \"\" || (!!relative && !relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n\nfunction candidateFiles(source, args) {\n if (args.sessionId) {\n const roots = source === \"codex\" ? [path.join(CODEX_DIR, \"sessions\"), path.join(CODEX_DIR, \"archived_sessions\")] : [path.join(CLAUDE_DIR, \"projects\")];\n return roots.flatMap(walk).filter((file) => file.includes(args.sessionId));\n }\n const since = parseSince(args.since);\n const roots = source === \"codex\" ? [path.join(CODEX_DIR, \"sessions\"), path.join(CODEX_DIR, \"archived_sessions\")] : [path.join(CLAUDE_DIR, \"projects\")];\n const projectFragment = encodedProject(args.project);\n const files = roots.flatMap(walk).filter((file) => {\n let stat;\n try {\n stat = fs.statSync(file);\n } catch {\n return false;\n }\n if (stat.mtimeMs < since) return false;\n if (args.allProjects || source === \"codex\") return true;\n return file.includes(projectFragment) || file.includes(path.basename(args.project));\n }).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);\n return source === \"codex\" && !args.allProjects ? files : files.slice(0, args.scanLimit);\n}\n\nfunction projectMatches(session, args) {\n if (args.allProjects || args.sessionId) return true;\n const project = path.resolve(args.project);\n if (session.cwd && pathInsideOrEqual(path.resolve(session.cwd), project)) return true;\n return session.path.includes(encodedProject(project));\n}\n\nfunction collectSessions(args) {\n const sources = args.source === \"both\" ? [\"codex\", \"claude\"] : [args.source];\n const sessions = [];\n for (const source of sources) {\n for (const file of candidateFiles(source, args)) {\n try {\n const summary = source === \"codex\" ? summarizeCodex(file) : summarizeClaude(file);\n if (projectMatches(summary, args)) sessions.push(summary);\n } catch {}\n }\n }\n if (args.mode === \"current\" || args.sessionId) sessions.sort((a, b) => b.mtime - a.mtime);\n else sessions.sort((a, b) => b.tokens - a.tokens || b.mtime - a.mtime);\n return sessions.slice(0, args.last);\n}\n\nfunction aggregate(sessions) {\n const out = { categories: {}, tools: {}, paths: {} };\n for (const session of sessions) {\n mergeCounter(out.categories, session.categories);\n mergeCounter(out.tools, session.tools);\n mergeCounter(out.paths, session.paths);\n }\n return out;\n}\n\nfunction sortedEntries(counter, limit) {\n return Object.entries(counter || {}).sort((a, b) => b[1] - a[1]).slice(0, limit);\n}\n\nfunction recommendations(sessions) {\n if (!sessions.length) return [\"No matching sessions found. Try context-xray threads --all-projects --since 2w --open.\"];\n const agg = aggregate(sessions);\n const total = Object.values(agg.categories).reduce((sum, value) => sum + value, 0);\n const tips = [];\n const toolOutput = pct(agg.categories.tool_output || 0, total);\n const instructions = pct(agg.categories.instructions || 0, total);\n const assistant = pct(agg.categories.assistant || 0, total);\n const maxSession = sessions.reduce((a, b) => a.tokens > b.tokens ? a : b);\n if (toolOutput > 45) tips.push(\"Tool output dominates context. Prefer targeted rg/sed ranges, cap logs, and ask agents to summarize failing blocks instead of pasting full output.\");\n if (instructions > 25) tips.push(\"Instructions are a large share. Move stable workflow rules into skills or AGENTS/CLAUDE files and keep per-turn prompts short.\");\n if (assistant > 45) tips.push(\"Assistant prose is heavy. Ask for terse progress updates during long runs and save rationale only when it changes decisions.\");\n if (maxSession.tokens > 80000) tips.push(\"The largest session is about \" + fmtTokens(maxSession.tokens) + \" \" + maxSession.tokenMethod + \" tokens. Compact or start a fresh handoff before another big implementation pass.\");\n const topTool = sortedEntries(agg.tools, 1)[0];\n if (topTool && topTool[1] > 20) tips.push(topTool[0] + \" appears \" + topTool[1] + \" times. Batch independent inspection and use parallel reads/searches.\");\n const topPath = sortedEntries(agg.paths, 1)[0];\n if (topPath && topPath[1] > 12) tips.push(topPath[0] + \" appears repeatedly (\" + topPath[1] + \" mentions). Pin a short role summary instead of rereading it.\");\n return tips.length ? tips.slice(0, 6) : [\"Recent sessions look balanced. Keep using focused reads, compact after milestones, and preserve decisions in a skill or repo doc.\"];\n}\n\nfunction escapeHtml(value) {\n return String(value || \"\").replace(/[&<>\"']/g, (ch) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", \"\\\"\": \"&quot;\", \"'\": \"&#39;\" }[ch]));\n}\n\nfunction cleanTitle(value) {\n return String(value || \"\").replace(/<[^>]+>/g, \" \").replace(/\\s+/g, \" \").trim();\n}\n\nfunction categoryBar(categories) {\n const total = Object.values(categories || {}).reduce((sum, value) => sum + value, 0);\n if (!total) return \"<div class=\\\"bar\\\"></div>\";\n return \"<div class=\\\"bar\\\">\" + CATEGORIES.map((cat) => {\n const value = categories[cat] || 0;\n if (!value) return \"\";\n const width = Math.max(1, pct(value, total));\n return \"<span title=\\\"\" + escapeHtml(LABELS[cat]) + \"\\\" style=\\\"width:\" + width.toFixed(2) + \"%;background:\" + COLORS[cat] + \"\\\"></span>\";\n }).join(\"\") + \"</div>\";\n}\n\nfunction renderHtml(sessions, args) {\n const agg = aggregate(sessions);\n const totalTokens = sessions.reduce((sum, s) => sum + s.tokens, 0);\n const tips = recommendations(sessions);\n const categoryRows = CATEGORIES.map((cat) => {\n const chars = agg.categories[cat] || 0;\n if (!chars) return \"\";\n return \"<tr><td>\" + escapeHtml(LABELS[cat]) + \"</td><td>\" + fmtTokens(estimateTokens(chars)) + \"</td><td>\" + pct(chars, Object.values(agg.categories).reduce((sum, value) => sum + value, 0)).toFixed(0) + \"%</td></tr>\";\n }).join(\"\");\n const sessionCards = sessions.map((session) => {\n const cats = Object.entries(session.categories).sort((a, b) => b[1] - a[1]).map((entry) => \"<tr><td>\" + escapeHtml(LABELS[entry[0]] || entry[0]) + \"</td><td>\" + fmtTokens(estimateTokens(entry[1])) + \"</td><td>\" + pct(entry[1], session.totalChars).toFixed(0) + \"%</td></tr>\").join(\"\");\n const tools = sortedEntries(session.tools, 6).map((entry) => \"<span class=\\\"badge\\\">\" + escapeHtml(entry[0]) + \" x\" + entry[1] + \"</span>\").join(\"\");\n const paths = sortedEntries(session.paths, 5).map((entry) => \"<span class=\\\"badge muted\\\">\" + escapeHtml(entry[0]) + \" x\" + entry[1] + \"</span>\").join(\"\");\n return \"<article class=\\\"card session\\\"><div class=\\\"session-head\\\"><div><div class=\\\"eyebrow\\\">\" + escapeHtml(session.source) + \" - \" + escapeHtml(session.updatedAt || \"unknown time\") + \"</div><h3>\" + escapeHtml(session.title || session.sessionId) + \"</h3><p class=\\\"path\\\">\" + escapeHtml(session.cwd || session.path) + \"</p></div><div class=\\\"token-big\\\">\" + fmtTokens(session.tokens) + \"</div></div>\" + categoryBar(session.categories) + \"<div class=\\\"session-grid\\\"><table><tbody>\" + cats + \"</tbody></table><div><div class=\\\"mini-label\\\">Frequent tools</div><div class=\\\"badges\\\">\" + (tools || \"<span class=\\\"muted-text\\\">none detected</span>\") + \"</div><div class=\\\"mini-label\\\">Repeated paths</div><div class=\\\"badges\\\">\" + (paths || \"<span class=\\\"muted-text\\\">none detected</span>\") + \"</div></div></div></article>\";\n }).join(\"\");\n const topTools = sortedEntries(agg.tools, 10).map((entry) => \"<tr><td>\" + escapeHtml(entry[0]) + \"</td><td>\" + entry[1] + \"</td></tr>\").join(\"\");\n const topPaths = sortedEntries(agg.paths, 10).map((entry) => \"<tr><td>\" + escapeHtml(entry[0]) + \"</td><td>\" + entry[1] + \"</td></tr>\").join(\"\");\n const sourceCounts = sessions.reduce((counts, s) => (counts[s.source] = (counts[s.source] || 0) + 1, counts), {});\n return \"<!doctype html><html><head><meta charset=\\\"utf-8\\\"><meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1\\\"><title>Context X-Ray</title><style>\" +\n \"body{margin:0;background:#0e1116;color:#f3f5f8;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;line-height:1.45}main{max-width:1180px;margin:0 auto;padding:32px 20px 56px}header{display:flex;justify-content:space-between;gap:24px;align-items:flex-end;margin-bottom:24px}h1{margin:0;font-size:clamp(28px,5vw,52px);letter-spacing:0}h2{margin:0 0 14px;font-size:18px}h3{margin:3px 0 4px;font-size:17px}p{margin:0}.muted,.path,.eyebrow,.muted-text{color:#9aa3ad}.eyebrow{text-transform:uppercase;letter-spacing:.08em;font-size:11px}.summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin:18px 0}.card{background:linear-gradient(180deg,#151922,#1c2230);border:1px solid #2a3140;border-radius:8px;padding:16px;box-shadow:0 12px 40px rgba(0,0,0,.22)}.stat strong{display:block;font-size:28px;line-height:1.1}.grid{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(280px,.8fr);gap:16px;align-items:start}.bar{display:flex;overflow:hidden;height:14px;border-radius:999px;background:#252b37;margin:12px 0}.bar span{display:block;min-width:2px}.tips li{margin:0 0 9px}.session{margin-top:14px}.session-head{display:flex;justify-content:space-between;gap:16px}.token-big{font-weight:700;font-size:28px;color:#8ba8ff;white-space:nowrap}.session-grid{display:grid;grid-template-columns:260px minmax(0,1fr);gap:14px;margin-top:12px}table{width:100%;border-collapse:collapse;font-size:13px}td{border-top:1px solid #2a3140;padding:7px 0;vertical-align:top}td:last-child{text-align:right;color:#9aa3ad}.mini-label{margin:7px 0 6px;font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:#9aa3ad}.badges{display:flex;flex-wrap:wrap;gap:6px}.badge{display:inline-flex;border:1px solid #2a3140;border-radius:999px;padding:3px 8px;font-size:12px;background:rgba(255,255,255,.04)}.badge.muted{color:#9aa3ad}footer{color:#9aa3ad;margin-top:24px;font-size:12px}@media(max-width:840px){header,.grid,.session-grid{grid-template-columns:1fr;display:grid}.summary{grid-template-columns:repeat(2,minmax(0,1fr))}}\" +\n \"</style></head><body><main><header><div><div class=\\\"eyebrow\\\">Local coding context profile</div><h1>Context X-Ray</h1><p class=\\\"muted\\\">Generated \" + escapeHtml(new Date().toLocaleString()) + \" - mode=\" + escapeHtml(args.mode) + \" - source=\" + escapeHtml(args.source) + \" - since=\" + escapeHtml(args.since) + \"</p></div></header><section class=\\\"summary\\\"><div class=\\\"card stat\\\"><span class=\\\"muted\\\">Sessions</span><strong>\" + sessions.length + \"</strong></div><div class=\\\"card stat\\\"><span class=\\\"muted\\\">Observed/estimated tokens</span><strong>\" + fmtTokens(totalTokens) + \"</strong></div><div class=\\\"card stat\\\"><span class=\\\"muted\\\">Codex</span><strong>\" + (sourceCounts.codex || 0) + \"</strong></div><div class=\\\"card stat\\\"><span class=\\\"muted\\\">Claude</span><strong>\" + (sourceCounts.claude || 0) + \"</strong></div></section><section class=\\\"card\\\"><h2>Where The Context Is Going</h2>\" + categoryBar(agg.categories) + \"<table><tbody>\" + categoryRows + \"</tbody></table></section><div class=\\\"grid\\\" style=\\\"margin-top:16px\\\"><section class=\\\"card tips\\\"><h2>Warnings And Optimizations</h2><ol>\" + tips.map((tip) => \"<li>\" + escapeHtml(tip) + \"</li>\").join(\"\") + \"</ol></section><section class=\\\"card\\\"><h2>Hotspots</h2><div class=\\\"mini-label\\\">Top tools</div><table><tbody>\" + (topTools || \"<tr><td>None detected</td><td></td></tr>\") + \"</tbody></table><div class=\\\"mini-label\\\">Top paths</div><table><tbody>\" + (topPaths || \"<tr><td>None detected</td><td></td></tr>\") + \"</tbody></table></section></div><section style=\\\"margin-top:16px\\\"><h2>Sessions</h2>\" + sessionCards + \"</section><footer>Reads local transcript files only. No transcript content is uploaded.</footer></main></body></html>\";\n}\n\nfunction writeJson(sessions, args, file) {\n const agg = aggregate(sessions);\n fs.writeFileSync(file, JSON.stringify({\n generatedAt: new Date().toISOString(),\n mode: args.mode,\n source: args.source,\n since: args.since,\n totalTokens: sessions.reduce((sum, s) => sum + s.tokens, 0),\n categories: agg.categories,\n tools: sortedEntries(agg.tools, 25),\n paths: sortedEntries(agg.paths, 25),\n recommendations: recommendations(sessions),\n sessions,\n }, null, 2));\n}\n\nfunction openUrl(url) {\n const cmd = process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"cmd\" : \"xdg-open\";\n const args = process.platform === \"win32\" ? [\"/c\", \"start\", \"\", url] : [url];\n try {\n childProcess.spawn(cmd, args, { detached: true, stdio: \"ignore\" }).unref();\n } catch {}\n}\n\nfunction printSummary(sessions, args, file, url) {\n const total = sessions.reduce((sum, s) => sum + s.tokens, 0);\n console.log(\"Context X-Ray: analyzed \" + sessions.length + \" session(s), about \" + fmtTokens(total) + \" observed/estimated tokens.\");\n if (url) console.log(\"Open: \" + url);\n else console.log(\"Report: \" + file);\n console.log(\"\");\n for (const tip of recommendations(sessions).slice(0, 4)) console.log(\"- \" + tip);\n const tools = sortedEntries(aggregate(sessions).tools, 5);\n if (tools.length) console.log(\"- Frequent tools: \" + tools.map((entry) => entry[0] + \" x\" + entry[1]).join(\", \"));\n}\n\nfunction main() {\n const args = parseArgs(process.argv.slice(2));\n if (args.help) return help();\n const sessions = collectSessions(args);\n mkdirp(OUT_DIR);\n const suffix = args.format === \"json\" ? \"json\" : \"html\";\n const file = args.out || path.join(OUT_DIR, \"context-xray-\" + new Date().toISOString().replace(/[:.]/g, \"-\") + \".\" + suffix);\n mkdirp(path.dirname(file));\n if (args.format === \"json\") writeJson(sessions, args, file);\n else fs.writeFileSync(file, renderHtml(sessions, args));\n const url = args.open && args.format === \"html\" ? pathToFileURL(file).href : \"\";\n if (url) openUrl(url);\n printSummary(sessions, args, file, url);\n}\n\nmain();\n`;\n\nexport const CONTEXT_XRAY_SKILL_MD = `---\nname: context-xray\ndescription: >-\n Visualize local Codex and Claude Code context usage, open an inline/browser\n report, flag warnings, and suggest prompt/tooling optimizations. Use when the\n user types /context-xray, asks where context is going, wants recent local\n coding-agent trends, or wants to improve context efficiency.\nmetadata:\n visibility: exported\n---\n\n# Context X-Ray\n\nUse the locally installed Context X-Ray command to visualize recent Codex and\nClaude Code context usage. It reads local transcript files only and does not\nupload transcript content.\n\nProject-scoped installs write only project \\`.agents\\` skill and command\nartifacts; user-scoped installs write global Codex/Claude instructions.\n\n## Run\n\nCurrent or most recent local thread:\n\n\\`\\`\\`sh\n~/.agent-native/context-xray/context-xray --open\n\\`\\`\\`\n\nThread picker / recent sessions:\n\n\\`\\`\\`sh\n~/.agent-native/context-xray/context-xray threads --open\n\\`\\`\\`\n\nWeekly trends:\n\n\\`\\`\\`sh\n~/.agent-native/context-xray/context-xray trends --since 7d --open\n\\`\\`\\`\n\nExact session when the host exposes one:\n\n\\`\\`\\`sh\n~/.agent-native/context-xray/context-xray --session-id \"$CLAUDE_CODE_SESSION_ID\" --open\n\\`\\`\\`\n\nAfter running, report the link, the number of sessions analyzed, the largest\ncontext buckets, and 3-5 specific optimizations.\n\\`--open\\` opens the generated local HTML file directly and does not keep a\nbackground report server running.\n\n## Interpret\n\n- Tool output heavy: use narrower commands, smaller file ranges, and summarized\n logs.\n- Instructions heavy: move stable behavior into skills or AGENTS/CLAUDE files.\n- Assistant prose heavy: ask for shorter status updates during long runs.\n- One huge session: compact or start a follow-up thread with a handoff summary.\n- Repeated path: pin a short file-role summary instead of rereading the file.\n- Repeated tool: batch independent searches or delegate parallel inspection.\n`;\n\nexport const CONTEXT_XRAY_COMMAND_MD = `---\ndescription: Visualize local Codex/Claude context usage and get optimization tips.\nargument-hint: [current|threads|trends|--since 7d]\n---\n\nRun Context X-Ray locally and show the user the generated report link plus the\ntop warnings.\n\nChoose the command from the user's arguments:\n\n- No arguments or \\`current\\`:\n \\`~/.agent-native/context-xray/context-xray --open\\`\n- \\`threads\\`:\n \\`~/.agent-native/context-xray/context-xray threads --open\\`\n- \\`trends\\`:\n \\`~/.agent-native/context-xray/context-xray trends --since 7d --open\\`\n\nIf \\`$ARGUMENTS\\` includes flags such as \\`--since 24h\\`, \\`--last 20\\`, or\n\\`--all-projects\\`, pass them through to the command. If the host exposes\n\\`CLAUDE_CODE_SESSION_ID\\`, prefer:\n\n\\`\\`\\`sh\n~/.agent-native/context-xray/context-xray --session-id \"$CLAUDE_CODE_SESSION_ID\" --open\n\\`\\`\\`\n\n\\`--open\\` opens a local HTML report file directly; there should not be a\nlong-running server process to monitor.\n\nAfter the command finishes, summarize:\n\n- the report link\n- sessions analyzed\n- the largest context bucket\n- the most important warning\n- two or three concrete ways to improve this thread\n`;\n\nfunction codexHome(): string {\n return process.env.CODEX_HOME?.trim() || path.join(os.homedir(), \".codex\");\n}\n\nfunction writeExecutable(file: string, content: string): void {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, content, \"utf-8\");\n fs.chmodSync(file, 0o755);\n}\n\nfunction writeFile(file: string, content: string, written: string[]): void {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, content, \"utf-8\");\n written.push(file);\n}\n\nfunction installProjectArtifacts(baseDir: string, written: string[]): void {\n writeFile(\n path.join(baseDir, \".agents\", \"skills\", \"context-xray\", \"SKILL.md\"),\n CONTEXT_XRAY_SKILL_MD,\n written,\n );\n writeFile(\n path.join(baseDir, \".agents\", \"commands\", \"context-xray.md\"),\n CONTEXT_XRAY_COMMAND_MD,\n written,\n );\n}\n\nexport function installLocalContextXray(\n options: InstallLocalContextXrayOptions,\n): InstallLocalContextXrayResult {\n const installDir = path.join(os.homedir(), \".agent-native\", \"context-xray\");\n const scriptPath = path.join(installDir, \"context-xray\");\n const binPath = path.join(os.homedir(), \".local\", \"bin\", \"context-xray\");\n const written: string[] = [];\n\n if (options.dryRun) {\n return {\n commands: [\"context-xray --open\"],\n scriptPath,\n written,\n };\n }\n\n writeExecutable(scriptPath, CONTEXT_XRAY_EXECUTABLE);\n written.push(scriptPath);\n if (process.platform === \"win32\") {\n const cmdPath = `${binPath}.cmd`;\n writeExecutable(\n cmdPath,\n `@echo off\\r\\nnode ${JSON.stringify(scriptPath)} %*\\r\\n`,\n );\n written.push(cmdPath);\n } else {\n writeExecutable(\n binPath,\n `#!/usr/bin/env sh\\nexec ${JSON.stringify(scriptPath)} \"$@\"\\n`,\n );\n written.push(binPath);\n }\n\n const clientSet = new Set(options.clients);\n const wantsCodex = clientSet.has(\"codex\");\n const wantsClaude =\n clientSet.has(\"claude-code\") || clientSet.has(\"claude-code-cli\");\n\n if (options.scope === \"project\" && options.baseDir) {\n installProjectArtifacts(options.baseDir, written);\n } else if (wantsCodex) {\n writeFile(\n path.join(codexHome(), \"skills\", \"context-xray\", \"SKILL.md\"),\n CONTEXT_XRAY_SKILL_MD,\n written,\n );\n writeFile(\n path.join(codexHome(), \"commands\", \"context-xray.md\"),\n CONTEXT_XRAY_COMMAND_MD,\n written,\n );\n }\n\n if (options.scope !== \"project\" && wantsClaude) {\n writeFile(\n path.join(os.homedir(), \".claude\", \"skills\", \"context-xray\", \"SKILL.md\"),\n CONTEXT_XRAY_SKILL_MD,\n written,\n );\n writeFile(\n path.join(os.homedir(), \".claude\", \"commands\", \"context-xray.md\"),\n CONTEXT_XRAY_COMMAND_MD,\n written,\n );\n }\n\n return {\n commands: [\"context-xray --open\"],\n scriptPath,\n written,\n };\n}\n"]}
@@ -34,6 +34,9 @@ export interface SkillsAddResult {
34
34
  mcpClients: ClientId[];
35
35
  dryRun: boolean;
36
36
  commands: string[];
37
+ local?: boolean;
38
+ scriptPath?: string;
39
+ written?: string[];
37
40
  }
38
41
  interface RunCommandOptions {
39
42
  stdio?: "inherit" | "stderr" | "silent";
@@ -1 +1 @@
1
- {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/cli/skills.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAoBH,OAAO,EAAW,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAocjE,KAAK,aAAa,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,GAAG,EAAE,OAAO,CAAC;IACb;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAWD,UAAU,iBAAiB;IACzB,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;CACzC;AAED,UAAU,gBAAgB;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC;IAC9B,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,aAAa,CAAC,EAAE,CACd,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,CACb,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IAC9B,UAAU,CAAC,EAAE,CACX,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,iBAAiB,KACxB,OAAO,CAAC,MAAM,CAAC,CAAC;CACtB;AAED,UAAU,yBAAyB;IACjC,cAAc,EAAE,QAAQ,EAAE,CAAC;IAC3B,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE;AAED,UAAU,yBAAyB;IACjC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChE;AAsID,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAgEhE;AAkND,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAkG1B;AAeD,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA0Ff"}
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/cli/skills.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH,OAAO,EAAW,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AA4gBjE,KAAK,aAAa,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,GAAG,EAAE,OAAO,CAAC;IACb;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAWD,UAAU,iBAAiB;IACzB,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;CACzC;AAED,UAAU,gBAAgB;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC;IAC9B,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,aAAa,CAAC,EAAE,CACd,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,CACb,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IAC9B,UAAU,CAAC,EAAE,CACX,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,iBAAiB,KACxB,OAAO,CAAC,MAAM,CAAC,CAAC;CACtB;AAED,UAAU,yBAAyB;IACjC,cAAc,EAAE,QAAQ,EAAE,CAAC;IAC3B,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE;AAED,UAAU,yBAAyB;IACjC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChE;AA4ID,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAgEhE;AAkND,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAiJ1B;AAgBD,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAyGf"}
@@ -9,18 +9,20 @@ import path from "node:path";
9
9
  import { spawn } from "node:child_process";
10
10
  import { buildAppSkillPack, ensureAppSkill, loadAppSkillManifest, normalizeAppSkillManifest, } from "./app-skill.js";
11
11
  import { readConnectClientPreferences, resolveClients, writeConnectClientPreferences, } from "./connect.js";
12
+ import { CONTEXT_XRAY_SKILL_MD, installLocalContextXray, } from "./context-xray-local.js";
12
13
  import { CLIENTS } from "./mcp-config-writers.js";
13
14
  const HELP = `agent-native skills
14
15
 
15
16
  Usage:
16
17
  agent-native skills list
17
- agent-native skills add assets|design-exploration|contracts [--client codex|claude-code|claude-code-cli|cowork|all] [--scope user|project] [--mcp-url <url>] [--yes] [--dry-run] [--json]
18
+ agent-native skills add assets|design-exploration|visual-plans|context-xray [--client codex|claude-code|claude-code-cli|cowork|all] [--scope user|project] [--mcp-url <url>] [--yes] [--dry-run] [--json]
18
19
  agent-native skills add <manifest-or-app-dir> [--client ...] [--yes]
19
20
 
20
21
  Examples:
21
22
  agent-native skills add assets
22
23
  agent-native skills add design-exploration
23
- agent-native skills add contracts
24
+ agent-native skills add visual-plans
25
+ agent-native skills add context-xray --client all
24
26
  agent-native skills add assets --client claude-code
25
27
  agent-native skills add assets --mcp-url https://my-app.ngrok-free.dev
26
28
  agent-native skills add ./dist/assets-skill --client codex
@@ -183,33 +185,37 @@ iteration, or a human-in-the-loop choice among design directions.
183
185
  - If you inspect local MCP config, redact \`Authorization\`, \`http_headers\`,
184
186
  and token values. Never paste bearer tokens into chat or logs.
185
187
  `;
186
- const CONTRACTS_SKILL_MD = `---
187
- name: contracts
188
+ const VISUAL_PLANS_SKILL_MD = `---
189
+ name: visual-plans
188
190
  description: >-
189
- Use Contracts for coding-agent work that needs assumption review, mid-flight
190
- feedback, acceptance criteria, evidence capture, and proof-before-done
191
- through the hosted Contracts MCP app.
191
+ Use Visual Plans for coding-agent work that needs an interactive HTML plan,
192
+ diagrams, wireframes, prototype options, annotations, implementation tasks,
193
+ feedback, and proof gates through the hosted Visual Plans MCP app.
192
194
  metadata:
193
195
  visibility: exported
194
196
  ---
195
197
 
196
- # Contracts
198
+ # Visual Plans
197
199
 
198
- Use Contracts as the trust layer for non-trivial coding work. It records what
199
- the agent is assuming, lets a human correct those assumptions before they become
200
- code, and keeps acceptance criteria separate from verified evidence.
200
+ Use Visual Plans as HTML plan mode for coding work. The point is not to create a
201
+ prettier Markdown plan. The point is to give the user something visual to react
202
+ to before the agent edits code: diagrams, wireframes, option cards, clickable
203
+ prototype sketches, assumptions, tasks, annotations, and proof gates.
204
+
205
+ Text is the fallback layer. Default to visual artifacts.
201
206
 
202
207
  ## When To Use
203
208
 
204
- Create or update a contract when:
209
+ Create or update a visual plan when:
205
210
 
206
- - the user asks for Contracts, specs, proof, review, acceptance criteria, or a
207
- structured plan;
208
- - work is multi-file, ambiguous, long-running, or risky;
211
+ - the user asks for a plan, visual plan, HTML plan, plannotate-style review,
212
+ diagrams, wireframes, mockups, prototype options, comments, or annotations;
213
+ - work is multi-file, ambiguous, long-running, risky, or UI-heavy;
214
+ - the user needs to react quickly to direction rather than read prose;
209
215
  - the task touches auth, billing, migrations, public APIs, tests, production
210
216
  config, data, security, permissions, or deploy behavior;
211
217
  - you would otherwise proceed on a material assumption;
212
- - you are about to claim the work is complete.
218
+ - you are about to claim the work is complete and need proof gates checked.
213
219
 
214
220
  Do not log every trivial inference. An assumption is material when changing it
215
221
  would affect user-visible behavior, data model, permissions, billing, public API
@@ -218,52 +224,60 @@ deployment/configuration, file scope, or the definition of done.
218
224
 
219
225
  ## Core Workflow
220
226
 
221
- 1. Call \`create-contract\` with the goal, source, repo path, and initial
222
- assumptions/criteria before risky implementation.
223
- 2. Surface the returned Contracts UI link or inline MCP App. In CLI hosts, tell
224
- the user to open the link and review the queue.
225
- 3. Call \`get-feedback\` before risky edits, after review, after any long pause,
227
+ 1. Call \`create-visual-plan\` with the goal, source, repo path, and initial
228
+ plan nodes before implementation.
229
+ 2. Surface the returned Visual Plans link or inline MCP App. In CLI hosts, tell
230
+ the user to open the link and review the visual plan.
231
+ 3. Prefer diagrams, wireframes, UI mockups, option cards, and small interactive
232
+ prototypes over paragraphs.
233
+ 4. Call \`get-plan-feedback\` before editing, after review, after any long pause,
226
234
  and before the final response.
227
- 4. If the user accepts, rejects, corrects, or requests evidence, consume the
228
- structured feedback and change your plan accordingly.
229
- 5. If new facts require a change after approval, create an \`amendment\` or
230
- \`deviation\` item with \`upsert-contract-items\` instead of drifting silently.
231
- 6. Attach command/test/log/diff/screenshot evidence with \`record-evidence\`.
232
- 7. Do not treat your own claim as proof. Agent attestation is low trust.
233
- Criteria are done only when verified by human, CI, deterministic checks, or
234
- an independent verifier.
235
- 8. Export a JSON/Markdown receipt with \`export-contract\` when the user wants a
236
- shareable summary.
235
+ 5. If the user comments, accepts, rejects, corrects, or requests proof, consume
236
+ the structured feedback and update the implementation plan accordingly.
237
+ 6. If new facts require a change after approval, create an amendment or
238
+ deviation with \`update-visual-plan\` instead of drifting silently.
239
+ 7. Attach command/test/log/diff/screenshot/design artifacts with
240
+ \`record-plan-evidence\`. Agent claims are not proof.
241
+ 8. Export an HTML/JSON/Markdown receipt with \`export-visual-plan\` when the
242
+ user wants a shareable summary.
243
+
244
+ ## Visual Defaults
245
+
246
+ - UI work gets wireframes or prototype options before coding.
247
+ - Backend/refactor work gets architecture and data-flow diagrams.
248
+ - Complex tradeoffs get two or three option cards with consequences.
249
+ - Assumptions are shown as reviewable visual callouts, not hidden prose.
250
+ - Proof gates stay compact: what must pass, current evidence, and missing proof.
251
+ - Long prose is collapsed behind the visual plan.
237
252
 
238
253
  ## Tool Guidance
239
254
 
240
- - \`create-contract\`: start one contract per agent task/run.
241
- - \`upsert-contract-items\`: bulk add/update assumptions, decisions, criteria,
242
- risks, deviations, open questions, and amendments.
243
- - \`get-contract\` and \`get-review-queue\`: read current structured state.
244
- - \`get-feedback\`: read unconsumed human feedback. Use it frequently.
245
- - \`record-progress\`: update phase/status and mark feedback consumed only after
246
- you incorporated it.
247
- - \`record-evidence\`: attach artifacts and provenance. Use high trust for
255
+ - \`create-visual-plan\`: start one visual plan per agent task/run.
256
+ - \`update-visual-plan\`: bulk add/update plan nodes, options, assumptions,
257
+ decisions, tasks, risks, deviations, annotations, and proof gates.
258
+ - \`get-visual-plan\` and \`get-plan-review-queue\`: read current plan state.
259
+ - \`get-plan-feedback\`: read unconsumed human feedback. Use it frequently.
260
+ - \`record-plan-progress\`: update phase/status and mark feedback consumed only
261
+ after you incorporated it.
262
+ - \`record-plan-evidence\`: attach artifacts and provenance. Use high trust for
248
263
  captured commands/tests/CI, human_confirmed for explicit human confirmation,
249
264
  and low trust for agent-only statements.
250
- - \`analyze-plan\`: import pasted plan text and let Contracts create possible
251
- assumptions/criteria. Treat detections as possible, not authoritative.
265
+ - \`analyze-visual-plan\`: import pasted Markdown/text and create possible
266
+ visual plan nodes. Treat detections as possible, not authoritative.
252
267
 
253
268
  ## Guardrails
254
269
 
270
+ - Keep it simple. Do not build a ten-tab dashboard unless the user asks.
255
271
  - Before high-risk actions, create a blocking review item or ask the user
256
272
  directly.
257
- - Never modify tests merely to make implementation pass unless the contract
273
+ - Never modify tests merely to make implementation pass unless the visual plan
258
274
  explicitly approves test expectation changes.
259
275
  - If proof is missing, say so. Do not call the task complete just because code
260
276
  was changed.
261
- - If evidence contains secrets or tokens, rely on Contracts redaction and avoid
262
- pasting raw output into chat.
263
277
  - Do not hand-roll MCP HTTP requests with curl. Use host-exposed tools after
264
278
  restart/reload, or use the returned browser/deep-link fallback.
265
279
  - Hosted default: connect
266
- \`https://contracts.agent-native.com/_agent-native/mcp\`. Do not put shared
280
+ \`https://plans.agent-native.com/_agent-native/mcp\`. Do not put shared
267
281
  secrets in skill files.
268
282
  `;
269
283
  const BUILT_IN_APP_SKILLS = {
@@ -353,34 +367,34 @@ const BUILT_IN_APP_SKILLS = {
353
367
  }),
354
368
  skillMarkdown: DESIGN_EXPLORATION_SKILL_MD,
355
369
  },
356
- contracts: {
357
- skillName: "contracts",
370
+ "visual-plans": {
371
+ skillName: "visual-plans",
358
372
  manifest: normalizeAppSkillManifest({
359
373
  schemaVersion: 1,
360
- id: "contracts",
361
- displayName: "Contracts",
362
- description: "Review coding-agent assumptions, feedback, acceptance criteria, and proof before work is called done.",
374
+ id: "visual-plans",
375
+ displayName: "Visual Plans",
376
+ description: "Review coding-agent plans as interactive HTML with diagrams, wireframes, annotations, and proof gates.",
363
377
  hosted: {
364
- url: "https://contracts.agent-native.com",
365
- mcpUrl: "https://contracts.agent-native.com/_agent-native/mcp",
378
+ url: "https://plans.agent-native.com",
379
+ mcpUrl: "https://plans.agent-native.com/_agent-native/mcp",
366
380
  },
367
- mcp: { serverName: "agent-native-contracts" },
381
+ mcp: { serverName: "agent-native-visual-plans" },
368
382
  auth: {
369
383
  mode: "oauth",
370
- setup: "Authenticate with the Contracts MCP connector in the host app. No shared secrets are stored in skill files.",
384
+ setup: "Authenticate with the Visual Plans MCP connector in the host app. No shared secrets are stored in skill files.",
371
385
  },
372
386
  surfaces: [
373
387
  {
374
- id: "review-inbox",
375
- action: "create-contract",
376
- path: "/contracts",
388
+ id: "visual-plan",
389
+ action: "create-visual-plan",
390
+ path: "/plans",
377
391
  },
378
392
  ],
379
393
  skills: [
380
394
  {
381
- path: "skills/contracts",
395
+ path: "skills/visual-plans",
382
396
  visibility: "exported",
383
- exportAs: "contracts",
397
+ exportAs: "visual-plans",
384
398
  },
385
399
  ],
386
400
  hostAdapters: [
@@ -393,7 +407,38 @@ const BUILT_IN_APP_SKILLS = {
393
407
  "generic-mcp",
394
408
  ],
395
409
  }),
396
- skillMarkdown: CONTRACTS_SKILL_MD,
410
+ skillMarkdown: VISUAL_PLANS_SKILL_MD,
411
+ },
412
+ "context-xray": {
413
+ skillName: "context-xray",
414
+ localOnly: true,
415
+ manifest: normalizeAppSkillManifest({
416
+ schemaVersion: 1,
417
+ id: "context-xray",
418
+ displayName: "Context X-Ray",
419
+ description: "Visualize local Codex and Claude Code context usage with warnings and optimization tips.",
420
+ hosted: {
421
+ url: "https://context-xray.agent-native.com",
422
+ mcpUrl: "https://context-xray.agent-native.com/_agent-native/mcp",
423
+ },
424
+ mcp: { serverName: "agent-native-context-xray" },
425
+ auth: { mode: "none" },
426
+ surfaces: [
427
+ {
428
+ id: "context-xray-report",
429
+ path: "/",
430
+ },
431
+ ],
432
+ skills: [
433
+ {
434
+ path: "skills/context-xray",
435
+ visibility: "exported",
436
+ exportAs: "context-xray",
437
+ },
438
+ ],
439
+ hostAdapters: ["plain-skill", "claude-skill"],
440
+ }),
441
+ skillMarkdown: CONTEXT_XRAY_SKILL_MD,
397
442
  },
398
443
  };
399
444
  const BUILT_IN_APP_SKILL_ALIASES = {
@@ -412,12 +457,27 @@ const BUILT_IN_APP_SKILL_ALIASES = {
412
457
  "ux-exploration": "design",
413
458
  "agent-native-design": "design",
414
459
  "agent-native-design-exploration": "design",
415
- contracts: "contracts",
416
- contract: "contracts",
417
- proof: "contracts",
418
- "proof-check": "contracts",
419
- "assumption-review": "contracts",
420
- "agent-native-contracts": "contracts",
460
+ "visual-plans": "visual-plans",
461
+ "visual-plan": "visual-plans",
462
+ plans: "visual-plans",
463
+ plan: "visual-plans",
464
+ "html-plan": "visual-plans",
465
+ "plan-mode": "visual-plans",
466
+ plannotate: "visual-plans",
467
+ plannotator: "visual-plans",
468
+ contracts: "visual-plans",
469
+ contract: "visual-plans",
470
+ proof: "visual-plans",
471
+ "proof-check": "visual-plans",
472
+ "assumption-review": "visual-plans",
473
+ "agent-native-contracts": "visual-plans",
474
+ "agent-native-visual-plans": "visual-plans",
475
+ "context-xray": "context-xray",
476
+ "local-context-xray": "context-xray",
477
+ xray: "context-xray",
478
+ "context-window": "context-xray",
479
+ "context-usage": "context-xray",
480
+ "agent-native-context-xray": "context-xray",
421
481
  };
422
482
  const BUILT_IN_APP_SKILL_DISPLAY_ALIASES = {
423
483
  assets: ["images", "image-generation", "agent-native-images"],
@@ -426,7 +486,14 @@ const BUILT_IN_APP_SKILL_DISPLAY_ALIASES = {
426
486
  "ux-exploration",
427
487
  "agent-native-design-exploration",
428
488
  ],
429
- contracts: ["contract", "proof-check", "assumption-review"],
489
+ "visual-plans": [
490
+ "plans",
491
+ "html-plan",
492
+ "plannotate",
493
+ "contracts",
494
+ "proof-check",
495
+ ],
496
+ "context-xray": ["xray", "context-window", "context-usage"],
430
497
  };
431
498
  const CLIENT_LABELS = {
432
499
  "claude-code": "Claude Code",
@@ -449,6 +516,9 @@ function normalizeKnownSkillTarget(value) {
449
516
  function isKnownSkill(value) {
450
517
  return Boolean(normalizeKnownSkillTarget(value));
451
518
  }
519
+ function isLocalOnlyBuiltInSkill(entry) {
520
+ return Boolean(entry && "localOnly" in entry && entry.localOnly);
521
+ }
452
522
  function normalizeClientIds(values) {
453
523
  if (!Array.isArray(values))
454
524
  return [];
@@ -823,6 +893,49 @@ export async function addAgentNativeSkill(parsed, options = {}) {
823
893
  if (!knownTarget && !fs.existsSync(path.resolve(target))) {
824
894
  throw new Error(`Unknown skill or manifest path: ${target}. Run "agent-native skills list".`);
825
895
  }
896
+ const knownBuiltIn = knownTarget ? BUILT_IN_APP_SKILLS[knownTarget] : null;
897
+ if (isLocalOnlyBuiltInSkill(knownBuiltIn)) {
898
+ if (parsed.mcpUrl) {
899
+ throw new Error("Context X-Ray is installed locally and does not use --mcp-url yet.");
900
+ }
901
+ if (!parsed.instructions && parsed.mcp) {
902
+ throw new Error("Context X-Ray does not need MCP config yet. Run without --mcp-only.");
903
+ }
904
+ const clients = parsed.clients ?? resolveClients(parsed.client);
905
+ const skillsAgents = skillsAgentsForClients(clients);
906
+ if (parsed.dryRun) {
907
+ return {
908
+ id: knownBuiltIn.manifest.id,
909
+ displayName: knownBuiltIn.manifest.displayName,
910
+ skillNames: [knownBuiltIn.skillName],
911
+ skillsAgents,
912
+ mcpUrl: "",
913
+ mcpClients: [],
914
+ dryRun: true,
915
+ local: true,
916
+ commands: [dryRunInstallCommand(parsed, target)],
917
+ };
918
+ }
919
+ const localInstall = installLocalContextXray({
920
+ baseDir: options.baseDir ?? process.cwd(),
921
+ clients,
922
+ scope: parsed.scope,
923
+ });
924
+ return {
925
+ id: knownBuiltIn.manifest.id,
926
+ displayName: knownBuiltIn.manifest.displayName,
927
+ instructionSource: localInstall.scriptPath,
928
+ skillNames: [knownBuiltIn.skillName],
929
+ skillsAgents,
930
+ mcpUrl: "",
931
+ mcpClients: [],
932
+ dryRun: false,
933
+ local: true,
934
+ scriptPath: localInstall.scriptPath,
935
+ written: localInstall.written,
936
+ commands: localInstall.commands,
937
+ };
938
+ }
826
939
  let installTarget = loadSkillTarget(target);
827
940
  if (parsed.mcpUrl) {
828
941
  installTarget = withMcpUrlOverride(installTarget, parsed.mcpUrl);
@@ -916,7 +1029,8 @@ function listSkills() {
916
1029
  aliases: BUILT_IN_APP_SKILL_DISPLAY_ALIASES[entry.manifest.id] ?? [],
917
1030
  name: entry.manifest.displayName,
918
1031
  description: entry.manifest.description,
919
- mcpUrl: entry.manifest.hosted.mcpUrl,
1032
+ mcpUrl: isLocalOnlyBuiltInSkill(entry) ? "" : entry.manifest.hosted.mcpUrl,
1033
+ local: isLocalOnlyBuiltInSkill(entry),
920
1034
  }));
921
1035
  }
922
1036
  export async function runSkills(argv, options = {}) {
@@ -939,7 +1053,8 @@ export async function runSkills(argv, options = {}) {
939
1053
  const aliases = skill.aliases.length
940
1054
  ? ` Aliases: ${skill.aliases.join(", ")}.`
941
1055
  : "";
942
- process.stdout.write(`${skill.id.padEnd(12)} ${description}${aliases} (${skill.mcpUrl})\n`);
1056
+ const target = skill.local ? "local command" : skill.mcpUrl;
1057
+ process.stdout.write(`${skill.id.padEnd(12)} ${description}${aliases} (${target})\n`);
943
1058
  }
944
1059
  return;
945
1060
  }
@@ -976,15 +1091,27 @@ export async function runSkills(argv, options = {}) {
976
1091
  const mcpClients = [
977
1092
  ...new Set(results.flatMap((result) => result.mcpClients)),
978
1093
  ];
979
- const mcpUrls = [...new Set(results.map((result) => result.mcpUrl))];
1094
+ const mcpUrls = [
1095
+ ...new Set(results.map((result) => result.mcpUrl).filter(Boolean)),
1096
+ ];
1097
+ const localCommands = [
1098
+ ...new Set(results
1099
+ .filter((result) => result.local)
1100
+ .flatMap((result) => result.commands)),
1101
+ ];
980
1102
  process.stdout.write([
981
1103
  `Installed ${installedNames} skill${results.length === 1 ? "" : "s"}.`,
982
1104
  skillsAgents.length
983
1105
  ? `Skill instructions: ${skillsAgents.join(", ")}.`
984
1106
  : "Skill instructions: skipped.",
985
- `MCP config: ${mcpClients.join(", ")}.`,
986
- `MCP URL${mcpUrls.length === 1 ? "" : "s"}: ${mcpUrls.join(", ")}.`,
987
- "Restart or reload selected agent clients if the tools are not visible yet.",
1107
+ mcpClients.length
1108
+ ? `MCP config: ${mcpClients.join(", ")}.`
1109
+ : "MCP config: not required.",
1110
+ mcpUrls.length
1111
+ ? `MCP URL${mcpUrls.length === 1 ? "" : "s"}: ${mcpUrls.join(", ")}.`
1112
+ : "",
1113
+ localCommands.length ? `Local command: ${localCommands.join(", ")}.` : "",
1114
+ "Restart or reload selected agent clients if the skill is not visible yet.",
988
1115
  parsed.clientExplicit
989
1116
  ? ""
990
1117
  : `To add another client later, rerun with --client <client> (for example: --client claude-code).`,