codehere 0.8.0 → 1.0.2

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 (119) hide show
  1. package/README.md +37 -184
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +329 -0
  5. package/dist/index.js +74 -1004
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
  7. package/dist/presentation/cli/commands/check-command.js +251 -0
  8. package/dist/presentation/cli/commands/help-command.js +22 -286
  9. package/dist/ui.js +19 -438
  10. package/dist/utils/version.js +0 -12
  11. package/package.json +18 -27
  12. package/dist/application/use-cases/domain-trust.js +0 -110
  13. package/dist/application/use-cases/generate-final-report.js +0 -134
  14. package/dist/application/use-cases/generate-narration.js +0 -128
  15. package/dist/application/use-cases/generate-thread-title.js +0 -178
  16. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  17. package/dist/application/use-cases/thread-export.js +0 -155
  18. package/dist/application/use-cases/verify-claim.js +0 -274
  19. package/dist/application/use-cases/write-pass.js +0 -277
  20. package/dist/domain/entities/task.js +0 -61
  21. package/dist/domain/services/codebase-detector.js +0 -91
  22. package/dist/error-handler.js +0 -251
  23. package/dist/infrastructure/completion/bash-completion.js +0 -25
  24. package/dist/infrastructure/completion/fish-completion.js +0 -22
  25. package/dist/infrastructure/completion/zsh-completion.js +0 -30
  26. package/dist/infrastructure/config/config-manager.js +0 -173
  27. package/dist/infrastructure/config/product-context.js +0 -67
  28. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  29. package/dist/infrastructure/context/adapters/figma-source.js +0 -163
  30. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
  31. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  32. package/dist/infrastructure/context/context-resolver.js +0 -144
  33. package/dist/infrastructure/context/context-source.js +0 -114
  34. package/dist/infrastructure/context/oauth-flow.js +0 -207
  35. package/dist/infrastructure/context/oauth-store.js +0 -119
  36. package/dist/infrastructure/context/registry-bootstrap.js +0 -99
  37. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  38. package/dist/infrastructure/cost/budget-config.js +0 -113
  39. package/dist/infrastructure/cost/cost-router.js +0 -104
  40. package/dist/infrastructure/cost/spend-aggregator.js +0 -147
  41. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
  42. package/dist/infrastructure/hooks/hook-mode.js +0 -71
  43. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
  44. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  45. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  46. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  47. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  48. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  49. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
  50. package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
  51. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  52. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
  53. package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
  54. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
  55. package/dist/infrastructure/orchestration/context-handoff.js +0 -144
  56. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  57. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  58. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  59. package/dist/infrastructure/orchestration/failover-config.js +0 -77
  60. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  61. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
  62. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
  63. package/dist/infrastructure/orchestration/graphify-context.js +0 -254
  64. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  65. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
  66. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  67. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  68. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  69. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
  70. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
  71. package/dist/infrastructure/orchestration/readiness.js +0 -103
  72. package/dist/infrastructure/orchestration/session-manager.js +0 -1091
  73. package/dist/infrastructure/orchestration/session-persistence.js +0 -258
  74. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  75. package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
  76. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  77. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  78. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
  79. package/dist/infrastructure/protocols/probe-port.js +0 -36
  80. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  81. package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
  82. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  83. package/dist/infrastructure/protocols/verify-page.js +0 -983
  84. package/dist/infrastructure/security/file-collection.js +0 -118
  85. package/dist/infrastructure/security/input-validator.js +0 -152
  86. package/dist/infrastructure/security/permissions-config.js +0 -234
  87. package/dist/infrastructure/security/trust-scorer.js +0 -657
  88. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  89. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  90. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  91. package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
  92. package/dist/infrastructure/storage/project-thread-reader.js +0 -172
  93. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  94. package/dist/infrastructure/storage/task-repository.js +0 -206
  95. package/dist/monitoring.js +0 -212
  96. package/dist/presentation/cli/commands/audit-command.js +0 -126
  97. package/dist/presentation/cli/commands/budget-command.js +0 -79
  98. package/dist/presentation/cli/commands/config-command.js +0 -125
  99. package/dist/presentation/cli/commands/context-source-command.js +0 -198
  100. package/dist/presentation/cli/commands/hook-command.js +0 -265
  101. package/dist/presentation/cli/commands/init-command.js +0 -578
  102. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  103. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  104. package/dist/presentation/cli/commands/permission-command.js +0 -116
  105. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  106. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  107. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  108. package/dist/presentation/cli/commands/report-command.js +0 -387
  109. package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
  110. package/dist/presentation/cli/commands/setup-command.js +0 -307
  111. package/dist/presentation/cli/commands/status-command.js +0 -661
  112. package/dist/presentation/cli/commands/thread-command.js +0 -288
  113. package/dist/presentation/cli/commands/trust-command.js +0 -306
  114. package/dist/presentation/cli/commands/verify-command.js +0 -233
  115. package/dist/presentation/cli/error-display.js +0 -336
  116. package/dist/presentation/cli/repl.js +0 -797
  117. package/dist/presentation/cli/task-deliverable.js +0 -127
  118. package/dist/testing/cassette.js +0 -125
  119. package/dist/testing/replay-adapter.js +0 -166
