Merge upstream/main and resolve setup-python.sh conflict
Resolved conflict in scripts/setup-python.sh by keeping upstream's
improved formatting with color codes and ${PYTHON_CMD} variable.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,9 @@ jobs:
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Run auto-close-duplicates tests
|
||||
run: bun test scripts/auto-close-duplicates
|
||||
|
||||
- name: Auto-close duplicate issues
|
||||
run: bun run scripts/auto-close-duplicates.ts
|
||||
env:
|
||||
|
||||
+2
-1
@@ -9,7 +9,8 @@
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"setup": "echo '⚠️ This npm setup is for the archived web application. For agent development, use: ./scripts/setup-python.sh' && bash scripts/setup.sh"
|
||||
"setup": "echo '⚠️ This npm setup is for the archived web application. For agent development, use: ./scripts/setup-python.sh' && bash scripts/setup.sh",
|
||||
"test:duplicates": "bun test scripts/auto-close-duplicates"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.0",
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* Tests for auto-close-duplicates script: comment filter, 12h check,
|
||||
* author reaction, extractDuplicateIssueNumber, and decideAutoClose
|
||||
* (circular-dup and self-ref prevention).
|
||||
*/
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import {
|
||||
authorDisagreedWithDupe,
|
||||
decideAutoClose,
|
||||
extractDuplicateIssueNumber,
|
||||
getLastDupeComment,
|
||||
isDupeComment,
|
||||
isDupeCommentOldEnough,
|
||||
type GitHubComment,
|
||||
type GitHubIssue,
|
||||
type GitHubReaction,
|
||||
} from "./auto-close-duplicates";
|
||||
|
||||
describe("extractDuplicateIssueNumber", () => {
|
||||
test("extracts #123 format", () => {
|
||||
expect(
|
||||
extractDuplicateIssueNumber("Found a possible duplicate of #1275: ...")
|
||||
).toBe(1275);
|
||||
expect(extractDuplicateIssueNumber("Duplicate of #1")).toBe(1);
|
||||
expect(extractDuplicateIssueNumber("See #1000")).toBe(1000);
|
||||
});
|
||||
|
||||
test("extracts first #N when multiple present", () => {
|
||||
expect(
|
||||
extractDuplicateIssueNumber("Duplicate of #1000 and also #1275")
|
||||
).toBe(1000);
|
||||
});
|
||||
|
||||
test("extracts GitHub issue URL format", () => {
|
||||
expect(
|
||||
extractDuplicateIssueNumber(
|
||||
"Duplicate of https://github.com/adenhq/hive/issues/42"
|
||||
)
|
||||
).toBe(42);
|
||||
});
|
||||
|
||||
test("returns null when no issue number", () => {
|
||||
expect(extractDuplicateIssueNumber("No number here")).toBe(null);
|
||||
expect(extractDuplicateIssueNumber("")).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isDupeComment", () => {
|
||||
test("true when body has 'possible duplicate' and user is Bot", () => {
|
||||
expect(
|
||||
isDupeComment({
|
||||
id: 1,
|
||||
body: "Found a possible duplicate of #1000: same bug",
|
||||
created_at: "",
|
||||
user: { type: "Bot", id: 2 },
|
||||
})
|
||||
).toBe(true);
|
||||
expect(
|
||||
isDupeComment({
|
||||
id: 1,
|
||||
body: "Possible duplicate of #1275",
|
||||
created_at: "",
|
||||
user: { type: "Bot", id: 2 },
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("false when body lacks 'possible duplicate'", () => {
|
||||
expect(
|
||||
isDupeComment({
|
||||
id: 1,
|
||||
body: "Not a duplicate",
|
||||
created_at: "",
|
||||
user: { type: "Bot", id: 2 },
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test("false when user is not Bot", () => {
|
||||
expect(
|
||||
isDupeComment({
|
||||
id: 1,
|
||||
body: "Found a possible duplicate of #1000",
|
||||
created_at: "",
|
||||
user: { type: "User", id: 2 },
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isDupeCommentOldEnough", () => {
|
||||
test("true when comment date is before twelveHoursAgo", () => {
|
||||
const twelveHoursAgo = new Date("2025-01-28T12:00:00Z");
|
||||
const oldComment = new Date("2025-01-28T00:00:00Z");
|
||||
expect(isDupeCommentOldEnough(oldComment, twelveHoursAgo)).toBe(true);
|
||||
});
|
||||
|
||||
test("true when comment date equals twelveHoursAgo", () => {
|
||||
const twelveHoursAgo = new Date("2025-01-28T12:00:00Z");
|
||||
expect(isDupeCommentOldEnough(twelveHoursAgo, twelveHoursAgo)).toBe(true);
|
||||
});
|
||||
|
||||
test("false when comment is after twelveHoursAgo (too recent)", () => {
|
||||
const twelveHoursAgo = new Date("2025-01-28T12:00:00Z");
|
||||
const recentComment = new Date("2025-01-28T18:00:00Z");
|
||||
expect(isDupeCommentOldEnough(recentComment, twelveHoursAgo)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("authorDisagreedWithDupe", () => {
|
||||
test("true when issue author gave thumbs down", () => {
|
||||
const issue = { number: 1275, title: "", state: "open", user: { id: 42 }, created_at: "" };
|
||||
const reactions: GitHubReaction[] = [
|
||||
{ user: { id: 42 }, content: "-1" },
|
||||
];
|
||||
expect(authorDisagreedWithDupe(reactions, issue)).toBe(true);
|
||||
});
|
||||
|
||||
test("false when only other users reacted", () => {
|
||||
const issue = { number: 1275, title: "", state: "open", user: { id: 42 }, created_at: "" };
|
||||
const reactions: GitHubReaction[] = [
|
||||
{ user: { id: 99 }, content: "-1" },
|
||||
{ user: { id: 1 }, content: "+1" },
|
||||
];
|
||||
expect(authorDisagreedWithDupe(reactions, issue)).toBe(false);
|
||||
});
|
||||
|
||||
test("false when author gave +1 or other reaction", () => {
|
||||
const issue = { number: 1275, title: "", state: "open", user: { id: 42 }, created_at: "" };
|
||||
expect(authorDisagreedWithDupe([{ user: { id: 42 }, content: "+1" }], issue)).toBe(false);
|
||||
expect(authorDisagreedWithDupe([{ user: { id: 42 }, content: "eyes" }], issue)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getLastDupeComment", () => {
|
||||
test("returns null when no dupe comments", () => {
|
||||
expect(
|
||||
getLastDupeComment([
|
||||
{ id: 1, body: "Not a duplicate", created_at: "", user: { type: "User", id: 1 } },
|
||||
])
|
||||
).toBe(null);
|
||||
});
|
||||
|
||||
test("returns the only dupe comment when one exists", () => {
|
||||
const c: GitHubComment = {
|
||||
id: 1,
|
||||
body: "Found a possible duplicate of #1000",
|
||||
created_at: "",
|
||||
user: { type: "Bot", id: 2 },
|
||||
};
|
||||
expect(getLastDupeComment([c])).toBe(c);
|
||||
});
|
||||
|
||||
test("returns the last dupe comment when multiple exist", () => {
|
||||
const c1: GitHubComment = {
|
||||
id: 1,
|
||||
body: "Found a possible duplicate of #1000",
|
||||
created_at: "",
|
||||
user: { type: "Bot", id: 2 },
|
||||
};
|
||||
const c2: GitHubComment = {
|
||||
id: 2,
|
||||
body: "Found a possible duplicate of #1275",
|
||||
created_at: "",
|
||||
user: { type: "Bot", id: 2 },
|
||||
};
|
||||
const other: GitHubComment = {
|
||||
id: 3,
|
||||
body: "Some other comment",
|
||||
created_at: "",
|
||||
user: { type: "User", id: 3 },
|
||||
};
|
||||
expect(getLastDupeComment([other, c1, c2])).toBe(c2);
|
||||
});
|
||||
});
|
||||
|
||||
function issue(num: number, state = "open"): GitHubIssue {
|
||||
return {
|
||||
number: num,
|
||||
title: `Issue ${num}`,
|
||||
state,
|
||||
user: { id: 1 },
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
function comment(body: string): GitHubComment {
|
||||
return {
|
||||
id: 1,
|
||||
body,
|
||||
created_at: new Date().toISOString(),
|
||||
user: { type: "Bot", id: 2 },
|
||||
};
|
||||
}
|
||||
|
||||
describe("decideAutoClose", () => {
|
||||
test("returns null when comment has no extractable issue number", async () => {
|
||||
const result = await decideAutoClose(
|
||||
issue(1275),
|
||||
comment("Possible duplicate of something else"),
|
||||
async () => ({ state: "open" })
|
||||
);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
test("returns null when duplicate target is self (same issue number)", async () => {
|
||||
const result = await decideAutoClose(
|
||||
issue(1275),
|
||||
comment("Found a possible duplicate of #1275: same issue"),
|
||||
async () => ({ state: "open" })
|
||||
);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
test("returns null when target issue is closed (avoids circular closure)", async () => {
|
||||
const result = await decideAutoClose(
|
||||
issue(1275),
|
||||
comment("Found a possible duplicate of #1000"),
|
||||
async (num) => (num === 1000 ? { state: "closed" } : { state: "open" })
|
||||
);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
test("returns null when getTargetIssue returns null", async () => {
|
||||
const result = await decideAutoClose(
|
||||
issue(1275),
|
||||
comment("Found a possible duplicate of #1000"),
|
||||
async () => null
|
||||
);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
test("returns null when getTargetIssue throws", async () => {
|
||||
const result = await decideAutoClose(
|
||||
issue(1275),
|
||||
comment("Found a possible duplicate of #1000"),
|
||||
async () => {
|
||||
throw new Error("API error");
|
||||
}
|
||||
);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
test("returns duplicateOf number when target is open (should close)", async () => {
|
||||
const result = await decideAutoClose(
|
||||
issue(1275),
|
||||
comment("Found a possible duplicate of #1000: same bug"),
|
||||
async (num) => (num === 1000 ? { state: "open" } : { state: "closed" })
|
||||
);
|
||||
expect(result).toBe(1000);
|
||||
});
|
||||
|
||||
test("returns null when target state is not exactly 'open' (e.g. uppercase)", async () => {
|
||||
const result = await decideAutoClose(
|
||||
issue(1275),
|
||||
comment("Found a possible duplicate of #1000"),
|
||||
async () => ({ state: "OPEN" } as { state: string })
|
||||
);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -6,21 +6,22 @@ declare global {
|
||||
};
|
||||
}
|
||||
|
||||
interface GitHubIssue {
|
||||
export interface GitHubIssue {
|
||||
number: number;
|
||||
title: string;
|
||||
state: string;
|
||||
user: { id: number };
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface GitHubComment {
|
||||
export interface GitHubComment {
|
||||
id: number;
|
||||
body: string;
|
||||
created_at: string;
|
||||
user: { type: string; id: number };
|
||||
}
|
||||
|
||||
interface GitHubReaction {
|
||||
export interface GitHubReaction {
|
||||
user: { id: number };
|
||||
content: string;
|
||||
}
|
||||
@@ -57,7 +58,41 @@ async function githubRequest<T>(
|
||||
return response.json();
|
||||
}
|
||||
|
||||
function extractDuplicateIssueNumber(commentBody: string): number | null {
|
||||
/** True if comment is a bot "possible duplicate" detection (used for filtering). */
|
||||
export function isDupeComment(comment: GitHubComment): boolean {
|
||||
const bodyLower = comment.body.toLowerCase();
|
||||
return (
|
||||
bodyLower.includes("possible duplicate") && comment.user.type === "Bot"
|
||||
);
|
||||
}
|
||||
|
||||
/** True if the duplicate comment is old enough to auto-close (>= 12h). */
|
||||
export function isDupeCommentOldEnough(
|
||||
dupeCommentDate: Date,
|
||||
twelveHoursAgo: Date
|
||||
): boolean {
|
||||
return dupeCommentDate <= twelveHoursAgo;
|
||||
}
|
||||
|
||||
/** True if the issue author reacted with thumbs down to the duplicate comment. */
|
||||
export function authorDisagreedWithDupe(
|
||||
reactions: GitHubReaction[],
|
||||
issue: GitHubIssue
|
||||
): boolean {
|
||||
return reactions.some(
|
||||
(r) => r.user.id === issue.user.id && r.content === "-1"
|
||||
);
|
||||
}
|
||||
|
||||
/** Returns the most recent duplicate-detection comment, or null if none. */
|
||||
export function getLastDupeComment(
|
||||
comments: GitHubComment[]
|
||||
): GitHubComment | null {
|
||||
const dupeComments = comments.filter(isDupeComment);
|
||||
return dupeComments.length > 0 ? dupeComments[dupeComments.length - 1]! : null;
|
||||
}
|
||||
|
||||
export function extractDuplicateIssueNumber(commentBody: string): number | null {
|
||||
// Try to match #123 format first
|
||||
let match = commentBody.match(/#(\d+)/);
|
||||
if (match) {
|
||||
@@ -73,6 +108,30 @@ function extractDuplicateIssueNumber(commentBody: string): number | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether to auto-close this issue as duplicate of another.
|
||||
* Returns the target issue number to close as duplicate of, or null to skip.
|
||||
* Used by the main loop and by tests.
|
||||
*/
|
||||
export async function decideAutoClose(
|
||||
issue: GitHubIssue,
|
||||
lastDupeComment: GitHubComment,
|
||||
getTargetIssue: (issueNumber: number) => Promise<{ state: string } | null>
|
||||
): Promise<number | null> {
|
||||
const duplicateIssueNumber = extractDuplicateIssueNumber(lastDupeComment.body);
|
||||
if (duplicateIssueNumber === null) return null;
|
||||
|
||||
if (duplicateIssueNumber === issue.number) return null;
|
||||
|
||||
try {
|
||||
const targetIssue = await getTargetIssue(duplicateIssueNumber);
|
||||
if (!targetIssue || targetIssue.state !== "open") return null;
|
||||
return duplicateIssueNumber;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function closeIssueAsDuplicate(
|
||||
owner: string,
|
||||
repo: string,
|
||||
@@ -173,25 +232,18 @@ async function autoCloseDuplicates(): Promise<void> {
|
||||
`[DEBUG] Issue #${issue.number} has ${comments.length} comments`
|
||||
);
|
||||
|
||||
const dupeComments = comments.filter((comment) => {
|
||||
const bodyLower = comment.body.toLowerCase();
|
||||
return (
|
||||
bodyLower.includes("possible duplicate") &&
|
||||
comment.user.type === "Bot"
|
||||
);
|
||||
});
|
||||
const lastDupeComment = getLastDupeComment(comments);
|
||||
const dupeCount = comments.filter(isDupeComment).length;
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} has ${dupeComments.length} duplicate detection comments`
|
||||
`[DEBUG] Issue #${issue.number} has ${dupeCount} duplicate detection comments`
|
||||
);
|
||||
|
||||
if (dupeComments.length === 0) {
|
||||
if (lastDupeComment === null) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - no duplicate comments found, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const lastDupeComment = dupeComments[dupeComments.length - 1];
|
||||
const dupeCommentDate = new Date(lastDupeComment.created_at);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${
|
||||
@@ -199,7 +251,7 @@ async function autoCloseDuplicates(): Promise<void> {
|
||||
} - most recent duplicate comment from: ${dupeCommentDate.toISOString()}`
|
||||
);
|
||||
|
||||
if (dupeCommentDate > twelveHoursAgo) {
|
||||
if (!isDupeCommentOldEnough(dupeCommentDate, twelveHoursAgo)) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - duplicate comment is too recent, skipping`
|
||||
);
|
||||
@@ -224,10 +276,7 @@ async function autoCloseDuplicates(): Promise<void> {
|
||||
`[DEBUG] Issue #${issue.number} - duplicate comment has ${reactions.length} reactions`
|
||||
);
|
||||
|
||||
const authorThumbsDown = reactions.some(
|
||||
(reaction) =>
|
||||
reaction.user.id === issue.user.id && reaction.content === "-1"
|
||||
);
|
||||
const authorThumbsDown = authorDisagreedWithDupe(reactions, issue);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - author thumbs down reaction: ${authorThumbsDown}`
|
||||
);
|
||||
@@ -239,12 +288,19 @@ async function autoCloseDuplicates(): Promise<void> {
|
||||
continue;
|
||||
}
|
||||
|
||||
const duplicateIssueNumber = extractDuplicateIssueNumber(
|
||||
lastDupeComment.body
|
||||
const duplicateOf = await decideAutoClose(
|
||||
issue,
|
||||
lastDupeComment,
|
||||
(issueNumber) =>
|
||||
githubRequest<GitHubIssue>(
|
||||
`/repos/${owner}/${repo}/issues/${issueNumber}`,
|
||||
token
|
||||
).then((i) => ({ state: i.state }))
|
||||
);
|
||||
if (!duplicateIssueNumber) {
|
||||
|
||||
if (duplicateOf === null) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - could not extract duplicate issue number from comment, skipping`
|
||||
`[DEBUG] Issue #${issue.number} - skipping (invalid/self/closed target or fetch error)`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -254,17 +310,17 @@ async function autoCloseDuplicates(): Promise<void> {
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`[INFO] Auto-closing issue #${issue.number} as duplicate of #${duplicateIssueNumber}: ${issueUrl}`
|
||||
`[INFO] Auto-closing issue #${issue.number} as duplicate of #${duplicateOf}: ${issueUrl}`
|
||||
);
|
||||
await closeIssueAsDuplicate(
|
||||
owner,
|
||||
repo,
|
||||
issue.number,
|
||||
duplicateIssueNumber,
|
||||
duplicateOf,
|
||||
token
|
||||
);
|
||||
console.log(
|
||||
`[SUCCESS] Successfully closed issue #${issue.number} as duplicate of #${duplicateIssueNumber}`
|
||||
`[SUCCESS] Successfully closed issue #${issue.number} as duplicate of #${duplicateOf}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
@@ -278,6 +334,8 @@ async function autoCloseDuplicates(): Promise<void> {
|
||||
);
|
||||
}
|
||||
|
||||
autoCloseDuplicates().catch(console.error);
|
||||
if (import.meta.main) {
|
||||
autoCloseDuplicates().catch(console.error);
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
+62
-22
@@ -25,16 +25,48 @@ echo " Aden Agent Framework - Python Setup"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
|
||||
# Check for Python
|
||||
if ! command -v python &> /dev/null && ! command -v python3 &> /dev/null; then
|
||||
# Check for Python, honoring $PYTHON even if it's not on PATH
|
||||
if [ -n "$PYTHON" ]; then
|
||||
if [ ! -x "$PYTHON" ] && ! command -v "$PYTHON" &> /dev/null; then
|
||||
echo -e "${RED}Error: PYTHON is set to '$PYTHON' but it was not found or not executable.${NC}"
|
||||
echo "Example: PYTHON=/opt/python3.12/bin/python3.12 ./scripts/setup-python.sh"
|
||||
exit 1
|
||||
fi
|
||||
elif ! command -v python &> /dev/null \
|
||||
&& ! command -v python3 &> /dev/null \
|
||||
&& ! command -v python3.11 &> /dev/null \
|
||||
&& ! command -v python3.12 &> /dev/null; then
|
||||
echo -e "${RED}Error: Python is not installed.${NC}"
|
||||
echo "Please install Python 3.11+ from https://python.org"
|
||||
echo "Please install Python 3.11+ (recommended 3.12) from https://python.org"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use python3 if available, otherwise python
|
||||
PYTHON_CMD="python3"
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
# Choose Python interpreter
|
||||
# Priority:
|
||||
# 1) $PYTHON env var override (e.g., PYTHON=python3.11)
|
||||
# 2) python3.12 if available
|
||||
# 3) python3.11 if available
|
||||
# 4) python3
|
||||
# 5) python
|
||||
PYTHON_CMD="${PYTHON:-}"
|
||||
|
||||
if [ -n "$PYTHON_CMD" ]; then
|
||||
if [ -x "$PYTHON_CMD" ]; then
|
||||
: # absolute / explicit path provided and executable
|
||||
elif command -v "$PYTHON_CMD" &> /dev/null; then
|
||||
: # found on PATH
|
||||
else
|
||||
echo -e "${RED}Error: PYTHON is set to '$PYTHON_CMD' but it was not found in PATH or as an absolute path.${NC}"
|
||||
echo "Example: PYTHON=/opt/python3.12/bin/python3.12 ./scripts/setup-python.sh"
|
||||
exit 1
|
||||
fi
|
||||
elif command -v python3.12 &> /dev/null; then
|
||||
PYTHON_CMD="python3.12"
|
||||
elif command -v python3.11 &> /dev/null; then
|
||||
PYTHON_CMD="python3.11"
|
||||
elif command -v python3 &> /dev/null; then
|
||||
PYTHON_CMD="python3"
|
||||
else
|
||||
PYTHON_CMD="python"
|
||||
fi
|
||||
|
||||
@@ -43,18 +75,26 @@ PYTHON_VERSION=$($PYTHON_CMD -c 'import sys; print(f"{sys.version_info.major}.{s
|
||||
PYTHON_MAJOR=$($PYTHON_CMD -c 'import sys; print(sys.version_info.major)')
|
||||
PYTHON_MINOR=$($PYTHON_CMD -c 'import sys; print(sys.version_info.minor)')
|
||||
|
||||
echo -e "${BLUE}Detected Python:${NC} $PYTHON_VERSION"
|
||||
echo -e "${BLUE}Detected Python:${NC} $PYTHON_VERSION (binary: ${PYTHON_CMD})"
|
||||
|
||||
# Require 3.11+
|
||||
if [ "$PYTHON_MAJOR" -lt 3 ] || ([ "$PYTHON_MAJOR" -eq 3 ] && [ "$PYTHON_MINOR" -lt 11 ]); then
|
||||
echo -e "${RED}Error: Python 3.11+ is required (found $PYTHON_VERSION)${NC}"
|
||||
echo "Please upgrade your Python installation"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$PYTHON_MINOR" -lt 11 ]; then
|
||||
echo -e "${YELLOW}Warning: Python 3.11+ is recommended for best compatibility${NC}"
|
||||
echo -e "${YELLOW}You have Python $PYTHON_VERSION which may work but is not officially supported${NC}"
|
||||
echo -e "${RED}Error: Python 3.11+ is required (found $PYTHON_VERSION via '$PYTHON_CMD')${NC}"
|
||||
echo ""
|
||||
echo "Fix options (Linux/macOS/WSL):"
|
||||
echo " • Ubuntu/Debian/Pop!_OS/WSL:"
|
||||
echo " sudo apt update && sudo apt install -y python3.11 python3.11-venv python3.11-dev"
|
||||
echo " • Fedora:"
|
||||
echo " sudo dnf install -y python3.11 python3.11-devel"
|
||||
echo " • Arch/Manjaro:"
|
||||
echo " sudo pacman -S python"
|
||||
echo " • macOS (Homebrew):"
|
||||
echo " brew install python@3.11"
|
||||
echo ""
|
||||
echo "Then re-run with an explicit interpreter if needed:"
|
||||
echo " PYTHON=python3.11 ./scripts/setup-python.sh"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓${NC} Python version check passed"
|
||||
@@ -215,15 +255,15 @@ echo " • All dependencies and compatibility fixes applied"
|
||||
echo ""
|
||||
echo "To run agents, use:"
|
||||
echo ""
|
||||
echo " From project root: "
|
||||
echo " PYTHONPATH=core:exports $TOOLS_PYTHON -m agent_name validate"
|
||||
echo " PYTHONPATH=core:exports $TOOLS_PYTHON -m agent_name info"
|
||||
echo " PYTHONPATH=core:exports $TOOLS_PYTHON -m agent_name run --input '{...}'"
|
||||
echo " ${BLUE}# From project root:${NC}"
|
||||
echo " PYTHONPATH=core:exports ${PYTHON_CMD} -m agent_name validate"
|
||||
echo " PYTHONPATH=core:exports ${PYTHON_CMD} -m agent_name info"
|
||||
echo " PYTHONPATH=core:exports ${PYTHON_CMD} -m agent_name run --input '{...}'"
|
||||
echo ""
|
||||
echo "Available commands for your new agent:"
|
||||
echo " PYTHONPATH=core:exports $TOOLS_PYTHON -m support_ticket_agent validate"
|
||||
echo " PYTHONPATH=core:exports $TOOLS_PYTHON -m support_ticket_agent info"
|
||||
echo " PYTHONPATH=core:exports $TOOLS_PYTHON -m support_ticket_agent run --input '{\"ticket_content\":\"...\",\"customer_id\":\"...\",\"ticket_id\":\"...\"}'"
|
||||
echo " PYTHONPATH=core:exports ${PYTHON_CMD} -m support_ticket_agent validate"
|
||||
echo " PYTHONPATH=core:exports ${PYTHON_CMD} -m support_ticket_agent info"
|
||||
echo " PYTHONPATH=core:exports ${PYTHON_CMD} -m support_ticket_agent run --input '{\"ticket_content\":\"...\",\"customer_id\":\"...\",\"ticket_id\":\"...\"}'"
|
||||
echo ""
|
||||
echo "To build new agents, use Claude Code skills:"
|
||||
echo " • /building-agents - Build a new agent"
|
||||
|
||||
Reference in New Issue
Block a user