clew-code 0.2.29 → 0.2.30

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 (37) hide show
  1. package/bin/clew.cjs +2 -2
  2. package/package.json +5 -2
  3. package/scripts/auto-close-duplicates.ts +277 -0
  4. package/scripts/backfill-duplicate-comments.ts +213 -0
  5. package/scripts/codegraph.ts +221 -0
  6. package/scripts/comment-on-duplicates.sh +95 -0
  7. package/scripts/edit-issue-labels.sh +84 -0
  8. package/scripts/final-peer-rename.mjs +46 -0
  9. package/scripts/fix-encoding.mjs +83 -0
  10. package/scripts/fix-inconsistencies.mjs +67 -0
  11. package/scripts/generate-docs.ts +307 -0
  12. package/scripts/gh.sh +96 -0
  13. package/scripts/install.ps1 +37 -0
  14. package/scripts/install.sh +49 -0
  15. package/scripts/issue-lifecycle.ts +38 -0
  16. package/scripts/lifecycle-comment.ts +53 -0
  17. package/scripts/normalize-html.mjs +37 -0
  18. package/scripts/preload.ts +159 -0
  19. package/scripts/rename-content-peer-to-swarm.mjs +67 -0
  20. package/scripts/rename-hooks-mesh.mjs +6 -0
  21. package/scripts/rename-peer-to-swarm.mjs +62 -0
  22. package/scripts/run_devcontainer_claude_code.ps1 +152 -0
  23. package/scripts/scrape.py +82 -0
  24. package/scripts/session.ts +195 -0
  25. package/scripts/sweep.ts +168 -0
  26. package/scripts/write-discovery-test.cjs +93 -0
  27. package/scripts/write-discovery-test2.cjs +65 -0
  28. package/scripts/write-server-final.cjs +108 -0
  29. package/scripts/write-server-test.cjs +69 -0
  30. package/scripts/write-server-test2.cjs +99 -0
  31. package/scripts/write-server-test3.cjs +59 -0
  32. package/scripts/write-server-test4.cjs +108 -0
  33. package/scripts/write-server-v2.cjs +142 -0
  34. package/scripts/write-server-v2b.cjs +129 -0
  35. package/scripts/write-server-v2c.cjs +136 -0
  36. package/scripts/write-store-test.cjs +12 -0
  37. package/scripts/write-store-test2.cjs +163 -0
