@sheepbun/yips 0.1.1
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/dist/agent/commands/command-catalog.js +243 -0
- package/dist/agent/commands/commands.js +418 -0
- package/dist/agent/conductor.js +118 -0
- package/dist/agent/context/code-context.js +68 -0
- package/dist/agent/context/memory-store.js +159 -0
- package/dist/agent/context/session-store.js +211 -0
- package/dist/agent/protocol/tool-protocol.js +160 -0
- package/dist/agent/skills/skills.js +327 -0
- package/dist/agent/tools/tool-executor.js +415 -0
- package/dist/agent/tools/tool-safety.js +52 -0
- package/dist/app/index.js +35 -0
- package/dist/app/repl.js +105 -0
- package/dist/app/update-check.js +132 -0
- package/dist/app/version.js +51 -0
- package/dist/code-context.js +68 -0
- package/dist/colors.js +204 -0
- package/dist/command-catalog.js +242 -0
- package/dist/commands.js +350 -0
- package/dist/conductor.js +94 -0
- package/dist/config/config.js +335 -0
- package/dist/config/hooks.js +187 -0
- package/dist/config.js +335 -0
- package/dist/downloader-state.js +302 -0
- package/dist/downloader-ui.js +289 -0
- package/dist/gateway/adapters/discord.js +108 -0
- package/dist/gateway/adapters/formatting.js +96 -0
- package/dist/gateway/adapters/telegram.js +106 -0
- package/dist/gateway/adapters/types.js +2 -0
- package/dist/gateway/adapters/whatsapp.js +124 -0
- package/dist/gateway/auth-policy.js +66 -0
- package/dist/gateway/core.js +87 -0
- package/dist/gateway/headless-conductor.js +328 -0
- package/dist/gateway/message-router.js +23 -0
- package/dist/gateway/rate-limiter.js +48 -0
- package/dist/gateway/runtime/backend-policy.js +18 -0
- package/dist/gateway/runtime/discord-bot.js +104 -0
- package/dist/gateway/runtime/discord-main.js +69 -0
- package/dist/gateway/session-manager.js +77 -0
- package/dist/gateway/types.js +2 -0
- package/dist/hardware.js +92 -0
- package/dist/hooks.js +187 -0
- package/dist/index.js +34 -0
- package/dist/input-engine.js +250 -0
- package/dist/llama-client.js +227 -0
- package/dist/llama-server.js +620 -0
- package/dist/llm/llama-client.js +227 -0
- package/dist/llm/llama-server.js +620 -0
- package/dist/llm/token-counter.js +47 -0
- package/dist/memory-store.js +159 -0
- package/dist/messages.js +59 -0
- package/dist/model-downloader.js +382 -0
- package/dist/model-manager-state.js +118 -0
- package/dist/model-manager-ui.js +194 -0
- package/dist/model-manager.js +190 -0
- package/dist/models/hardware.js +92 -0
- package/dist/models/model-downloader.js +382 -0
- package/dist/models/model-manager.js +190 -0
- package/dist/prompt-box.js +78 -0
- package/dist/prompt-composer.js +498 -0
- package/dist/repl.js +105 -0
- package/dist/session-store.js +211 -0
- package/dist/spinner.js +76 -0
- package/dist/title-box.js +388 -0
- package/dist/token-counter.js +47 -0
- package/dist/tool-executor.js +415 -0
- package/dist/tool-protocol.js +121 -0
- package/dist/tool-safety.js +52 -0
- package/dist/tui/app.js +2553 -0
- package/dist/tui/startup.js +56 -0
- package/dist/tui-input-routing.js +53 -0
- package/dist/tui.js +51 -0
- package/dist/types/app-types.js +2 -0
- package/dist/types.js +2 -0
- package/dist/ui/colors.js +204 -0
- package/dist/ui/downloader/downloader-state.js +302 -0
- package/dist/ui/downloader/downloader-ui.js +289 -0
- package/dist/ui/input/input-engine.js +250 -0
- package/dist/ui/input/tui-input-routing.js +53 -0
- package/dist/ui/input/vt-session.js +168 -0
- package/dist/ui/messages.js +59 -0
- package/dist/ui/model-manager/model-manager-state.js +118 -0
- package/dist/ui/model-manager/model-manager-ui.js +194 -0
- package/dist/ui/prompt/prompt-box.js +78 -0
- package/dist/ui/prompt/prompt-composer.js +498 -0
- package/dist/ui/spinner.js +76 -0
- package/dist/ui/title-box.js +388 -0
- package/dist/ui/tui/app.js +6 -0
- package/dist/ui/tui/autocomplete.js +85 -0
- package/dist/ui/tui/constants.js +18 -0
- package/dist/ui/tui/history.js +29 -0
- package/dist/ui/tui/layout.js +341 -0
- package/dist/ui/tui/runtime-core.js +2584 -0
- package/dist/ui/tui/runtime-utils.js +53 -0
- package/dist/ui/tui/start-tui.js +54 -0
- package/dist/ui/tui/startup.js +56 -0
- package/dist/version.js +51 -0
- package/dist/vt-session.js +168 -0
- package/install.sh +457 -0
- package/package.json +128 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getInstalledPackageVersion = getInstalledPackageVersion;
|
|
4
|
+
exports.checkForUpdates = checkForUpdates;
|
|
5
|
+
const promises_1 = require("node:fs/promises");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const DEFAULT_PACKAGE_NAME = "@sheepbun/yips";
|
|
8
|
+
const DEFAULT_REGISTRY_BASE_URL = "https://registry.npmjs.org";
|
|
9
|
+
const FALLBACK_VERSION = "0.1.0";
|
|
10
|
+
const PACKAGE_JSON_PATH = (0, node_path_1.resolve)(__dirname, "../..", "package.json");
|
|
11
|
+
function normalizeSemverInput(version) {
|
|
12
|
+
return version.trim().replace(/^v/u, "");
|
|
13
|
+
}
|
|
14
|
+
function parseSemver(version) {
|
|
15
|
+
const normalized = normalizeSemverInput(version);
|
|
16
|
+
const match = normalized.match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/u);
|
|
17
|
+
if (!match) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const major = Number(match[1]);
|
|
21
|
+
const minor = Number(match[2]);
|
|
22
|
+
const patch = Number(match[3]);
|
|
23
|
+
if (!Number.isInteger(major) || !Number.isInteger(minor) || !Number.isInteger(patch)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return [major, minor, patch];
|
|
27
|
+
}
|
|
28
|
+
function compareSemver(left, right) {
|
|
29
|
+
const leftParsed = parseSemver(left);
|
|
30
|
+
const rightParsed = parseSemver(right);
|
|
31
|
+
if (!leftParsed || !rightParsed) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const [leftMajor, leftMinor, leftPatch] = leftParsed;
|
|
35
|
+
const [rightMajor, rightMinor, rightPatch] = rightParsed;
|
|
36
|
+
const components = [
|
|
37
|
+
[leftMajor, rightMajor],
|
|
38
|
+
[leftMinor, rightMinor],
|
|
39
|
+
[leftPatch, rightPatch]
|
|
40
|
+
];
|
|
41
|
+
for (const [leftPart, rightPart] of components) {
|
|
42
|
+
if (leftPart > rightPart) {
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
if (leftPart < rightPart) {
|
|
46
|
+
return -1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
async function getInstalledPackageVersion() {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = await readInstalledPackageMetadata();
|
|
54
|
+
if (typeof parsed.version === "string" && parsed.version.trim().length > 0) {
|
|
55
|
+
return parsed.version.trim();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Fallback below.
|
|
60
|
+
}
|
|
61
|
+
return FALLBACK_VERSION;
|
|
62
|
+
}
|
|
63
|
+
async function getInstalledPackageName() {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = await readInstalledPackageMetadata();
|
|
66
|
+
if (typeof parsed.name === "string" && parsed.name.trim().length > 0) {
|
|
67
|
+
return parsed.name.trim();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Fallback below.
|
|
72
|
+
}
|
|
73
|
+
return DEFAULT_PACKAGE_NAME;
|
|
74
|
+
}
|
|
75
|
+
async function readInstalledPackageMetadata() {
|
|
76
|
+
const raw = await (0, promises_1.readFile)(PACKAGE_JSON_PATH, "utf8");
|
|
77
|
+
return JSON.parse(raw);
|
|
78
|
+
}
|
|
79
|
+
async function checkForUpdates(currentVersion, options = {}) {
|
|
80
|
+
const packageName = options.packageName ?? (await getInstalledPackageName());
|
|
81
|
+
const registryBaseUrl = (options.registryBaseUrl ?? DEFAULT_REGISTRY_BASE_URL).replace(/\/+$/u, "");
|
|
82
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
83
|
+
if (!fetchImpl) {
|
|
84
|
+
return {
|
|
85
|
+
currentVersion,
|
|
86
|
+
latestVersion: null,
|
|
87
|
+
status: "unknown",
|
|
88
|
+
source: "npm-registry",
|
|
89
|
+
error: "Fetch API is unavailable in this runtime."
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const response = await fetchImpl(`${registryBaseUrl}/${encodeURIComponent(packageName)}/latest`);
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
return {
|
|
96
|
+
currentVersion,
|
|
97
|
+
latestVersion: null,
|
|
98
|
+
status: "unknown",
|
|
99
|
+
source: "npm-registry",
|
|
100
|
+
error: `Registry lookup failed (${response.status} ${response.statusText}).`
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const payload = (await response.json());
|
|
104
|
+
if (typeof payload.version !== "string" || payload.version.trim().length === 0) {
|
|
105
|
+
return {
|
|
106
|
+
currentVersion,
|
|
107
|
+
latestVersion: null,
|
|
108
|
+
status: "unknown",
|
|
109
|
+
source: "npm-registry",
|
|
110
|
+
error: "Registry response did not include a valid version."
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const latestVersion = payload.version.trim();
|
|
114
|
+
const comparison = compareSemver(currentVersion, latestVersion);
|
|
115
|
+
return {
|
|
116
|
+
currentVersion,
|
|
117
|
+
latestVersion,
|
|
118
|
+
status: comparison === -1 ? "update-available" : "up-to-date",
|
|
119
|
+
source: "npm-registry"
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
124
|
+
return {
|
|
125
|
+
currentVersion,
|
|
126
|
+
latestVersion: null,
|
|
127
|
+
status: "unknown",
|
|
128
|
+
source: "npm-registry",
|
|
129
|
+
error: message
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Git-derived date-based versioning in the format vYYYY.M.D-SHORTHASH. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.getGitInfo = getGitInfo;
|
|
5
|
+
exports.generateVersion = generateVersion;
|
|
6
|
+
exports.getVersion = getVersion;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const node_util_1 = require("node:util");
|
|
10
|
+
const execFile = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
11
|
+
const EXEC_TIMEOUT_MS = 5000;
|
|
12
|
+
const SHORT_SHA_LENGTH = 7;
|
|
13
|
+
const FALLBACK_VERSION = "1.0.0";
|
|
14
|
+
const REPO_ROOT = (0, node_path_1.resolve)(__dirname, "../..");
|
|
15
|
+
async function getGitInfo() {
|
|
16
|
+
try {
|
|
17
|
+
const [timestampResult, shaResult] = await Promise.all([
|
|
18
|
+
execFile("git", ["-C", REPO_ROOT, "log", "-1", "--format=%ct"], {
|
|
19
|
+
timeout: EXEC_TIMEOUT_MS
|
|
20
|
+
}),
|
|
21
|
+
execFile("git", ["-C", REPO_ROOT, "rev-parse", `--short=${SHORT_SHA_LENGTH}`, "HEAD"], {
|
|
22
|
+
timeout: EXEC_TIMEOUT_MS
|
|
23
|
+
})
|
|
24
|
+
]);
|
|
25
|
+
const timestamp = parseInt(timestampResult.stdout.trim(), 10);
|
|
26
|
+
if (Number.isNaN(timestamp)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const shortSha = shaResult.stdout.trim();
|
|
30
|
+
if (shortSha.length === 0) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return { commitDate: new Date(timestamp * 1000), shortSha };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function generateVersion(commitDate, shortSha) {
|
|
40
|
+
const year = commitDate.getFullYear();
|
|
41
|
+
const month = commitDate.getMonth() + 1;
|
|
42
|
+
const day = commitDate.getDate();
|
|
43
|
+
return `v${year}.${month}.${day}-${shortSha}`;
|
|
44
|
+
}
|
|
45
|
+
async function getVersion() {
|
|
46
|
+
const info = await getGitInfo();
|
|
47
|
+
if (info === null) {
|
|
48
|
+
return FALLBACK_VERSION;
|
|
49
|
+
}
|
|
50
|
+
return generateVersion(info.commitDate, info.shortSha);
|
|
51
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findCodeMdCandidates = findCodeMdCandidates;
|
|
4
|
+
exports.loadCodeContext = loadCodeContext;
|
|
5
|
+
exports.toCodeContextSystemMessage = toCodeContextSystemMessage;
|
|
6
|
+
const promises_1 = require("node:fs/promises");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const CODE_MD_FILENAME = "CODE.md";
|
|
9
|
+
const MAX_CODE_MD_CHARS = 24_000;
|
|
10
|
+
function truncateContent(content, maxChars) {
|
|
11
|
+
if (content.length <= maxChars) {
|
|
12
|
+
return { content, truncated: false };
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
content: `${content.slice(0, maxChars)}\n\n[CODE.md truncated to ${maxChars} characters]`,
|
|
16
|
+
truncated: true
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async function isReadable(path) {
|
|
20
|
+
try {
|
|
21
|
+
await (0, promises_1.access)(path);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function findCodeMdCandidates(startDir) {
|
|
29
|
+
const candidates = [];
|
|
30
|
+
let current = (0, node_path_1.resolve)(startDir);
|
|
31
|
+
for (;;) {
|
|
32
|
+
candidates.push((0, node_path_1.join)(current, CODE_MD_FILENAME));
|
|
33
|
+
const parent = (0, node_path_1.dirname)(current);
|
|
34
|
+
if (parent === current) {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
current = parent;
|
|
38
|
+
}
|
|
39
|
+
return candidates;
|
|
40
|
+
}
|
|
41
|
+
async function loadCodeContext(startDir = process.cwd()) {
|
|
42
|
+
const candidates = findCodeMdCandidates(startDir);
|
|
43
|
+
for (const candidate of candidates) {
|
|
44
|
+
if (!(await isReadable(candidate))) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const raw = await (0, promises_1.readFile)(candidate, "utf8");
|
|
49
|
+
const trimmed = raw.trim();
|
|
50
|
+
if (trimmed.length === 0) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const truncated = truncateContent(trimmed, MAX_CODE_MD_CHARS);
|
|
54
|
+
return {
|
|
55
|
+
path: candidate,
|
|
56
|
+
content: truncated.content,
|
|
57
|
+
truncated: truncated.truncated
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// continue to parent candidate
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
function toCodeContextSystemMessage(context) {
|
|
67
|
+
return [`Project context from CODE.md (${context.path}):`, "", context.content].join("\n");
|
|
68
|
+
}
|
package/dist/colors.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Color palette and gradient utilities for ANSI truecolor output. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.DIM_GRAY = exports.SUCCESS_GREEN = exports.WARNING_YELLOW = exports.ERROR_RED = exports.VT_GREEN = exports.INPUT_PINK = exports.DARK_BLUE = exports.GRADIENT_BLUE = exports.GRADIENT_YELLOW = exports.GRADIENT_PINK = void 0;
|
|
5
|
+
exports.parseHex = parseHex;
|
|
6
|
+
exports.toHex = toHex;
|
|
7
|
+
exports.interpolateColor = interpolateColor;
|
|
8
|
+
exports.stripAnsi = stripAnsi;
|
|
9
|
+
exports.colorChar = colorChar;
|
|
10
|
+
exports.colorText = colorText;
|
|
11
|
+
exports.bgColorText = bgColorText;
|
|
12
|
+
exports.horizontalGradient = horizontalGradient;
|
|
13
|
+
exports.horizontalGradientAtOffset = horizontalGradientAtOffset;
|
|
14
|
+
exports.horizontalGradientBackground = horizontalGradientBackground;
|
|
15
|
+
exports.diagonalGradient = diagonalGradient;
|
|
16
|
+
exports.rowMajorGradient = rowMajorGradient;
|
|
17
|
+
// --- Palette constants ---
|
|
18
|
+
exports.GRADIENT_PINK = { r: 0xff, g: 0x14, b: 0x93 };
|
|
19
|
+
exports.GRADIENT_YELLOW = { r: 0xff, g: 0xe1, b: 0x35 };
|
|
20
|
+
exports.GRADIENT_BLUE = { r: 0x89, g: 0xcf, b: 0xf0 };
|
|
21
|
+
exports.DARK_BLUE = { r: 0x00, g: 0x66, b: 0xcc };
|
|
22
|
+
exports.INPUT_PINK = { r: 0xff, g: 0xcc, b: 0xff };
|
|
23
|
+
exports.VT_GREEN = { r: 0x00, g: 0xff, b: 0x00 };
|
|
24
|
+
exports.ERROR_RED = { r: 0xff, g: 0x44, b: 0x44 };
|
|
25
|
+
exports.WARNING_YELLOW = { r: 0xff, g: 0xcc, b: 0x00 };
|
|
26
|
+
exports.SUCCESS_GREEN = { r: 0x44, g: 0xff, b: 0x44 };
|
|
27
|
+
exports.DIM_GRAY = { r: 0x88, g: 0x88, b: 0x88 };
|
|
28
|
+
const ANSI_RESET_FOREGROUND = "\u001b[39m";
|
|
29
|
+
const ANSI_RESET_ALL = "\u001b[0m";
|
|
30
|
+
// --- Color utilities ---
|
|
31
|
+
function parseHex(hex) {
|
|
32
|
+
const cleaned = hex.replace(/^#/, "");
|
|
33
|
+
return {
|
|
34
|
+
r: parseInt(cleaned.slice(0, 2), 16),
|
|
35
|
+
g: parseInt(cleaned.slice(2, 4), 16),
|
|
36
|
+
b: parseInt(cleaned.slice(4, 6), 16)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function toHex(color) {
|
|
40
|
+
const r = Math.round(Math.max(0, Math.min(255, color.r)));
|
|
41
|
+
const g = Math.round(Math.max(0, Math.min(255, color.g)));
|
|
42
|
+
const b = Math.round(Math.max(0, Math.min(255, color.b)));
|
|
43
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
44
|
+
}
|
|
45
|
+
function interpolateColor(start, end, t) {
|
|
46
|
+
const clamped = Math.max(0, Math.min(1, t));
|
|
47
|
+
return {
|
|
48
|
+
r: start.r + (end.r - start.r) * clamped,
|
|
49
|
+
g: start.g + (end.g - start.g) * clamped,
|
|
50
|
+
b: start.b + (end.b - start.b) * clamped
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function toAnsiColor(color) {
|
|
54
|
+
const r = Math.round(Math.max(0, Math.min(255, color.r)));
|
|
55
|
+
const g = Math.round(Math.max(0, Math.min(255, color.g)));
|
|
56
|
+
const b = Math.round(Math.max(0, Math.min(255, color.b)));
|
|
57
|
+
return `\u001b[38;2;${r};${g};${b}m`;
|
|
58
|
+
}
|
|
59
|
+
function toAnsiBackground(color) {
|
|
60
|
+
const r = Math.round(Math.max(0, Math.min(255, color.r)));
|
|
61
|
+
const g = Math.round(Math.max(0, Math.min(255, color.g)));
|
|
62
|
+
const b = Math.round(Math.max(0, Math.min(255, color.b)));
|
|
63
|
+
return `\u001b[48;2;${r};${g};${b}m`;
|
|
64
|
+
}
|
|
65
|
+
function stripAnsi(text) {
|
|
66
|
+
let output = "";
|
|
67
|
+
for (let i = 0; i < text.length; i++) {
|
|
68
|
+
if (text[i] === "\u001b" && text[i + 1] === "[") {
|
|
69
|
+
i += 2;
|
|
70
|
+
while (i < text.length && text[i] !== "m") {
|
|
71
|
+
i += 1;
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
output += text[i] ?? "";
|
|
76
|
+
}
|
|
77
|
+
return output;
|
|
78
|
+
}
|
|
79
|
+
/** Wrap a single character with ANSI truecolor sequence. */
|
|
80
|
+
function colorChar(char, color) {
|
|
81
|
+
return `${toAnsiColor(color)}${char}`;
|
|
82
|
+
}
|
|
83
|
+
/** Apply a solid color to an entire string using ANSI truecolor. */
|
|
84
|
+
function colorText(text, color) {
|
|
85
|
+
if (text.length === 0)
|
|
86
|
+
return "";
|
|
87
|
+
return `${toAnsiColor(color)}${text}${ANSI_RESET_FOREGROUND}`;
|
|
88
|
+
}
|
|
89
|
+
/** Apply background (+ optional foreground) color to a string. */
|
|
90
|
+
function bgColorText(text, bgColor, fgColor) {
|
|
91
|
+
if (text.length === 0)
|
|
92
|
+
return "";
|
|
93
|
+
const fg = fgColor ? toAnsiColor(fgColor) : "";
|
|
94
|
+
return `${toAnsiBackground(bgColor)}${fg}${text}${ANSI_RESET_ALL}`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Apply a horizontal gradient across a string.
|
|
98
|
+
* Each visible character gets its own color interpolated between start and end.
|
|
99
|
+
*/
|
|
100
|
+
function horizontalGradient(text, startColor, endColor) {
|
|
101
|
+
const chars = Array.from(text);
|
|
102
|
+
if (chars.length === 0)
|
|
103
|
+
return "";
|
|
104
|
+
if (chars.length === 1) {
|
|
105
|
+
return `${colorChar(chars[0] ?? "", startColor)}${ANSI_RESET_FOREGROUND}`;
|
|
106
|
+
}
|
|
107
|
+
const gradientChars = [];
|
|
108
|
+
for (let i = 0; i < chars.length; i++) {
|
|
109
|
+
const t = i / (chars.length - 1);
|
|
110
|
+
const color = interpolateColor(startColor, endColor, t);
|
|
111
|
+
gradientChars.push(colorChar(chars[i] ?? "", color));
|
|
112
|
+
}
|
|
113
|
+
gradientChars.push(ANSI_RESET_FOREGROUND);
|
|
114
|
+
return gradientChars.join("");
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Apply a horizontal gradient to a string using an absolute offset in a larger line.
|
|
118
|
+
* Useful when rendering segmented strings that should share one continuous gradient.
|
|
119
|
+
*/
|
|
120
|
+
function horizontalGradientAtOffset(text, startColor, endColor, offset, totalWidth) {
|
|
121
|
+
const chars = Array.from(text);
|
|
122
|
+
if (chars.length === 0)
|
|
123
|
+
return "";
|
|
124
|
+
const safeTotalWidth = Math.max(1, totalWidth);
|
|
125
|
+
const denominator = Math.max(1, safeTotalWidth - 1);
|
|
126
|
+
const baseOffset = Math.max(0, offset);
|
|
127
|
+
const gradientChars = [];
|
|
128
|
+
for (let i = 0; i < chars.length; i++) {
|
|
129
|
+
const t = Math.max(0, Math.min(1, (baseOffset + i) / denominator));
|
|
130
|
+
const color = interpolateColor(startColor, endColor, t);
|
|
131
|
+
gradientChars.push(colorChar(chars[i] ?? "", color));
|
|
132
|
+
}
|
|
133
|
+
gradientChars.push(ANSI_RESET_FOREGROUND);
|
|
134
|
+
return gradientChars.join("");
|
|
135
|
+
}
|
|
136
|
+
/** Apply a left-to-right background gradient across a string. */
|
|
137
|
+
function horizontalGradientBackground(text, startColor, endColor, fgColor) {
|
|
138
|
+
const chars = Array.from(text);
|
|
139
|
+
if (chars.length === 0)
|
|
140
|
+
return "";
|
|
141
|
+
const fg = fgColor ? toAnsiColor(fgColor) : "";
|
|
142
|
+
if (chars.length === 1) {
|
|
143
|
+
return `${toAnsiBackground(startColor)}${fg}${chars[0] ?? ""}${ANSI_RESET_ALL}`;
|
|
144
|
+
}
|
|
145
|
+
const gradientChars = [];
|
|
146
|
+
for (let i = 0; i < chars.length; i++) {
|
|
147
|
+
const t = i / (chars.length - 1);
|
|
148
|
+
const color = interpolateColor(startColor, endColor, t);
|
|
149
|
+
gradientChars.push(`${toAnsiBackground(color)}${fg}${chars[i] ?? ""}`);
|
|
150
|
+
}
|
|
151
|
+
gradientChars.push(ANSI_RESET_ALL);
|
|
152
|
+
return gradientChars.join("");
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Apply a diagonal gradient across multiple lines.
|
|
156
|
+
* Each character's color is based on (row + col) / (totalRows + totalCols).
|
|
157
|
+
*/
|
|
158
|
+
function diagonalGradient(lines, startColor, endColor) {
|
|
159
|
+
if (lines.length === 0)
|
|
160
|
+
return [];
|
|
161
|
+
const splitLines = lines.map((line) => Array.from(line));
|
|
162
|
+
const maxLen = Math.max(...splitLines.map((lineChars) => lineChars.length));
|
|
163
|
+
if (maxLen === 0)
|
|
164
|
+
return lines.map(() => "");
|
|
165
|
+
const totalSteps = lines.length - 1 + maxLen - 1;
|
|
166
|
+
return splitLines.map((lineChars, row) => {
|
|
167
|
+
if (lineChars.length === 0)
|
|
168
|
+
return "";
|
|
169
|
+
const gradientChars = [];
|
|
170
|
+
for (let col = 0; col < lineChars.length; col++) {
|
|
171
|
+
const t = totalSteps > 0 ? (row + col) / totalSteps : 0;
|
|
172
|
+
const color = interpolateColor(startColor, endColor, t);
|
|
173
|
+
gradientChars.push(colorChar(lineChars[col] ?? "", color));
|
|
174
|
+
}
|
|
175
|
+
gradientChars.push(ANSI_RESET_FOREGROUND);
|
|
176
|
+
return gradientChars.join("");
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Apply a multiline gradient by scanning cells row-by-row.
|
|
181
|
+
* Progress advances left-to-right per row, then continues on the next row.
|
|
182
|
+
*/
|
|
183
|
+
function rowMajorGradient(lines, startColor, endColor) {
|
|
184
|
+
if (lines.length === 0)
|
|
185
|
+
return [];
|
|
186
|
+
const splitLines = lines.map((line) => Array.from(line));
|
|
187
|
+
const maxLen = Math.max(...splitLines.map((lineChars) => lineChars.length));
|
|
188
|
+
if (maxLen === 0)
|
|
189
|
+
return lines.map(() => "");
|
|
190
|
+
const totalCells = lines.length * maxLen;
|
|
191
|
+
return splitLines.map((lineChars, row) => {
|
|
192
|
+
if (lineChars.length === 0)
|
|
193
|
+
return "";
|
|
194
|
+
const gradientChars = [];
|
|
195
|
+
for (let col = 0; col < lineChars.length; col++) {
|
|
196
|
+
const cellIndex = row * maxLen + col;
|
|
197
|
+
const t = totalCells > 1 ? cellIndex / (totalCells - 1) : 0;
|
|
198
|
+
const color = interpolateColor(startColor, endColor, t);
|
|
199
|
+
gradientChars.push(colorChar(lineChars[col] ?? "", color));
|
|
200
|
+
}
|
|
201
|
+
gradientChars.push(ANSI_RESET_FOREGROUND);
|
|
202
|
+
return gradientChars.join("");
|
|
203
|
+
});
|
|
204
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadCommandCatalog = loadCommandCatalog;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const GENERIC_DESCRIPTIONS = new Set(["Tool command", "Markdown skill", "Command"]);
|
|
7
|
+
const RESTORED_COMMAND_DEFAULTS = [
|
|
8
|
+
{ name: "backend", description: "Switch AI backends (llamacpp, claude)", kind: "builtin" },
|
|
9
|
+
{ name: "clear", description: "Clear context and start a new session", kind: "builtin" },
|
|
10
|
+
{ name: "dl", description: "Alias for /download", kind: "builtin" },
|
|
11
|
+
{ name: "download", description: "Open the interactive model downloader", kind: "builtin" },
|
|
12
|
+
{ name: "exit", description: "Exit Yips", kind: "builtin" },
|
|
13
|
+
{ name: "fetch", description: "Retrieve and display content from a URL", kind: "tool" },
|
|
14
|
+
{ name: "grab", description: "Read a file's content into context", kind: "tool" },
|
|
15
|
+
{ name: "help", description: "Show available commands and tips", kind: "skill" },
|
|
16
|
+
{ name: "memorize", description: "Save a fact to long-term memory", kind: "tool" },
|
|
17
|
+
{ name: "model", description: "Open the Model Manager or switch to a specific model", kind: "builtin" },
|
|
18
|
+
{ name: "new", description: "Start a new session", kind: "builtin" },
|
|
19
|
+
{ name: "nick", description: "Set a custom nickname for a model", kind: "builtin" },
|
|
20
|
+
{ name: "quit", description: "Exit Yips", kind: "builtin" },
|
|
21
|
+
{ name: "search", description: "Search the web (DuckDuckGo)", kind: "tool" },
|
|
22
|
+
{ name: "sessions", description: "Interactively select and load a session", kind: "builtin" },
|
|
23
|
+
{ name: "stream", description: "Toggle streaming responses", kind: "builtin" },
|
|
24
|
+
{ name: "tokens", description: "Show or set token counter mode and max", kind: "builtin" },
|
|
25
|
+
{ name: "verbose", description: "Toggle verbose output", kind: "builtin" },
|
|
26
|
+
{ name: "vt", description: "Toggle the Virtual Terminal", kind: "tool" }
|
|
27
|
+
];
|
|
28
|
+
function normalizeCommandName(name) {
|
|
29
|
+
return name.trim().replace(/^\/+/, "").toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
function isGenericDescription(description) {
|
|
32
|
+
const trimmed = description.trim();
|
|
33
|
+
if (trimmed.length === 0) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return GENERIC_DESCRIPTIONS.has(trimmed);
|
|
37
|
+
}
|
|
38
|
+
function normalizeDescription(description) {
|
|
39
|
+
const trimmed = description.trim();
|
|
40
|
+
return trimmed.length > 0 ? trimmed : "Command";
|
|
41
|
+
}
|
|
42
|
+
function sortedDirectoryEntries(path) {
|
|
43
|
+
return (0, node_fs_1.readdirSync)(path, { withFileTypes: true })
|
|
44
|
+
.filter((entry) => entry.isDirectory())
|
|
45
|
+
.map((entry) => entry.name)
|
|
46
|
+
.sort((left, right) => left.localeCompare(right));
|
|
47
|
+
}
|
|
48
|
+
function fileNames(path) {
|
|
49
|
+
return (0, node_fs_1.readdirSync)(path, { withFileTypes: true })
|
|
50
|
+
.filter((entry) => entry.isFile())
|
|
51
|
+
.map((entry) => entry.name)
|
|
52
|
+
.sort((left, right) => left.localeCompare(right));
|
|
53
|
+
}
|
|
54
|
+
function fileBasename(filename) {
|
|
55
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
56
|
+
return dotIndex >= 0 ? filename.slice(0, dotIndex) : filename;
|
|
57
|
+
}
|
|
58
|
+
function pickCommandFile(commandDir, commandDirName, extension) {
|
|
59
|
+
if (!(0, node_fs_1.existsSync)(commandDir)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const names = fileNames(commandDir);
|
|
63
|
+
const extensionLower = extension.toLowerCase();
|
|
64
|
+
const targetBase = commandDirName.toLowerCase();
|
|
65
|
+
const exact = names.find((name) => {
|
|
66
|
+
if (!name.toLowerCase().endsWith(extensionLower)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return fileBasename(name).toLowerCase() === targetBase;
|
|
70
|
+
});
|
|
71
|
+
if (exact) {
|
|
72
|
+
return (0, node_path_1.join)(commandDir, exact);
|
|
73
|
+
}
|
|
74
|
+
const fallback = names.find((name) => name.toLowerCase().endsWith(extensionLower));
|
|
75
|
+
return fallback ? (0, node_path_1.join)(commandDir, fallback) : null;
|
|
76
|
+
}
|
|
77
|
+
function toNonEmptyTrimmedLines(text) {
|
|
78
|
+
return text
|
|
79
|
+
.split(/\r?\n/u)
|
|
80
|
+
.map((line) => line.trim())
|
|
81
|
+
.filter((line) => line.length > 0);
|
|
82
|
+
}
|
|
83
|
+
function descriptionFromLines(lines) {
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
if (/^description\s*:/iu.test(line)) {
|
|
86
|
+
const extracted = line.replace(/^description\s*:\s*/iu, "").trim();
|
|
87
|
+
if (extracted.length > 0) {
|
|
88
|
+
return extracted;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const first = lines[0];
|
|
93
|
+
if (!first) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const split = first.split(" - ");
|
|
97
|
+
if (split.length > 1) {
|
|
98
|
+
const remainder = split.slice(1).join(" - ").trim();
|
|
99
|
+
if (remainder.length > 0) {
|
|
100
|
+
return remainder;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return first;
|
|
104
|
+
}
|
|
105
|
+
function extractPythonDescription(contents) {
|
|
106
|
+
const docstringMatch = contents.match(/^\s*(?:#.*\r?\n|\s)*(?:"""([\s\S]*?)"""|'''([\s\S]*?)''')/u);
|
|
107
|
+
const docstring = docstringMatch?.[1] ?? docstringMatch?.[2];
|
|
108
|
+
if (!docstring) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return descriptionFromLines(toNonEmptyTrimmedLines(docstring));
|
|
112
|
+
}
|
|
113
|
+
function extractMarkdownDescription(contents) {
|
|
114
|
+
const lines = toNonEmptyTrimmedLines(contents);
|
|
115
|
+
if (lines.length === 0) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const first = lines[0];
|
|
119
|
+
if (!first) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
if (!first.startsWith("#")) {
|
|
123
|
+
return first;
|
|
124
|
+
}
|
|
125
|
+
for (const line of lines.slice(1)) {
|
|
126
|
+
if (line.startsWith("#") || line.startsWith("!") || line.startsWith("```")) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
return line;
|
|
130
|
+
}
|
|
131
|
+
const heading = first.replace(/^#+\s*/u, "").trim();
|
|
132
|
+
return heading.length > 0 ? heading : null;
|
|
133
|
+
}
|
|
134
|
+
function readTextSafely(path) {
|
|
135
|
+
try {
|
|
136
|
+
return (0, node_fs_1.readFileSync)(path, "utf8");
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function discoverCommands(parentDir, parentKind) {
|
|
143
|
+
if (!(0, node_fs_1.existsSync)(parentDir)) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
const discovered = [];
|
|
147
|
+
for (const commandDirName of sortedDirectoryEntries(parentDir)) {
|
|
148
|
+
const commandName = normalizeCommandName(commandDirName);
|
|
149
|
+
if (commandName.length === 0) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const commandDir = (0, node_path_1.join)(parentDir, commandDirName);
|
|
153
|
+
const pythonPath = pickCommandFile(commandDir, commandDirName, ".py");
|
|
154
|
+
const markdownPath = pickCommandFile(commandDir, commandDirName, ".md");
|
|
155
|
+
let kind = parentKind;
|
|
156
|
+
let description = null;
|
|
157
|
+
let priority = 0;
|
|
158
|
+
if (pythonPath) {
|
|
159
|
+
const contents = readTextSafely(pythonPath);
|
|
160
|
+
if (contents) {
|
|
161
|
+
description = extractPythonDescription(contents);
|
|
162
|
+
}
|
|
163
|
+
kind = "tool";
|
|
164
|
+
priority = description ? 2 : 0;
|
|
165
|
+
}
|
|
166
|
+
else if (markdownPath) {
|
|
167
|
+
const contents = readTextSafely(markdownPath);
|
|
168
|
+
if (contents) {
|
|
169
|
+
description = extractMarkdownDescription(contents);
|
|
170
|
+
}
|
|
171
|
+
kind = "skill";
|
|
172
|
+
priority = description ? 2 : 0;
|
|
173
|
+
}
|
|
174
|
+
discovered.push({
|
|
175
|
+
name: commandName,
|
|
176
|
+
description: normalizeDescription(description ?? "Command"),
|
|
177
|
+
kind,
|
|
178
|
+
implemented: false,
|
|
179
|
+
priority
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return discovered;
|
|
183
|
+
}
|
|
184
|
+
function upsertCatalogEntry(catalog, candidate) {
|
|
185
|
+
const normalizedName = normalizeCommandName(candidate.name);
|
|
186
|
+
if (normalizedName.length === 0) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const normalizedCandidate = {
|
|
190
|
+
...candidate,
|
|
191
|
+
name: normalizedName,
|
|
192
|
+
description: normalizeDescription(candidate.description)
|
|
193
|
+
};
|
|
194
|
+
const existing = catalog.get(normalizedName);
|
|
195
|
+
if (!existing) {
|
|
196
|
+
catalog.set(normalizedName, normalizedCandidate);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const candidateGeneric = isGenericDescription(normalizedCandidate.description);
|
|
200
|
+
const existingGeneric = isGenericDescription(existing.description);
|
|
201
|
+
const shouldReplace = normalizedCandidate.priority > existing.priority ||
|
|
202
|
+
(normalizedCandidate.priority === existing.priority && !candidateGeneric && existingGeneric);
|
|
203
|
+
if (shouldReplace) {
|
|
204
|
+
catalog.set(normalizedName, {
|
|
205
|
+
...normalizedCandidate,
|
|
206
|
+
implemented: existing.implemented || normalizedCandidate.implemented
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
catalog.set(normalizedName, {
|
|
211
|
+
...existing,
|
|
212
|
+
implemented: existing.implemented || normalizedCandidate.implemented
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function loadCommandCatalog(options = {}) {
|
|
216
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
217
|
+
const catalog = new Map();
|
|
218
|
+
for (const descriptor of RESTORED_COMMAND_DEFAULTS) {
|
|
219
|
+
upsertCatalogEntry(catalog, {
|
|
220
|
+
...descriptor,
|
|
221
|
+
name: normalizeCommandName(descriptor.name),
|
|
222
|
+
implemented: false,
|
|
223
|
+
priority: 1
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const toolsDir = (0, node_path_1.join)(projectRoot, "commands", "tools");
|
|
227
|
+
const skillsDir = (0, node_path_1.join)(projectRoot, "commands", "skills");
|
|
228
|
+
for (const descriptor of discoverCommands(toolsDir, "tool")) {
|
|
229
|
+
upsertCatalogEntry(catalog, descriptor);
|
|
230
|
+
}
|
|
231
|
+
for (const descriptor of discoverCommands(skillsDir, "skill")) {
|
|
232
|
+
upsertCatalogEntry(catalog, descriptor);
|
|
233
|
+
}
|
|
234
|
+
return [...catalog.values()]
|
|
235
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
236
|
+
.map((descriptor) => ({
|
|
237
|
+
name: descriptor.name,
|
|
238
|
+
description: descriptor.description,
|
|
239
|
+
kind: descriptor.kind,
|
|
240
|
+
implemented: descriptor.implemented
|
|
241
|
+
}));
|
|
242
|
+
}
|