@@ -1,25 +0,0 @@
1
- /**
2
- * Bash Completion Script Generator
3
- *
4
- * Phase 1: v0.5.0-alpha.0
5
- */
6
- export function generateBashCompletion() {
7
- return `# Codehere bash completion
8
- _complete_codehere() {
9
- local cur prev opts
10
- COMPREPLY=()
11
- cur="\${COMP_WORDS[COMP_CWORD]}"
12
- prev="\${COMP_WORDS[COMP_CWORD-1]}"
13
- opts="ask plan orchestrate fix suggest run review status help version"
14
-
15
- if [[ \${cur} == -* ]]; then
16
- COMPREPLY=($(compgen -W "--help --version" -- \${cur}))
17
- else
18
- COMPREPLY=($(compgen -W "\${opts}" -- \${cur}))
19
- fi
20
- }
21
-
22
- complete -F _complete_codehere codehere
23
- `;
24
- }
25
- //# sourceMappingURL=bash-completion.js.map
@@ -1,22 +0,0 @@
1
- /**
2
- * Fish Completion Script Generator
3
- *
4
- * Phase 1: v0.5.0-alpha.0
5
- */
6
- export function generateFishCompletion() {
7
- return `# Codehere fish completion
8
- complete -c codehere -f
9
-
10
- complete -c codehere -n __fish_use_subcommand -a ask -d "Answer questions about codebase"
11
- complete -c codehere -n __fish_use_subcommand -a plan -d "Generate execution plan"
12
- complete -c codehere -n __fish_use_subcommand -a orchestrate -d "Multi-agent orchestration"
13
- complete -c codehere -n __fish_use_subcommand -a fix -d "Apply AI edits to file"
14
- complete -c codehere -n __fish_use_subcommand -a suggest -d "Read-only assistant mode"
15
- complete -c codehere -n __fish_use_subcommand -a run -d "Unified agentic execution"
16
- complete -c codehere -n __fish_use_subcommand -a review -d "Task review interface"
17
- complete -c codehere -n __fish_use_subcommand -a status -d "Show agent status"
18
- complete -c codehere -n __fish_use_subcommand -a help -d "Show help"
19
- complete -c codehere -n __fish_use_subcommand -a version -d "Show version"
20
- `;
21
- }
22
- //# sourceMappingURL=fish-completion.js.map
@@ -1,30 +0,0 @@
1
- /**
2
- * Zsh Completion Script Generator
3
- *
4
- * Phase 1: v0.5.0-alpha.0
5
- */
6
- export function generateZshCompletion() {
7
- return `#compdef codehere
8
-
9
- _codehere() {
10
- local -a commands
11
- commands=(
12
- 'ask:Answer questions about codebase'
13
- 'plan:Generate execution plan'
14
- 'orchestrate:Multi-agent orchestration'
15
- 'fix:Apply AI edits to file'
16
- 'suggest:Read-only assistant mode'
17
- 'run:Unified agentic execution'
18
- 'review:Task review interface'
19
- 'status:Show agent status'
20
- 'help:Show help'
21
- 'version:Show version'
22
- )
23
-
24
- _describe 'codehere' commands
25
- }
26
-
27
- _codehere "$@"
28
- `;
29
- }
30
- //# sourceMappingURL=zsh-completion.js.map
@@ -1,173 +0,0 @@
1
- /**
2
- * Configuration Manager
3
- * Manages user preferences and settings for Codehere
4
- * Stores configuration in data/config.json
5
- */
6
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
7
- import { join } from 'path';
8
- import { getVersion } from '../../utils/version.js';
9
- function detectDefaultProvider() {
10
- if (process.env.ANTHROPIC_API_KEY)
11
- return { provider: 'anthropic', model: 'claude-sonnet-4-20250514' };
12
- if (process.env.OPENAI_API_KEY)
13
- return { provider: 'openai', model: 'gpt-4o-mini' };
14
- if (process.env.COHERE_API_KEY)
15
- return { provider: 'cohere', model: 'command-r-08-2024' };
16
- if (process.env.CODEHERE_AI_PROVIDER === 'local')
17
- return { provider: 'local', model: 'llama3' };
18
- return { provider: 'auto', model: 'auto' };
19
- }
20
- const DEFAULT_CONFIG = {
21
- version: getVersion(),
22
- preferences: {
23
- autoCommit: false,
24
- askBeforeDestructive: true,
25
- enableCrossTeamLearning: false,
26
- defaultModel: detectDefaultProvider().model,
27
- maxTokens: 4096,
28
- temperature: 0.3,
29
- enableStreaming: true,
30
- },
31
- codebase: {},
32
- api: {
33
- provider: detectDefaultProvider().provider,
34
- model: detectDefaultProvider().model,
35
- rateLimitHandling: 'retry',
36
- },
37
- ui: {
38
- showProgress: true,
39
- showSummary: true,
40
- outputFormat: 'full',
41
- },
42
- };
43
- export class ConfigManager {
44
- configPath;
45
- config;
46
- constructor(configDir) {
47
- const baseDir = configDir || process.cwd();
48
- this.configPath = join(baseDir, 'data', 'config.json');
49
- this.config = this.loadConfig();
50
- }
51
- /**
52
- * Load configuration from file, creating defaults if not exists
53
- */
54
- loadConfig() {
55
- // Ensure data directory exists
56
- const dataDir = join(process.cwd(), 'data');
57
- if (!existsSync(dataDir)) {
58
- mkdirSync(dataDir, { recursive: true });
59
- }
60
- // Load existing config or use defaults
61
- if (existsSync(this.configPath)) {
62
- try {
63
- const fileContent = readFileSync(this.configPath, 'utf-8');
64
- const loaded = JSON.parse(fileContent);
65
- // Merge with defaults to ensure all fields exist
66
- return {
67
- ...DEFAULT_CONFIG,
68
- ...loaded,
69
- preferences: {
70
- ...DEFAULT_CONFIG.preferences,
71
- ...loaded.preferences,
72
- },
73
- codebase: {
74
- ...DEFAULT_CONFIG.codebase,
75
- ...loaded.codebase,
76
- },
77
- api: {
78
- ...DEFAULT_CONFIG.api,
79
- ...loaded.api,
80
- },
81
- ui: {
82
- ...DEFAULT_CONFIG.ui,
83
- ...loaded.ui,
84
- },
85
- };
86
- }
87
- catch (error) {
88
- console.warn(`Failed to load config from ${this.configPath}, using defaults:`, error);
89
- return DEFAULT_CONFIG;
90
- }
91
- }
92
- return DEFAULT_CONFIG;
93
- }
94
- /**
95
- * Save configuration to file
96
- */
97
- saveConfig() {
98
- try {
99
- const dataDir = join(process.cwd(), 'data');
100
- if (!existsSync(dataDir)) {
101
- mkdirSync(dataDir, { recursive: true });
102
- }
103
- writeFileSync(this.configPath, JSON.stringify(this.config, null, 2), 'utf-8');
104
- }
105
- catch (error) {
106
- throw new Error(`Failed to save config to ${this.configPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
107
- }
108
- }
109
- /**
110
- * Get configuration value
111
- */
112
- get(key) {
113
- return this.config[key];
114
- }
115
- /**
116
- * Get preference value
117
- */
118
- getPreference(key) {
119
- return this.config.preferences[key];
120
- }
121
- /**
122
- * Set configuration value
123
- */
124
- set(key, value) {
125
- this.config[key] = value;
126
- }
127
- /**
128
- * Set preference value
129
- */
130
- setPreference(key, value) {
131
- this.config.preferences[key] = value;
132
- }
133
- /**
134
- * Update codebase information
135
- */
136
- updateCodebase(root, indexedAt) {
137
- this.config.codebase.root = root;
138
- if (indexedAt) {
139
- this.config.codebase.indexedAt = indexedAt;
140
- }
141
- this.config.codebase.lastIndexed = new Date().toISOString();
142
- }
143
- /**
144
- * Get full configuration
145
- */
146
- getAll() {
147
- return { ...this.config };
148
- }
149
- /**
150
- * Reset to defaults
151
- */
152
- reset() {
153
- this.config = DEFAULT_CONFIG;
154
- this.saveConfig();
155
- }
156
- /**
157
- * Get config file path
158
- */
159
- getConfigPath() {
160
- return this.configPath;
161
- }
162
- }
163
- /**
164
- * Get global config manager instance
165
- */
166
- let globalConfigManager = null;
167
- export function getConfigManager(configDir) {
168
- if (!globalConfigManager) {
169
- globalConfigManager = new ConfigManager(configDir);
170
- }
171
- return globalConfigManager;
172
- }
173
- //# sourceMappingURL=config-manager.js.map
@@ -1,67 +0,0 @@
1
- /**
2
- * Product Context
3
- *
4
- * Stores a lightweight "product brain" for the current project.
5
- * Written by `codehere product init`, read by trust/report/router
6
- * to give context-aware recommendations.
7
- *
8
- * Stored in: .codehere/product.json (per-project, gitignore-friendly)
9
- */
10
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
11
- import { join } from 'path';
12
- const CONFIG_DIR = '.codehere';
13
- const CONFIG_FILE = 'product.json';
14
- function getConfigPath(cwd) {
15
- return join(cwd, CONFIG_DIR, CONFIG_FILE);
16
- }
17
- export function loadProductContext(cwd = process.cwd()) {
18
- const path = getConfigPath(cwd);
19
- if (!existsSync(path))
20
- return null;
21
- try {
22
- return JSON.parse(readFileSync(path, 'utf-8'));
23
- }
24
- catch {
25
- return null;
26
- }
27
- }
28
- export function saveProductContext(ctx, cwd = process.cwd()) {
29
- const dir = join(cwd, CONFIG_DIR);
30
- if (!existsSync(dir))
31
- mkdirSync(dir, { recursive: true });
32
- writeFileSync(getConfigPath(cwd), JSON.stringify(ctx, null, 2) + '\n');
33
- }
34
- /**
35
- * Returns a plain-English risk label for use in recommendations.
36
- */
37
- export function riskLabel(level) {
38
- return level === 'critical'
39
- ? 'critical-risk'
40
- : level === 'high'
41
- ? 'high-risk'
42
- : level === 'medium'
43
- ? 'production'
44
- : 'low-risk';
45
- }
46
- /**
47
- * Adjusts a trust recommendation based on product context.
48
- * If no context is available, returns the base recommendation unchanged.
49
- */
50
- export function adjustRecommendation(base, criticalCount, highCount, score, ctx) {
51
- if (!ctx)
52
- return base;
53
- const prefix = ctx.productionApp ? `${ctx.name || 'Production app'} · ` : '';
54
- if (criticalCount > 0) {
55
- return `${prefix}${criticalCount} critical issue${criticalCount !== 1 ? 's' : ''} — fix before this goes to production.`;
56
- }
57
- if (ctx.riskLevel === 'critical' || ctx.riskLevel === 'high') {
58
- if (highCount > 0) {
59
- return `${prefix}${highCount} high-severity pattern${highCount !== 1 ? 's' : ''} — worth fixing before merge given the risk level.`;
60
- }
61
- if (score >= 75) {
62
- return `${prefix}Clean scan. Good to merge — double-check ${ctx.sensitiveAreas.slice(0, 2).join(' and ') || 'sensitive areas'} manually.`;
63
- }
64
- }
65
- return base;
66
- }
67
- //# sourceMappingURL=product-context.js.map
@@ -1,157 +0,0 @@
1
- /**
2
- * draw.io context-source adapter (v0.8 S5 · third reference adapter).
3
- *
4
- * URL shapes accepted (we route both via the same scheme to keep the
5
- * agent prompt forgiving):
6
- * - `drawio://<gist-or-raw-url-base64>` · base64url-encoded source URL
7
- * - `drawio://gh/<owner>/<repo>/<path>` · raw GitHub-hosted .drawio XML
8
- * - `drawio://url/<urlencoded-https-url>` · arbitrary HTTPS-hosted .drawio
9
- *
10
- * No OAuth · draw.io is a client-side editor, the file itself is the
11
- * payload. We fetch the raw XML, summarize node + edge counts, and emit
12
- * the first ~3KB of the XML so the agent can read structure without us
13
- * doing graphviz-style parsing here. Bigger diagrams get a `...truncated`
14
- * suffix; agents can ask the human for a screenshot if they need pixels.
15
- *
16
- * Local-first: no codehere endpoint is contacted. Network call goes
17
- * directly to GitHub-raw / the supplied HTTPS host. Audit chain records
18
- * fetch + byte count, never content.
19
- */
20
- import { ContextSourceError, estimateTokens, } from '../context-source.js';
21
- const MAX_XML_CHARS = 3000;
22
- const MAX_RESPONSE_BYTES = 1_500_000; // 1.5MB · refuse anything bigger to protect prompt budget
23
- /** Turn the codehere drawio:// URL into a concrete HTTPS fetch target. */
24
- function resolveTarget(url) {
25
- const raw = url.toString();
26
- const after = raw.slice('drawio://'.length).replace(/^\/+/, '');
27
- if (!after) {
28
- throw new ContextSourceError('drawio', 'invalid_url', 'drawio URL missing path');
29
- }
30
- // gh/<owner>/<repo>/<path...>
31
- if (after.startsWith('gh/')) {
32
- const parts = after.slice('gh/'.length).split('/');
33
- if (parts.length < 3) {
34
- throw new ContextSourceError('drawio', 'invalid_url', 'gh/ path needs <owner>/<repo>/<path>');
35
- }
36
- const [owner, repo, ...rest] = parts;
37
- const path = rest.join('/');
38
- if (!/^[A-Za-z0-9._-]+$/.test(owner) || !/^[A-Za-z0-9._-]+$/.test(repo)) {
39
- throw new ContextSourceError('drawio', 'invalid_url', 'gh/ owner/repo must be alphanumeric');
40
- }
41
- return {
42
- fetchUrl: `https://raw.githubusercontent.com/${owner}/${repo}/HEAD/${path}`,
43
- label: `github.com/${owner}/${repo}/${path}`,
44
- };
45
- }
46
- // url/<urlencoded-https-url>
47
- if (after.startsWith('url/')) {
48
- const encoded = after.slice('url/'.length);
49
- const decoded = safeDecode(encoded);
50
- if (!/^https:\/\//.test(decoded)) {
51
- throw new ContextSourceError('drawio', 'invalid_url', 'url/ target must be https://');
52
- }
53
- return { fetchUrl: decoded, label: decoded };
54
- }
55
- // base64url-encoded full URL
56
- const decoded = safeBase64UrlDecode(after);
57
- if (decoded && /^https:\/\//.test(decoded)) {
58
- return { fetchUrl: decoded, label: decoded };
59
- }
60
- throw new ContextSourceError('drawio', 'invalid_url', 'use drawio://gh/<owner>/<repo>/<path> · drawio://url/<urlencoded-https> · or drawio://<base64url-https>');
61
- }
62
- function safeDecode(s) {
63
- try {
64
- return decodeURIComponent(s);
65
- }
66
- catch {
67
- return '';
68
- }
69
- }
70
- function safeBase64UrlDecode(s) {
71
- try {
72
- const pad = '='.repeat((4 - (s.length % 4)) % 4);
73
- return Buffer.from(s.replace(/-/g, '+').replace(/_/g, '/') + pad, 'base64').toString('utf-8');
74
- }
75
- catch {
76
- return '';
77
- }
78
- }
79
- export const drawioSource = {
80
- id: 'drawio',
81
- label: 'draw.io',
82
- scheme: 'drawio',
83
- description: 'Fetch a draw.io / diagrams.net diagram XML (architecture, flowchart, etc.)',
84
- isConfigured() {
85
- // Public-by-URL · no auth needed. Always available.
86
- return true;
87
- },
88
- async fetch(url, ctx) {
89
- const target = resolveTarget(url);
90
- const res = await ctx.httpGet(target.fetchUrl, { 'User-Agent': 'codehere/0.8' });
91
- if (res.status === 404) {
92
- throw new ContextSourceError('drawio', 'not_found', `diagram not found at ${target.label}`);
93
- }
94
- if (res.status === 429) {
95
- throw new ContextSourceError('drawio', 'rate_limit', `host returned 429 for ${target.label}`);
96
- }
97
- if (res.status !== 200) {
98
- throw new ContextSourceError('drawio', 'network', `host returned ${res.status} for ${target.label}: ${res.body.slice(0, 200)}`);
99
- }
100
- if (res.body.length > MAX_RESPONSE_BYTES) {
101
- throw new ContextSourceError('drawio', 'parse', `diagram too big (${res.body.length} bytes · limit ${MAX_RESPONSE_BYTES})`);
102
- }
103
- if (!looksLikeDrawio(res.body)) {
104
- throw new ContextSourceError('drawio', 'parse', 'response does not look like a draw.io XML or SVG file');
105
- }
106
- const stats = quickStats(res.body);
107
- const text = renderDiagramAsPlainText(target, res.body, stats);
108
- return {
109
- url: url.toString(),
110
- source: 'drawio',
111
- fetchedAt: new Date(ctx.now ? ctx.now() : Date.now()).toISOString(),
112
- text,
113
- estimatedTokens: estimateTokens(text),
114
- metadata: {
115
- label: target.label,
116
- bytes: res.body.length,
117
- cellCount: stats.cellCount,
118
- edgeCount: stats.edgeCount,
119
- pageCount: stats.pageCount,
120
- },
121
- };
122
- },
123
- };
124
- /** Does this payload look like a draw.io diagram (XML) or SVG export?
125
- * Accept any prolog (whitespace, BOM, `<?xml ...?>`) before the first
126
- * meaningful tag · `<mxfile`, `<mxGraphModel`, or `<svg`. */
127
- function looksLikeDrawio(body) {
128
- const trimmed = body.replace(/^\uFEFF/, '').trimStart();
129
- const stripped = trimmed.startsWith('<?') ? trimmed.slice(trimmed.indexOf('?>') + 2).trimStart() : trimmed;
130
- return /^<(mxfile|mxGraphModel|svg)\b/i.test(stripped);
131
- }
132
- /** Cheap regex stats · we don't ship a real XML parser. */
133
- function quickStats(xml) {
134
- const cellMatches = xml.match(/<mxCell\b/g);
135
- const edgeMatches = xml.match(/<mxCell\b[^>]*\bedge="1"/g);
136
- const pageMatches = xml.match(/<diagram\b/g);
137
- return {
138
- cellCount: cellMatches ? cellMatches.length : 0,
139
- edgeCount: edgeMatches ? edgeMatches.length : 0,
140
- pageCount: pageMatches ? pageMatches.length : 1,
141
- };
142
- }
143
- function renderDiagramAsPlainText(target, xml, stats) {
144
- const lines = [];
145
- lines.push(`draw.io diagram · ${target.label}`);
146
- lines.push('');
147
- lines.push(`structure: ${stats.cellCount} cells · ${stats.edgeCount} edges · ${stats.pageCount} page${stats.pageCount === 1 ? '' : 's'}`);
148
- lines.push(`bytes: ${xml.length}`);
149
- lines.push('');
150
- lines.push('--- raw xml (truncated) ---');
151
- lines.push(xml.slice(0, MAX_XML_CHARS));
152
- if (xml.length > MAX_XML_CHARS) {
153
- lines.push(`...[truncated · diagram is ${xml.length} chars · ask the human for a screenshot if structure isn't clear]`);
154
- }
155
- return lines.join('\n');
156
- }
157
- //# sourceMappingURL=drawio-source.js.map
@@ -1,163 +0,0 @@
1
- /**
2
- * Figma context-source adapter (v0.8 S1 · second reference adapter).
3
- *
4
- * URL shapes:
5
- * figma://file/<file-key> · whole-file metadata + top-level frames
6
- * figma://file/<file-key>/node/<node-id> · one node + its children, flattened
7
- *
8
- * Returns the design tree as indented plain text: frame names, types,
9
- * dimensions, text content of TEXT nodes, fill colors when they're solid.
10
- * Generated previews + raster bytes are skipped · agents read text, not
11
- * pixels (overlay-renderer in v0.9 will surface raw frames separately).
12
- *
13
- * Auth: ctx.config['figma'] = { token: '<personal-access-token>' }
14
- * Token comes from figma.com/developers/api · personal access token.
15
- */
16
- import { ContextSourceError, estimateTokens, } from '../context-source.js';
17
- const FIGMA_API = 'https://api.figma.com/v1';
18
- const MAX_TREE_NODES = 200;
19
- const MAX_TEXT_PER_NODE = 240;
20
- function parsePath(url) {
21
- // figma://file/<key> · hostname = 'file', pathname = '/<key>'
22
- // figma://file/<key>/node/<id>
23
- const host = url.host || url.hostname; // 'file'
24
- if (host !== 'file') {
25
- throw new ContextSourceError('figma', 'invalid_url', `URL must start with figma://file/<key>; got host='${host}'`);
26
- }
27
- const parts = url.pathname.split('/').filter(Boolean);
28
- if (!parts.length) {
29
- throw new ContextSourceError('figma', 'invalid_url', 'missing file key in figma:// URL');
30
- }
31
- const fileKey = parts[0];
32
- let nodeId = null;
33
- if (parts.length >= 3 && parts[1] === 'node') {
34
- nodeId = decodeURIComponent(parts.slice(2).join('/'));
35
- }
36
- return { fileKey, nodeId };
37
- }
38
- export const figmaSource = {
39
- id: 'figma',
40
- label: 'Figma',
41
- scheme: 'figma',
42
- description: 'Fetch a Figma file or single node (tree, dimensions, text content)',
43
- isConfigured(ctx) {
44
- const cfg = ctx.config['figma'];
45
- if (!cfg)
46
- return false;
47
- if (cfg.oauth?.accessToken)
48
- return true;
49
- return typeof cfg.token === 'string' && cfg.token.length > 8;
50
- },
51
- async fetch(url, ctx) {
52
- const cfg = ctx.config['figma'] ?? {};
53
- // Figma supports BOTH a personal-access token (X-Figma-Token header) AND
54
- // an OAuth bearer (Authorization: Bearer ...) · pick whichever is set.
55
- let headers;
56
- if (cfg.oauth?.accessToken) {
57
- headers = { Authorization: `Bearer ${cfg.oauth.accessToken}` };
58
- }
59
- else if (cfg.token) {
60
- headers = { 'X-Figma-Token': cfg.token };
61
- }
62
- else {
63
- throw new ContextSourceError('figma', 'auth', 'no token or OAuth in ctx.config["figma"]');
64
- }
65
- const { fileKey, nodeId } = parsePath(url);
66
- const endpoint = cfg.endpoint ?? FIGMA_API;
67
- let res;
68
- if (nodeId) {
69
- const target = `${endpoint}/files/${encodeURIComponent(fileKey)}/nodes?ids=${encodeURIComponent(nodeId)}`;
70
- res = await ctx.httpGet(target, headers);
71
- }
72
- else {
73
- const target = `${endpoint}/files/${encodeURIComponent(fileKey)}?depth=2`;
74
- res = await ctx.httpGet(target, headers);
75
- }
76
- if (res.status === 401 || res.status === 403) {
77
- throw new ContextSourceError('figma', 'auth', `Figma returned ${res.status}`);
78
- }
79
- if (res.status === 404) {
80
- throw new ContextSourceError('figma', 'not_found', `file ${fileKey} not found`);
81
- }
82
- if (res.status === 429) {
83
- throw new ContextSourceError('figma', 'rate_limit', 'Figma rate limit hit');
84
- }
85
- if (res.status !== 200) {
86
- throw new ContextSourceError('figma', 'network', `Figma returned ${res.status}: ${res.body.slice(0, 200)}`);
87
- }
88
- let text;
89
- let metadata;
90
- if (nodeId) {
91
- const parsed = JSON.parse(res.body);
92
- if (parsed.err)
93
- throw new ContextSourceError('figma', 'parse', `Figma error: ${parsed.err}`);
94
- const node = parsed.nodes?.[nodeId]?.document;
95
- if (!node)
96
- throw new ContextSourceError('figma', 'not_found', `node ${nodeId} not in response`);
97
- text = renderNodeAsTree(node, fileKey, nodeId);
98
- metadata = { fileKey, nodeId, type: node.type, name: node.name };
99
- }
100
- else {
101
- const parsed = JSON.parse(res.body);
102
- if (parsed.err)
103
- throw new ContextSourceError('figma', 'parse', `Figma error: ${parsed.err}`);
104
- if (!parsed.document)
105
- throw new ContextSourceError('figma', 'parse', 'Figma response missing document');
106
- text = renderFile(parsed, fileKey);
107
- metadata = { fileKey, name: parsed.name ?? null, lastModified: parsed.lastModified ?? null };
108
- }
109
- return {
110
- url: url.toString(),
111
- source: 'figma',
112
- fetchedAt: new Date(ctx.now ? ctx.now() : Date.now()).toISOString(),
113
- text,
114
- estimatedTokens: estimateTokens(text),
115
- metadata,
116
- };
117
- },
118
- };
119
- function renderFile(file, fileKey) {
120
- const lines = [];
121
- lines.push(`Figma file · ${file.name ?? '(unnamed)'}`);
122
- lines.push(`https://www.figma.com/file/${fileKey}`);
123
- if (file.lastModified)
124
- lines.push(`updated: ${file.lastModified}`);
125
- lines.push('');
126
- if (file.document) {
127
- lines.push('--- top-level tree (depth 2) ---');
128
- renderInto(lines, file.document, 0, { remaining: MAX_TREE_NODES });
129
- }
130
- return lines.join('\n');
131
- }
132
- function renderNodeAsTree(node, fileKey, nodeId) {
133
- const lines = [];
134
- lines.push(`Figma node · ${node.name} (${node.type})`);
135
- lines.push(`https://www.figma.com/file/${fileKey}?node-id=${encodeURIComponent(nodeId)}`);
136
- lines.push('');
137
- renderInto(lines, node, 0, { remaining: MAX_TREE_NODES });
138
- return lines.join('\n');
139
- }
140
- function renderInto(out, node, depth, budget) {
141
- if (budget.remaining <= 0) {
142
- if (budget.remaining === 0) {
143
- out.push('...[tree truncated · node budget reached]');
144
- budget.remaining = -1;
145
- }
146
- return;
147
- }
148
- budget.remaining -= 1;
149
- const indent = ' '.repeat(depth);
150
- const bb = node.absoluteBoundingBox;
151
- const dim = bb ? ` ${Math.round(bb.width)}×${Math.round(bb.height)}` : '';
152
- const visible = node.visible === false ? ' [hidden]' : '';
153
- let line = `${indent}${node.type} · ${node.name}${dim}${visible}`;
154
- if (node.characters) {
155
- const t = node.characters.replace(/\s+/g, ' ').slice(0, MAX_TEXT_PER_NODE);
156
- line += ` · "${t}${node.characters.length > MAX_TEXT_PER_NODE ? '…' : ''}"`;
157
- }
158
- out.push(line);
159
- for (const child of node.children ?? []) {
160
- renderInto(out, child, depth + 1, budget);
161
- }
162
- }
163
- //# sourceMappingURL=figma-source.js.map