@@ -0,0 +1,307 @@
1
+ /**
2
+ * generate-docs.ts — Auto-generate HTML documentation from source code.
3
+ *
4
+ * Run: bun run scripts/generate-docs.ts
5
+ * Reads source data and writes to docs/generated/
6
+ *
7
+ * Currently generates:
8
+ * - providers.json → docs/generated/providers.html (provider reference table)
9
+ * - tool prompts → docs/generated/tools.html (tool reference with prompts)
10
+ */
11
+
12
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
13
+ import { join, dirname } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ const ROOT = join(__dirname, '..');
18
+ const SRC = join(ROOT, 'src');
19
+ const DOCS = join(ROOT, 'docs');
20
+ const OUT = join(DOCS, 'generated');
21
+
22
+ if (!existsSync(OUT)) mkdirSync(OUT, { recursive: true });
23
+
24
+ // ── Helpers ──
25
+
26
+ function html( Title: string, body: string, description = ''): string {
27
+ return `<!DOCTYPE html>
28
+ <html lang="en">
29
+ <head>
30
+ <meta charset="UTF-8">
31
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
32
+ <title>${Title} — Clew</title>
33
+ <meta name="description" content="${description}">
34
+ <link rel="preconnect" href="https://fonts.googleapis.com">
35
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
36
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
37
+ <link rel="stylesheet" href="../css/styles.css">
38
+ <link rel="icon" type="image/svg+xml" href="../assets/clew.svg">
39
+ </head>
40
+ <body>
41
+ <header class="header">
42
+ <div class="header-inner">
43
+ <a href="../index.html" class="logo"><span class="logo-mark">C</span><span>Clew Code</span></a>
44
+ <nav class="header-nav">
45
+ <a href="../index.html">Home</a>
46
+ <a href="../commands.html">Commands</a>
47
+ <a href="../tools.html">Tools</a>
48
+ <a href="../providers.html">Providers</a>
49
+ <a href="../prompts-and-features.html">Reference</a>
50
+ <a href="../quick-start.html">Docs</a>
51
+ </nav>
52
+ </div>
53
+ </header>
54
+ <div class="app">
55
+ <aside class="sidebar" id="sidebar">
56
+ <div class="sidebar-section">
57
+ <div class="sidebar-label">Generated Docs</div>
58
+ <a href="providers.html" class="sidebar-link"><span class="link-icon"></span>Providers</a>
59
+ <a href="tools.html" class="sidebar-link"><span class="link-icon"></span>Tools</a>
60
+ </div>
61
+ <div class="sidebar-section">
62
+ <div class="sidebar-label">Static Docs</div>
63
+ <a href="../commands.html" class="sidebar-link"><span class="link-icon"></span>Commands</a>
64
+ <a href="../tools.html" class="sidebar-link"><span class="link-icon"></span>Tools</a>
65
+ <a href="../providers.html" class="sidebar-link"><span class="link-icon"></span>Providers</a>
66
+ </div>
67
+ </aside>
68
+ <div class="sidebar-overlay"></div>
69
+ <div class="content-wrap"><main class="content">
70
+ <div class="breadcrumbs"><a href="../index.html">Home</a><span class="sep">/</span><span>${Title}</span></div>
71
+ <h1>${Title}</h1>
72
+ <p class="section-subtitle"><em>Auto-generated from source code. Last updated: ${new Date().toISOString().slice(0, 10)}.</em></p>
73
+ ${body}
74
+ </main></div>
75
+ </div>
76
+ </body>
77
+ </html>`;
78
+ }
79
+
80
+ function cap(s: string): string {
81
+ return s.charAt(0).toUpperCase() + s.slice(1);
82
+ }
83
+
84
+ // ═══════════════════════════════════════════════════════════════════════
85
+ // 1. GENERATE PROVIDERS
86
+ // ═══════════════════════════════════════════════════════════════════════
87
+
88
+ function generateProviders(): void {
89
+ const providers = JSON.parse(
90
+ readFileSync(join(SRC, 'services/ai/providers.json'), 'utf8'),
91
+ ) as Record<string, any>;
92
+
93
+ const CAP_KEYS: Record<string, string> = {
94
+ chat: 'Chat',
95
+ streaming: 'Streaming',
96
+ toolCalling: 'Tools',
97
+ vision: 'Vision',
98
+ jsonSchema: 'JSON Schema',
99
+ reasoningEffort: 'Reasoning',
100
+ };
101
+
102
+ let rows = '';
103
+ let detailSections = '';
104
+
105
+ for (const [id, p] of Object.entries(providers)) {
106
+ const caps = Object.entries(p.capabilities ?? {})
107
+ .filter(([, v]) => v === true || v === 'full' || v === 'native')
108
+ .map(([k]) => CAP_KEYS[k] ?? cap(k))
109
+ .join(', ') || '—';
110
+
111
+ const models = (p.models ?? []).map((m: any) => m.id).join(', ') || '—';
112
+
113
+ rows += `<tr>
114
+ <td><strong>${p.label}</strong></td>
115
+ <td><code>${p.envKey}</code></td>
116
+ <td>${p.defaultModel || '—'}</td>
117
+ <td style="font-size:0.75rem">${caps}</td>
118
+ </tr>`;
119
+
120
+ detailSections += `<details>
121
+ <summary><strong>${p.label}</strong> <code>${id}</code></summary>
122
+ <p>${p.note || 'No description.'}</p>
123
+ <table>
124
+ <tr><th>Property</th><th>Value</th></tr>
125
+ <tr><td>Provider ID</td><td><code>${id}</code></td></tr>
126
+ <tr><td>Env Key</td><td><code>${p.envKey}</code></td></tr>
127
+ <tr><td>Default Model</td><td>${p.defaultModel || '—'}</td></tr>
128
+ <tr><td>Base URL</td><td><code>${p.defaultBaseUrl}</code></td></tr>
129
+ <tr><td>Models URL</td><td>${p.modelsUrl ? `<code>${p.modelsUrl}</code>` : '—'}</td></tr>
130
+ <tr><td>Local</td><td>${p.isLocal ? '✅' : '—'}</td></tr>
131
+ <tr><td>Capabilities</td><td>${caps || '—'}</td></tr>
132
+ </table>
133
+ <p><strong>Models (${(p.models ?? []).length}):</strong> ${models}</p>
134
+ </details>`;
135
+ }
136
+
137
+ const body = `
138
+ <h2>All Providers</h2>
139
+ <p>Clew supports <strong>${Object.keys(providers).length} AI providers</strong>. Switch between them at runtime with <code>/model</code> or <code>/provider-select</code>.</p>
140
+ <div class="table-wrap"><table>
141
+ <tr><th>Provider</th><th>Env Key</th><th>Default Model</th><th>Capabilities</th></tr>
142
+ ${rows}
143
+ </table></div>
144
+
145
+ <h2>Provider Details</h2>
146
+ <div class="provider-group">${detailSections}</div>
147
+
148
+ <style>
149
+ .provider-group details {
150
+ background: var(--bg-card);
151
+ border: 1px solid var(--border-subtle);
152
+ border-radius: 8px;
153
+ padding: 0.75rem 1rem;
154
+ margin: 0.4rem 0;
155
+ }
156
+ .provider-group summary { cursor: pointer; font-weight: 600; font-size: var(--text-sm); }
157
+ .provider-group details p { margin: 0.75rem 0; font-size: var(--text-sm); color: var(--text-secondary); }
158
+ .provider-group details table { margin: 0.5rem 0; }
159
+ </style>
160
+ `;
161
+
162
+ writeFileSync(join(OUT, 'providers.html'), html(
163
+ 'Providers (Auto-generated)',
164
+ body,
165
+ `Auto-generated provider reference for ${Object.keys(providers).length} AI providers in Clew Code.`,
166
+ ));
167
+ console.log(`✅ Generated providers.html (${Object.keys(providers).length} providers)`);
168
+ }
169
+
170
+ // ═══════════════════════════════════════════════════════════════════════
171
+ // 2. GENERATE TOOLS
172
+ // ═══════════════════════════════════════════════════════════════════════
173
+
174
+ function generateTools(): void {
175
+ const TOOLS_DIR = join(SRC, 'tools');
176
+ const entries = existsSync(TOOLS_DIR) ? readFileSystem(TOOLS_DIR, 1) : [];
177
+
178
+ const toolEntries: Array<{ name: string; prompt: string; description: string }> = [];
179
+
180
+ for (const dir of entries) {
181
+ const promptPath = join(TOOLS_DIR, dir, 'prompt.ts');
182
+ if (!existsSync(promptPath)) continue;
183
+
184
+ const content = readFileSync(promptPath, 'utf8');
185
+
186
+ // Extract DESCRIPTION export
187
+ const descMatch = content.match(/export\s+const\s+DESCRIPTION\s*=\s*([`'"])(.+?)\1/s);
188
+ const description = descMatch ? descMatch[2] : '';
189
+
190
+ // Extract PROMPT export (multiline template literals)
191
+ let prompt = '';
192
+ const promptMatch = content.match(/export\s+const\s+PROMPT\s*=\s*`([\s\S]*?)`/);
193
+ if (promptMatch) {
194
+ prompt = promptMatch[1].trim();
195
+ }
196
+
197
+ // If no template literal, try string literal
198
+ if (!prompt) {
199
+ const strMatch = content.match(/export\s+const\s+PROMPT\s*=\s*['"](.+?)['"]/s);
200
+ if (strMatch) prompt = strMatch[1].trim();
201
+ }
202
+
203
+ toolEntries.push({
204
+ name: dir.replace(/Tool$/, ''),
205
+ prompt,
206
+ description: description || prompt.slice(0, 120),
207
+ });
208
+ }
209
+
210
+ toolEntries.sort((a, b) => a.name.localeCompare(b.name));
211
+
212
+ let rows = '';
213
+ let promptSections = '';
214
+
215
+ for (const t of toolEntries) {
216
+ const desc = t.description || `Built-in tool "${t.name}". See system prompt for details.`;
217
+ rows += `<tr><td><code>${t.name}</code></td><td>${escapeHtml(desc)}</td></tr>\n`;
218
+
219
+ if (t.prompt) {
220
+ promptSections += `<details>
221
+ <summary><code>${t.name}</code> — System Prompt</summary>
222
+ <div class="prompt-box">${escapeHtml(t.prompt)}</div>
223
+ </details>\n`;
224
+ }
225
+ }
226
+
227
+ const body = `
228
+ <h2>All Tools with Prompts</h2>
229
+ <p>Clew provides <strong>${toolEntries.length} tools</strong> with system prompts. These prompts are injected into the AI model's context at session start to define the tool interface.</p>
230
+ <div class="table-wrap"><table>
231
+ <tr><th>Tool</th><th>Description</th></tr>
232
+ ${rows}
233
+ </table></div>
234
+
235
+ <h2>Tool Prompts</h2>
236
+ <p>Each tool's system prompt, as seen by the AI model:</p>
237
+ <div class="tool-prompts">${promptSections}</div>
238
+
239
+ <style>
240
+ .tool-prompts details {
241
+ background: var(--bg-card);
242
+ border: 1px solid var(--border-subtle);
243
+ border-radius: 8px;
244
+ margin: 0.5rem 0;
245
+ padding: 0.75rem 1rem;
246
+ }
247
+ .tool-prompts summary { cursor: pointer; font-weight: 600; font-size: var(--text-sm); }
248
+ .prompt-box {
249
+ background: var(--code-bg);
250
+ border: 1px solid var(--code-border);
251
+ border-radius: 6px;
252
+ padding: 1rem;
253
+ margin: 0.75rem 0;
254
+ font-family: var(--font-mono);
255
+ font-size: var(--text-xs);
256
+ line-height: 1.6;
257
+ color: var(--code-text);
258
+ white-space: pre-wrap;
259
+ overflow-x: auto;
260
+ }
261
+ </style>
262
+ `;
263
+
264
+ writeFileSync(join(OUT, 'tools.html'), html(
265
+ 'Tools & Prompts (Auto-generated)',
266
+ body,
267
+ `Auto-generated tool reference with ${toolEntries.length} system prompts for Clew Code.`,
268
+ ));
269
+ console.log(`✅ Generated tools.html (${toolEntries.length} tools with prompts)`);
270
+ }
271
+
272
+ // ═══════════════════════════════════════════════════════════════════════
273
+ // UTILITIES
274
+ // ═══════════════════════════════════════════════════════════════════════
275
+
276
+ function readFileSystem(dir: string, depth: number): string[] {
277
+ const results: string[] = [];
278
+ const entries = existsSync(dir) ? readdirSync(dir) : [];
279
+ for (const entry of entries) {
280
+ const fullPath = join(dir, entry);
281
+ if (statSync(fullPath).isDirectory()) {
282
+ if (depth > 0) {
283
+ results.push(entry);
284
+ }
285
+ }
286
+ }
287
+ return results;
288
+ }
289
+
290
+ import { readdirSync, statSync } from 'node:fs';
291
+
292
+ function escapeHtml(s: string): string {
293
+ return s
294
+ .replace(/&/g, '&amp;')
295
+ .replace(/</g, '&lt;')
296
+ .replace(/>/g, '&gt;')
297
+ .replace(/"/g, '&quot;');
298
+ }
299
+
300
+ // ═══════════════════════════════════════════════════════════════════════
301
+ // MAIN
302
+ // ═══════════════════════════════════════════════════════════════════════
303
+
304
+ console.log('📄 Generating docs from source...\n');
305
+ generateProviders();
306
+ generateTools();
307
+ console.log('\n✨ Done — outputs in docs/generated/');
package/scripts/gh.sh ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Wrapper around gh CLI that only allows specific subcommands and flags.
5
+ # All commands are scoped to the current repository via GH_REPO or GITHUB_REPOSITORY.
6
+ #
7
+ # Usage:
8
+ # ./scripts/gh.sh issue view 123
9
+ # ./scripts/gh.sh issue view 123 --comments
10
+ # ./scripts/gh.sh issue list --state open --limit 20
11
+ # ./scripts/gh.sh search issues "search query" --limit 10
12
+ # ./scripts/gh.sh label list --limit 100
13
+
14
+ export GH_HOST=github.com
15
+
16
+ REPO="${GH_REPO:-${GITHUB_REPOSITORY:-}}"
17
+ if [[ -z "$REPO" || "$REPO" == */*/* || "$REPO" != */* ]]; then
18
+ echo "Error: GH_REPO or GITHUB_REPOSITORY must be set to owner/repo format (e.g., GITHUB_REPOSITORY=anthropics/claude-code)" >&2
19
+ exit 1
20
+ fi
21
+ export GH_REPO="$REPO"
22
+
23
+ ALLOWED_FLAGS=(--comments --state --limit --label)
24
+ FLAGS_WITH_VALUES=(--state --limit --label)
25
+
26
+ SUB1="${1:-}"
27
+ SUB2="${2:-}"
28
+ CMD="$SUB1 $SUB2"
29
+ case "$CMD" in
30
+ "issue view"|"issue list"|"search issues"|"label list")
31
+ ;;
32
+ *)
33
+ echo "Error: only 'issue view', 'issue list', 'search issues', 'label list' are allowed (e.g., ./scripts/gh.sh issue view 123)" >&2
34
+ exit 1
35
+ ;;
36
+ esac
37
+
38
+ shift 2
39
+
40
+ # Separate flags from positional arguments
41
+ POSITIONAL=()
42
+ FLAGS=()
43
+ skip_next=false
44
+ for arg in "$@"; do
45
+ if [[ "$skip_next" == true ]]; then
46
+ FLAGS+=("$arg")
47
+ skip_next=false
48
+ elif [[ "$arg" == -* ]]; then
49
+ flag="${arg%%=*}"
50
+ matched=false
51
+ for allowed in "${ALLOWED_FLAGS[@]}"; do
52
+ if [[ "$flag" == "$allowed" ]]; then
53
+ matched=true
54
+ break
55
+ fi
56
+ done
57
+ if [[ "$matched" == false ]]; then
58
+ echo "Error: only --comments, --state, --limit, --label flags are allowed (e.g., ./scripts/gh.sh issue list --state open --limit 20)" >&2
59
+ exit 1
60
+ fi
61
+ FLAGS+=("$arg")
62
+ # If flag expects a value and isn't using = syntax, skip next arg
63
+ if [[ "$arg" != *=* ]]; then
64
+ for vflag in "${FLAGS_WITH_VALUES[@]}"; do
65
+ if [[ "$flag" == "$vflag" ]]; then
66
+ skip_next=true
67
+ break
68
+ fi
69
+ done
70
+ fi
71
+ else
72
+ POSITIONAL+=("$arg")
73
+ fi
74
+ done
75
+
76
+ if [[ "$CMD" == "search issues" ]]; then
77
+ QUERY="${POSITIONAL[0]:-}"
78
+ QUERY_LOWER=$(echo "$QUERY" | tr '[:upper:]' '[:lower:]')
79
+ if [[ "$QUERY_LOWER" == *"repo:"* || "$QUERY_LOWER" == *"org:"* || "$QUERY_LOWER" == *"user:"* ]]; then
80
+ echo "Error: search query must not contain repo:, org:, or user: qualifiers (e.g., ./scripts/gh.sh search issues \"bug report\" --limit 10)" >&2
81
+ exit 1
82
+ fi
83
+ gh "$SUB1" "$SUB2" "$QUERY" --repo "$REPO" "${FLAGS[@]}"
84
+ elif [[ "$CMD" == "issue view" ]]; then
85
+ if [[ ${#POSITIONAL[@]} -ne 1 ]] || ! [[ "${POSITIONAL[0]}" =~ ^[0-9]+$ ]]; then
86
+ echo "Error: issue view requires exactly one numeric issue number (e.g., ./scripts/gh.sh issue view 123)" >&2
87
+ exit 1
88
+ fi
89
+ gh "$SUB1" "$SUB2" "${POSITIONAL[0]}" "${FLAGS[@]}"
90
+ else
91
+ if [[ ${#POSITIONAL[@]} -ne 0 ]]; then
92
+ echo "Error: issue list and label list do not accept positional arguments (e.g., ./scripts/gh.sh issue list --state open, ./scripts/gh.sh label list --limit 100)" >&2
93
+ exit 1
94
+ fi
95
+ gh "$SUB1" "$SUB2" "${FLAGS[@]}"
96
+ fi
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env pwsh
2
+ #Requires -PSEdition Desktop
3
+ #Requires -RunAsAdministrator
4
+
5
+ $ErrorActionPreference = 'Stop'
6
+
7
+ function Write-Info { Write-Host "✓ $($args -join ' ')" -ForegroundColor Green }
8
+ function Write-Warn { Write-Host "⚠ $($args -join ' ')" -ForegroundColor Yellow }
9
+ function Write-Error { Write-Host "✗ $($args -join ' ')" -ForegroundColor Red; exit 1 }
10
+
11
+ # ── Install bun if missing ───────────────────────────────────────────────────
12
+ if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
13
+ Write-Info 'Bun not found — installing...'
14
+ try {
15
+ $null = & powershell -NoProfile -Command "irm bun.sh/install.ps1 | iex" 2>&1
16
+ } catch {
17
+ Write-Error "Bun install failed: $($_.Exception.Message)"
18
+ }
19
+ # Refresh PATH so bun is available immediately
20
+ $env:Path = [Environment]::GetEnvironmentVariable('Path', 'User') +
21
+ ';' + [Environment]::GetEnvironmentVariable('Path', 'Machine')
22
+ if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
23
+ Write-Error 'Bun installed but not found in PATH. Restart your shell and try again.'
24
+ }
25
+ Write-Info "Bun $(& bun --version) installed"
26
+ } else {
27
+ Write-Info "Bun $(& bun --version) found"
28
+ }
29
+
30
+ # ── Install clew-code ───────────────────────────────────────────────────────
31
+ Write-Info 'Installing clew-code via bun...'
32
+ # --ignore-scripts skips sharp (from @xenova/transformers) install script,
33
+ # which fails on Node.js <14 or missing build tools. clew imports sharp
34
+ # dynamically — it's only needed for image/ComputerUse features.
35
+ & bun install -g clew-code --ignore-scripts
36
+
37
+ Write-Host "`nDone! Run clew to start." -ForegroundColor Green
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ INSTALL_DIR="${INSTALL_DIR:-$HOME/.clew}"
5
+
6
+ # ── Colors ──────────────────────────────────────────────────────────────────
7
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BOLD='\033[1m'; NC='\033[0m'
8
+ info() { printf "${GREEN}✓${NC} %s\n" "$*"; }
9
+ warn() { printf "${YELLOW}⚠${NC} %s\n" "$*"; }
10
+ err() { printf "${RED}✗${NC} %s\n" "$*" >&2; }
11
+
12
+ # ── Platform ─────────────────────────────────────────────────────────────────
13
+ OS="$(uname -s)"
14
+ case "$OS" in
15
+ Darwin|Linux|MINGW*|CYGWIN*|MSYS*) ;;
16
+ *) err "Unsupported OS: $OS (use install.ps1 on Windows)"; exit 1 ;;
17
+ esac
18
+
19
+ # ── Install bun if missing ──────────────────────────────────────────────────
20
+ if ! command -v bun &>/dev/null; then
21
+ info "Bun not found — installing..."
22
+ case "$OS" in
23
+ Darwin|Linux)
24
+ curl -fsSL https://bun.sh/install | bash
25
+ # Source the updated profile so bun is in PATH for the rest of this script
26
+ export BUN_INSTALL="${BUN_INSTALL:-$HOME/.bun}"
27
+ if [ -f "$BUN_INSTALL/env" ]; then
28
+ # shellcheck source=/dev/null
29
+ source "$BUN_INSTALL/env"
30
+ fi
31
+ ;;
32
+ esac
33
+ if ! command -v bun &>/dev/null; then
34
+ err "Bun installed but not found in PATH. Restart your shell and try again."
35
+ exit 1
36
+ fi
37
+ info "Bun $(bun --version) installed"
38
+ else
39
+ info "Bun $(bun --version) found"
40
+ fi
41
+
42
+ # ── Install clew-code ───────────────────────────────────────────────────────
43
+ info "Installing clew-code via bun..."
44
+ # --ignore-scripts skips sharp (from @xenova/transformers) install script,
45
+ # which fails on Node.js <14 or missing build tools. clew imports sharp
46
+ # dynamically — it's only needed for image/ComputerUse features.
47
+ bun install -g clew-code --ignore-scripts
48
+
49
+ printf "\n${BOLD}Done!${NC} Run ${BOLD}clew${NC} to start.\n"
@@ -0,0 +1,38 @@
1
+ // Single source of truth for issue lifecycle labels, timeouts, and messages.
2
+
3
+ export const lifecycle = [
4
+ {
5
+ label: "invalid",
6
+ days: 3,
7
+ reason: "this doesn't appear to be about Claude Code",
8
+ nudge: "This doesn't appear to be about [Claude Code](https://github.com/anthropics/claude-code). For general Anthropic support, visit [support.anthropic.com](https://support.anthropic.com).",
9
+ },
10
+ {
11
+ label: "needs-repro",
12
+ days: 7,
13
+ reason: "we still need reproduction steps to investigate",
14
+ nudge: "We weren't able to reproduce this. Could you provide steps to trigger the issue — what you ran, what happened, and what you expected?",
15
+ },
16
+ {
17
+ label: "needs-info",
18
+ days: 7,
19
+ reason: "we still need a bit more information to move forward",
20
+ nudge: "We need more information to continue investigating. Can you make sure to include your Claude Code version (`claude --version`), OS, and any error messages or logs?",
21
+ },
22
+ {
23
+ label: "stale",
24
+ days: 14,
25
+ reason: "inactive for too long",
26
+ nudge: "This issue has been automatically marked as stale due to inactivity.",
27
+ },
28
+ {
29
+ label: "autoclose",
30
+ days: 14,
31
+ reason: "inactive for too long",
32
+ nudge: "This issue has been marked for automatic closure.",
33
+ },
34
+ ] as const;
35
+
36
+ export type LifecycleLabel = (typeof lifecycle)[number]["label"];
37
+
38
+ export const STALE_UPVOTE_THRESHOLD = 10;
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bun
2
+
3
+ // Posts a comment when a lifecycle label is applied to an issue,
4
+ // giving the author a heads-up and a chance to respond before auto-close.
5
+
6
+ import { lifecycle } from "./issue-lifecycle.ts";
7
+
8
+ const DRY_RUN = process.argv.includes("--dry-run");
9
+ const token = process.env.GITHUB_TOKEN;
10
+ const repo = process.env.GITHUB_REPOSITORY; // owner/repo
11
+ const label = process.env.LABEL;
12
+ const issueNumber = process.env.ISSUE_NUMBER;
13
+
14
+ if (!DRY_RUN && !token) throw new Error("GITHUB_TOKEN required");
15
+ if (!repo) throw new Error("GITHUB_REPOSITORY required");
16
+ if (!label) throw new Error("LABEL required");
17
+ if (!issueNumber) throw new Error("ISSUE_NUMBER required");
18
+
19
+ const entry = lifecycle.find((l) => l.label === label);
20
+ if (!entry) {
21
+ console.log(`No lifecycle entry for label "${label}", skipping`);
22
+ process.exit(0);
23
+ }
24
+
25
+ const body = `${entry.nudge} This issue will be closed automatically if there's no activity within ${entry.days} days.`;
26
+
27
+ // --
28
+
29
+ if (DRY_RUN) {
30
+ console.log(`Would comment on #${issueNumber} for label "${label}":\n\n${body}`);
31
+ process.exit(0);
32
+ }
33
+
34
+ const response = await fetch(
35
+ `https://api.github.com/repos/${repo}/issues/${issueNumber}/comments`,
36
+ {
37
+ method: "POST",
38
+ headers: {
39
+ Authorization: `Bearer ${token}`,
40
+ Accept: "application/vnd.github.v3+json",
41
+ "Content-Type": "application/json",
42
+ "User-Agent": "lifecycle-comment",
43
+ },
44
+ body: JSON.stringify({ body }),
45
+ }
46
+ );
47
+
48
+ if (!response.ok) {
49
+ const text = await response.text();
50
+ throw new Error(`GitHub API ${response.status}: ${text}`);
51
+ }
52
+
53
+ console.log(`Commented on #${issueNumber} for label "${label}"`);
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Normalize HTML structure: replace inline headers with JS-injected pattern.
3
+ */
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+
7
+ const files = [
8
+ 'docs/features/swarm.html',
9
+ 'docs/internals/hidden-features.html',
10
+ 'docs/internals/hidden-features.th.html',
11
+ ];
12
+
13
+ for (const filePath of files) {
14
+ if (!fs.existsSync(filePath)) {
15
+ console.log('SKIP:', filePath);
16
+ continue;
17
+ }
18
+
19
+ let c = fs.readFileSync(filePath, 'utf-8');
20
+
21
+ // Remove inline header (full block between <header class="header"> and </header>)
22
+ c = c.replace(/<header class="header">[\s\S]*?<\/header>\n?/,
23
+ '<header class="header"></header>\n');
24
+
25
+ // Determine relative JS path
26
+ const dir = path.dirname(filePath);
27
+ const isSub = dir.includes('features') || dir.includes('internals');
28
+ const jsPath = isSub ? '../js/main.js' : 'js/main.js';
29
+
30
+ // Add script if missing
31
+ if (!c.includes('main.js')) {
32
+ c = c.replace('</body>', `<script src="${jsPath}"></script>\n</body>`);
33
+ }
34
+
35
+ fs.writeFileSync(filePath, c, 'utf-8');
36
+ console.log('Fixed:', filePath);
37
+ }