Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.14
- run: bun install --frozen-lockfile
- run: bun run lint
- run: bun test
- name: Check shell scripts
run: git ls-files -z '*.sh' | xargs -0 -r shellcheck
Expand Down
192 changes: 192 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

50 changes: 50 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";

// Every color in the TUI stays a 16-color slot so the user's terminal theme
// decides the actual shade. 256-color and truecolor parameters pin a fixed
// palette and are banned outright.
const banFixedPalettes = {
"no-restricted-syntax": [
"error",
{
selector: String.raw`TemplateElement[value.raw=/[34]8;[25];/]`,
message: "256-color / truecolor SGR is banned; use an ANSI-16 slot so the terminal theme decides the shade.",
},
{
selector: String.raw`Literal[value=/[34]8;[25];/]`,
message: "256-color / truecolor SGR is banned; use an ANSI-16 slot so the terminal theme decides the shade.",
},
],
};

// Raw escape sequences live only in cli.ts's paint definition; everything
// else goes through paint.* so styling stays in one place.
const banRawEscapes = {
"no-restricted-syntax": [
"error",
...banFixedPalettes["no-restricted-syntax"].slice(1),
{
selector: String.raw`TemplateElement[value.raw=/\\u001b|\\x1b/i]`,
message: "Do not write escape sequences directly; go through paint.*.",
},
{
selector: String.raw`Literal[raw=/\\u001b|\\x1b/i]`,
message: "Do not write escape sequences directly; go through paint.*.",
},
],
};

export default tseslint.config(
eslint.configs.recommended,
tseslint.configs.recommended,
{
files: ["src/**/*.ts"],
rules: banFixedPalettes,
},
{
files: ["src/**/*.ts"],
ignores: ["src/cli.ts"],
rules: banRawEscapes,
},
);
5 changes: 5 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ apply *args:
test *args:
@bun test {{ args }}

# Lint the TypeScript sources; extra flags pass through
[group('dev')]
lint *args:
@bun run --silent lint {{ args }}

# Validate the Claude Code plugin manifest
[group('dev')]
validate-plugins *args:
Expand Down
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
},
"scripts": {
"agent-skills": "bun src/cli.ts",
"lint": "eslint .",
"test": "bun test"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"eslint": "^10.8.0",
"typescript": "^6",
"typescript-eslint": "^8.66.0"
}
}
8 changes: 5 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ const paint = {
green: (text: string) => colorEnabled ? `\u001b[32m${text}\u001b[39m` : text,
yellow: (text: string) => colorEnabled ? `\u001b[33m${text}\u001b[39m` : text,
cyan: (text: string) => colorEnabled ? `\u001b[36m${text}\u001b[39m` : text,
gold: (text: string) => colorEnabled ? `\u001b[93m${text}\u001b[39m` : text,
magenta: (text: string) => colorEnabled ? `\u001b[35m${text}\u001b[39m` : text,
};

Expand Down Expand Up @@ -515,14 +516,15 @@ function renderTable(
// magenta — apply refuses to act until a human moves something aside
// green — steady state this repository owns
// cyan — informational; apply never touches it
// dim — benign by design, or a count of zero
// gold — served by the plugin, not this repository's links
// dim — a count of zero
function statusColor(status: string, text: string): string {
if (status === "MANAGED") return paint.green(text);
if (status === "MISSING") return paint.yellow(text);
if (status === "DUPLICATE") return paint.red(text);
if (status.includes("STALE")) return paint.red(text);
if (status === "EXTERNAL") return paint.cyan(text);
if (status === "PLUGIN") return paint.dim(text);
if (status === "PLUGIN") return paint.gold(text);
return text;
}

Expand All @@ -532,7 +534,7 @@ const summaryColumnColor: Array<((text: string) => string) | undefined> = [
paint.green, // Managed
paint.yellow, // Missing
paint.red, // Duplicate
paint.dim, // Plugin
paint.gold, // Plugin
paint.red, // Stale
paint.cyan, // External
];
Expand Down
Loading