clew-code 0.2.28 → 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.
- package/README.md +38 -28
- package/bin/clew.cjs +2 -2
- package/dist/main.js +2318 -2331
- package/docs/architecture.html +90 -91
- package/docs/changelog.html +141 -150
- package/docs/cli-reference.html +89 -90
- package/docs/commands.html +10 -9
- package/docs/daemon.html +62 -62
- package/docs/generated/providers.html +625 -0
- package/docs/generated/tools.html +559 -0
- package/docs/index.html +10 -13
- package/docs/personal-profile.html +3 -3
- package/docs/providers.html +625 -87
- package/docs/quick-start.html +81 -81
- package/docs/tools.html +551 -175
- package/docs/troubleshooting.html +86 -86
- package/package.json +165 -165
- package/scripts/auto-close-duplicates.ts +277 -0
- package/scripts/backfill-duplicate-comments.ts +213 -0
- package/scripts/codegraph.ts +221 -0
- package/scripts/comment-on-duplicates.sh +95 -0
- package/scripts/edit-issue-labels.sh +84 -0
- package/scripts/final-peer-rename.mjs +46 -0
- package/scripts/fix-encoding.mjs +83 -0
- package/scripts/fix-inconsistencies.mjs +67 -0
- package/scripts/generate-docs.ts +307 -0
- package/scripts/gh.sh +96 -0
- package/scripts/install.ps1 +37 -0
- package/scripts/install.sh +49 -0
- package/scripts/issue-lifecycle.ts +38 -0
- package/scripts/lifecycle-comment.ts +53 -0
- package/scripts/normalize-html.mjs +37 -0
- package/scripts/preload.ts +159 -0
- package/scripts/rename-content-peer-to-swarm.mjs +67 -0
- package/scripts/rename-hooks-mesh.mjs +6 -0
- package/scripts/rename-peer-to-swarm.mjs +62 -0
- package/scripts/run_devcontainer_claude_code.ps1 +152 -0
- package/scripts/scrape.py +82 -0
- package/scripts/session.ts +195 -0
- package/scripts/sweep.ts +168 -0
- package/scripts/write-discovery-test.cjs +93 -0
- package/scripts/write-discovery-test2.cjs +65 -0
- package/scripts/write-server-final.cjs +108 -0
- package/scripts/write-server-test.cjs +69 -0
- package/scripts/write-server-test2.cjs +99 -0
- package/scripts/write-server-test3.cjs +59 -0
- package/scripts/write-server-test4.cjs +108 -0
- package/scripts/write-server-v2.cjs +142 -0
- package/scripts/write-server-v2b.cjs +129 -0
- package/scripts/write-server-v2c.cjs +136 -0
- package/scripts/write-store-test.cjs +12 -0
- package/scripts/write-store-test2.cjs +163 -0
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
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Context Preloader
|
|
4
|
+
*
|
|
5
|
+
* Prepares module context before Claude Code starts editing.
|
|
6
|
+
* Usage: bun run scripts/preload.ts <module-name>
|
|
7
|
+
* bun run scripts/preload.ts src/bridge
|
|
8
|
+
* bun run scripts/preload.ts src/services/ai
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs'
|
|
12
|
+
import { join, dirname, basename } from 'path'
|
|
13
|
+
import { execSync } from 'child_process'
|
|
14
|
+
|
|
15
|
+
const ROOT = join(import.meta.dirname, '..')
|
|
16
|
+
const SRC = join(ROOT, 'src')
|
|
17
|
+
const CONTEXT_DIR = join(ROOT, '.clew', 'context')
|
|
18
|
+
|
|
19
|
+
const moduleArg = process.argv[2]
|
|
20
|
+
if (!moduleArg) {
|
|
21
|
+
console.error('Usage: bun run scripts/preload.ts <module-path>')
|
|
22
|
+
console.error(' e.g. bun run scripts/preload.ts bridge')
|
|
23
|
+
console.error(' e.g. bun run scripts/preload.ts src/bridge')
|
|
24
|
+
process.exit(1)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const modPath = moduleArg.replace(/\\/g, '/').replace(/^src\//, '')
|
|
28
|
+
const targetDir = join(SRC, modPath)
|
|
29
|
+
|
|
30
|
+
function run(cmd: string): string {
|
|
31
|
+
try {
|
|
32
|
+
return execSync(cmd, { cwd: ROOT, encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'ignore'] })
|
|
33
|
+
} catch (e: any) {
|
|
34
|
+
return e.stdout || ''
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function findFiles(dir: string): string[] {
|
|
39
|
+
try {
|
|
40
|
+
const entries = readdirSync(dir)
|
|
41
|
+
return entries
|
|
42
|
+
.filter(f => f.endsWith('.ts') || f.endsWith('.tsx'))
|
|
43
|
+
.map(f => join(dir, f))
|
|
44
|
+
.filter(f => statSync(f).isFile())
|
|
45
|
+
} catch {
|
|
46
|
+
return []
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Gather data ──
|
|
51
|
+
const files = findFiles(targetDir)
|
|
52
|
+
|
|
53
|
+
const md: string[] = []
|
|
54
|
+
md.push(`# Context: ${modPath}`)
|
|
55
|
+
md.push(`_Preloaded ${new Date().toISOString().slice(0, 16).replace('T', ' ')}_`)
|
|
56
|
+
md.push('')
|
|
57
|
+
|
|
58
|
+
// Summary
|
|
59
|
+
md.push(`## Scope`)
|
|
60
|
+
md.push('')
|
|
61
|
+
md.push(`Module: \`${modPath}\``)
|
|
62
|
+
md.push(`Files: ${files.length}`)
|
|
63
|
+
md.push('')
|
|
64
|
+
|
|
65
|
+
// File listing with sizes
|
|
66
|
+
md.push(`## Files`)
|
|
67
|
+
md.push('')
|
|
68
|
+
md.push(`| File | Lines | Exports | TODOs |`)
|
|
69
|
+
md.push(`|------|-------|---------|-------|`)
|
|
70
|
+
|
|
71
|
+
let totalLines = 0
|
|
72
|
+
for (const file of files) {
|
|
73
|
+
const relName = file.replace(/\\/g, '/').replace(ROOT.replace(/\\/g, '/') + '/', '')
|
|
74
|
+
const content = readFileSync(file, 'utf-8')
|
|
75
|
+
const lines = content.split('\n').length
|
|
76
|
+
totalLines += lines
|
|
77
|
+
|
|
78
|
+
const exports = content.match(/export (async )?(function|const|class|interface|type) [a-zA-Z_$][a-zA-Z0-9_$]*/g) || []
|
|
79
|
+
const exportList = exports.map(e => e.split(/\s+/).pop()).filter(Boolean).slice(0, 8).join(', ')
|
|
80
|
+
const todos = (content.match(/\bTODO|FIXME\b/g) || []).length
|
|
81
|
+
|
|
82
|
+
md.push(`| ${relName} | ${lines} | ${exportList || '-'} | ${todos || '-'} |`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
md.push(`| **Total** | **${totalLines}** | | |`)
|
|
86
|
+
md.push('')
|
|
87
|
+
|
|
88
|
+
// Internal dependencies
|
|
89
|
+
md.push(`## Internal Dependencies`)
|
|
90
|
+
md.push('')
|
|
91
|
+
md.push('```')
|
|
92
|
+
const deps = files.map(f => {
|
|
93
|
+
const c = readFileSync(f, 'utf-8')
|
|
94
|
+
return [...c.matchAll(/from\s+['"](src\/[^'"]+)['"]/g)].map(m => m[1])
|
|
95
|
+
}).flat()
|
|
96
|
+
const uniqueDeps = [...new Set(deps)].sort()
|
|
97
|
+
for (const d of uniqueDeps) md.push(d)
|
|
98
|
+
if (!uniqueDeps.length) md.push('(none)')
|
|
99
|
+
md.push('```')
|
|
100
|
+
md.push('')
|
|
101
|
+
|
|
102
|
+
// Recent git history
|
|
103
|
+
md.push(`## Recent Changes`)
|
|
104
|
+
md.push('')
|
|
105
|
+
md.push('```')
|
|
106
|
+
try {
|
|
107
|
+
const gitLog = run(`git log --oneline -10 -- "src/${modPath}/"`).trim()
|
|
108
|
+
md.push(gitLog || '(no recent changes)')
|
|
109
|
+
} catch {
|
|
110
|
+
md.push('(no git history)')
|
|
111
|
+
}
|
|
112
|
+
md.push('```')
|
|
113
|
+
md.push('')
|
|
114
|
+
|
|
115
|
+
// Key types
|
|
116
|
+
const typePattern = /export (interface|type) (\w+)/g
|
|
117
|
+
const allTypes: string[] = []
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
const c = readFileSync(file, 'utf-8')
|
|
120
|
+
let m: RegExpExecArray | null
|
|
121
|
+
while ((m = typePattern.exec(c)) !== null) allTypes.push(m[2])
|
|
122
|
+
}
|
|
123
|
+
if (allTypes.length) {
|
|
124
|
+
md.push(`## Key Types`)
|
|
125
|
+
md.push('')
|
|
126
|
+
md.push('- ' + [...new Set(allTypes)].join('\n- '))
|
|
127
|
+
md.push('')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Open TODOs
|
|
131
|
+
const allTodos: { file: string; line: string }[] = []
|
|
132
|
+
for (const file of files) {
|
|
133
|
+
const c = readFileSync(file, 'utf-8')
|
|
134
|
+
const lines = c.split('\n')
|
|
135
|
+
lines.forEach((l, i) => {
|
|
136
|
+
if (/\bTODO|FIXME\b/.test(l)) {
|
|
137
|
+
const text = l.replace(/^\s*\/\/\s*/, '').replace(/^\s*/, '').slice(0, 80)
|
|
138
|
+
allTodos.push({ file: file.replace(/\\/g, '/').replace(ROOT.replace(/\\/g, '/') + '/', ''), line: text })
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (allTodos.length) {
|
|
144
|
+
md.push(`## Open TODOs`)
|
|
145
|
+
md.push('')
|
|
146
|
+
for (const t of allTodos) md.push(`- \`${t.file}\`: ${t.line}`)
|
|
147
|
+
md.push('')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Write
|
|
151
|
+
mkdirSync(CONTEXT_DIR, { recursive: true })
|
|
152
|
+
const outPath = join(CONTEXT_DIR, `${modPath.replace(/\//g, '-')}.md`)
|
|
153
|
+
writeFileSync(outPath, md.join('\n'), 'utf-8')
|
|
154
|
+
|
|
155
|
+
console.log(`\n✅ Preloaded: ${modPath}`)
|
|
156
|
+
console.log(` ${files.length} files, ${totalLines} lines`)
|
|
157
|
+
console.log(` Saved to: ${outPath}`)
|
|
158
|
+
console.log('')
|
|
159
|
+
console.log('Claude Code can now read this context via Read tool.')
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content rename: peer -> swarm in all Swarm* tool files
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
function walk(dir) {
|
|
8
|
+
const r = [];
|
|
9
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
10
|
+
const f = path.join(dir, e.name);
|
|
11
|
+
if (e.isDirectory()) r.push(...walk(f));
|
|
12
|
+
else if (e.name.endsWith('.ts') || e.name.endsWith('.tsx')) r.push(f);
|
|
13
|
+
}
|
|
14
|
+
return r;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const dirs = [
|
|
18
|
+
'src/tools/SwarmBroadcastTool',
|
|
19
|
+
'src/tools/SwarmDisconnectTool',
|
|
20
|
+
'src/tools/SwarmDiscoverTool',
|
|
21
|
+
'src/tools/SwarmJoinTool',
|
|
22
|
+
'src/tools/SwarmListMessagesTool',
|
|
23
|
+
'src/tools/SwarmListRolesTool',
|
|
24
|
+
'src/tools/SwarmPingTool',
|
|
25
|
+
'src/tools/SwarmRunTool',
|
|
26
|
+
'src/tools/SwarmSendMessageTool',
|
|
27
|
+
'src/tools/SwarmSetNameTool',
|
|
28
|
+
'src/tools/SwarmSetRoleTool',
|
|
29
|
+
'src/tools/SwarmShareTool',
|
|
30
|
+
'src/tools/SwarmSpawnTool',
|
|
31
|
+
'src/tools/ProcessSwarmTool',
|
|
32
|
+
'src/tools/CodexSwarmTool',
|
|
33
|
+
'src/swarm',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const files = [];
|
|
37
|
+
for (const d of dirs) {
|
|
38
|
+
if (fs.existsSync(d)) files.push(...walk(d));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const f of files) {
|
|
42
|
+
let c = fs.readFileSync(f, 'utf-8');
|
|
43
|
+
const orig = c;
|
|
44
|
+
|
|
45
|
+
// PeerXxx -> SwarmXxx (class/identifier names)
|
|
46
|
+
c = c.replace(/\bPeer(Broadcast|Disconnect|Discover|Join|ListMessages|ListRoles|Ping|Run|SendMessage|SetName|SetRole|Share|Spawn|Info|Help|Server|Store|Discovery|StatusLine)\b/g, 'Swarm$1');
|
|
47
|
+
c = c.replace(/\bProcessPeer(Provider|Tool)\b/g, 'ProcessSwarm$1');
|
|
48
|
+
c = c.replace(/\bCodexPeerTool\b/g, 'CodexSwarmTool');
|
|
49
|
+
|
|
50
|
+
// peer_xxx -> swarm_xxx (tool name constants)
|
|
51
|
+
c = c.replace(/peer_/g, 'swarm_');
|
|
52
|
+
|
|
53
|
+
// peerXxx -> swarmXxx (camelCase variables)
|
|
54
|
+
c = c.replace(/\bpeer(Server|Store|Discovery|Port|Name|Info|List|Count|Connection|Sharing|Health)\b/g, 'swarm$1');
|
|
55
|
+
|
|
56
|
+
// usePeerAutoInject -> useSwarmAutoInject
|
|
57
|
+
c = c.replace(/\busePeerAutoInject\b/g, 'useSwarmAutoInject');
|
|
58
|
+
|
|
59
|
+
// /peer -> /swarm (command paths)
|
|
60
|
+
c = c.replace(/\/peer\b/g, '/swarm');
|
|
61
|
+
|
|
62
|
+
if (c !== orig) {
|
|
63
|
+
fs.writeFileSync(f, c, 'utf-8');
|
|
64
|
+
console.log('OK:', f);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
console.log('DONE');
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk rename: peer -> swarm across all source files
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
function walk(dir) {
|
|
8
|
+
const results = [];
|
|
9
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
10
|
+
const full = path.join(dir, entry.name);
|
|
11
|
+
if (entry.isDirectory()) {
|
|
12
|
+
if (!entry.name.startsWith('.') && entry.name !== 'node_modules' && entry.name !== 'dist') {
|
|
13
|
+
results.push(...walk(full));
|
|
14
|
+
}
|
|
15
|
+
} else if (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) {
|
|
16
|
+
results.push(full);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return results;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Only replace import paths — safe, targeted changes
|
|
23
|
+
const replacements = [
|
|
24
|
+
// Import paths for peer/ directory
|
|
25
|
+
[/from ['"]\.\.\/peer\//g, `from '../swarm/`],
|
|
26
|
+
[/from ['"]\.\/peer\//g, `from './swarm/`],
|
|
27
|
+
[/from ['"]\.\.\/\.\.\/peer\//g, `from '../../swarm/`],
|
|
28
|
+
[/from ['"]\.\.\/\.\.\/\.\.\/peer\//g, `from '../../../swarm/`],
|
|
29
|
+
|
|
30
|
+
// Import paths for tools/Peer* -> tools/Swarm*
|
|
31
|
+
[/from ['"]\.\.\/\.\.\/tools\/PeerInfoTool\//g, `from '../../tools/SwarmInfoTool/`],
|
|
32
|
+
[/from ['"]\.\.\/tools\/PeerInfoTool\//g, `from '../tools/SwarmInfoTool/`],
|
|
33
|
+
[/from ['"]\.\/PeerInfoTool\//g, `from './SwarmInfoTool/`],
|
|
34
|
+
[/from ['"]\.\.\/\.\.\/tools\/PeerHelpTool\//g, `from '../../tools/SwarmHelpTool/`],
|
|
35
|
+
[/from ['"]\.\.\/tools\/PeerHelpTool\//g, `from '../tools/SwarmHelpTool/`],
|
|
36
|
+
[/from ['"]\.\/PeerHelpTool\//g, `from './SwarmHelpTool/`],
|
|
37
|
+
[/from ['"]\.\.\/peer\//g, `from '../swarm/`],
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const srcDir = 'src';
|
|
41
|
+
const files = walk(srcDir);
|
|
42
|
+
let totalChanged = 0;
|
|
43
|
+
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
let content = fs.readFileSync(file, 'utf-8');
|
|
46
|
+
let changed = false;
|
|
47
|
+
|
|
48
|
+
for (const [pattern, replacement] of replacements) {
|
|
49
|
+
if (pattern.test(content)) {
|
|
50
|
+
content = content.replace(pattern, replacement);
|
|
51
|
+
changed = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (changed) {
|
|
56
|
+
fs.writeFileSync(file, content, 'utf-8');
|
|
57
|
+
totalChanged++;
|
|
58
|
+
console.log('Updated:', file);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(`\nTotal files updated: ${totalChanged}`);
|