@apolloyh/apollo-agent 0.1.4 → 0.1.5
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/brand.d.ts +1 -1
- package/dist/brand.js +1 -1
- package/dist/cli-input.d.ts +16 -0
- package/dist/cli-input.d.ts.map +1 -0
- package/dist/cli-input.js +68 -0
- package/dist/index.js +39 -37
- package/dist/skills/skills.d.ts.map +1 -1
- package/dist/skills/skills.js +8 -4
- package/dist/tools/builtin.js +2 -2
- package/package.json +1 -1
package/dist/brand.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Product identity — single source of truth for display strings. */
|
|
2
2
|
export declare const PRODUCT: {
|
|
3
3
|
readonly name: "Apollo";
|
|
4
|
-
readonly version: "0.1.
|
|
4
|
+
readonly version: "0.1.5";
|
|
5
5
|
readonly tagline: "Your personal agent — answer first, act when needed.";
|
|
6
6
|
readonly packageName: "@apolloyh/apollo-agent";
|
|
7
7
|
readonly userAgent: "ApolloAgent/0.1";
|
package/dist/brand.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Product identity — single source of truth for display strings. */
|
|
2
2
|
export const PRODUCT = {
|
|
3
3
|
name: "Apollo",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.5",
|
|
5
5
|
tagline: "Your personal agent — answer first, act when needed.",
|
|
6
6
|
packageName: "@apolloyh/apollo-agent",
|
|
7
7
|
userAgent: "ApolloAgent/0.1",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type PastedTextBlock = {
|
|
2
|
+
id: number;
|
|
3
|
+
label: string;
|
|
4
|
+
marker: string;
|
|
5
|
+
text: string;
|
|
6
|
+
};
|
|
7
|
+
export type InputView = {
|
|
8
|
+
cursorLine: number;
|
|
9
|
+
cursorWidth: number;
|
|
10
|
+
lines: string[];
|
|
11
|
+
};
|
|
12
|
+
export declare function createPastedTextBlock(text: string, id: number): PastedTextBlock | undefined;
|
|
13
|
+
export declare function resolvePastedText(value: string, blocks: ReadonlyMap<string, PastedTextBlock>): string;
|
|
14
|
+
export declare function pastedTextPreview(value: string, blocks: ReadonlyMap<string, PastedTextBlock>): string;
|
|
15
|
+
export declare function renderInputView(value: string, cursor: number, width: number, blocks: ReadonlyMap<string, PastedTextBlock>): InputView;
|
|
16
|
+
//# sourceMappingURL=cli-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-input.d.ts","sourceRoot":"","sources":["../src/cli-input.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAMF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAQ3F;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,MAAM,CAErG;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,MAAM,CAErG;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,GAC3C,SAAS,CAyCX"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const MIN_FOLDED_PASTE_LINES = 6;
|
|
2
|
+
const MIN_FOLDED_PASTE_CHARS = 1000;
|
|
3
|
+
const FIRST_MARKER = 0xf0000;
|
|
4
|
+
export function createPastedTextBlock(text, id) {
|
|
5
|
+
const lineCount = text.split("\n").length;
|
|
6
|
+
if (lineCount < MIN_FOLDED_PASTE_LINES && Array.from(text).length < MIN_FOLDED_PASTE_CHARS) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const marker = String.fromCodePoint(FIRST_MARKER + id);
|
|
10
|
+
const size = lineCount > 1 ? `+${lineCount} lines` : `+${Array.from(text).length} chars`;
|
|
11
|
+
return { id, label: `[Pasted text #${id} ${size}]`, marker, text };
|
|
12
|
+
}
|
|
13
|
+
export function resolvePastedText(value, blocks) {
|
|
14
|
+
return Array.from(value, (character) => blocks.get(character)?.text ?? character).join("");
|
|
15
|
+
}
|
|
16
|
+
export function pastedTextPreview(value, blocks) {
|
|
17
|
+
return Array.from(value, (character) => blocks.get(character)?.label ?? character).join("");
|
|
18
|
+
}
|
|
19
|
+
export function renderInputView(value, cursor, width, blocks) {
|
|
20
|
+
const characters = Array.from(value);
|
|
21
|
+
const positions = [];
|
|
22
|
+
const lines = [""];
|
|
23
|
+
let line = 0;
|
|
24
|
+
let column = 0;
|
|
25
|
+
const newLine = () => {
|
|
26
|
+
lines.push("");
|
|
27
|
+
line += 1;
|
|
28
|
+
column = 0;
|
|
29
|
+
};
|
|
30
|
+
const appendCharacter = (character) => {
|
|
31
|
+
if (character === "\n") {
|
|
32
|
+
newLine();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const rendered = character === "\t" ? " " : character;
|
|
36
|
+
for (const outputCharacter of rendered) {
|
|
37
|
+
const characterWidth = displayWidth(outputCharacter);
|
|
38
|
+
if (column > 0 && column + characterWidth > width)
|
|
39
|
+
newLine();
|
|
40
|
+
lines[line] += outputCharacter;
|
|
41
|
+
column += characterWidth;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
characters.forEach((character, index) => {
|
|
45
|
+
const block = blocks.get(character);
|
|
46
|
+
if (block && column > 0 && column + displayWidth(block.label) > width)
|
|
47
|
+
newLine();
|
|
48
|
+
positions[index] = { line, width: column };
|
|
49
|
+
for (const outputCharacter of block?.label ?? character)
|
|
50
|
+
appendCharacter(outputCharacter);
|
|
51
|
+
});
|
|
52
|
+
positions[characters.length] = { line, width: column };
|
|
53
|
+
const cursorPosition = positions[Math.max(0, Math.min(cursor, characters.length))] ?? { line: 0, width: 0 };
|
|
54
|
+
return {
|
|
55
|
+
cursorLine: cursorPosition.line,
|
|
56
|
+
cursorWidth: cursorPosition.width,
|
|
57
|
+
lines,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function displayWidth(text) {
|
|
61
|
+
let length = 0;
|
|
62
|
+
for (const character of text)
|
|
63
|
+
length += isWideCharacter(character) ? 2 : 1;
|
|
64
|
+
return length;
|
|
65
|
+
}
|
|
66
|
+
function isWideCharacter(character) {
|
|
67
|
+
return /[\u1100-\u115F\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE6F\uFF00-\uFF60\uFFE0-\uFFE6]/u.test(character);
|
|
68
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { stdin, stdout } from "node:process";
|
|
|
3
3
|
import readline from "node:readline/promises";
|
|
4
4
|
import { ASCII_LOGO, PRODUCT } from "./brand.js";
|
|
5
5
|
import { parseArgs } from "./cli-args.js";
|
|
6
|
+
import { createPastedTextBlock, pastedTextPreview, renderInputView, resolvePastedText, } from "./cli-input.js";
|
|
6
7
|
import { ensureUserEnvFile, loadAgentConfig, loadEnvFile, loadLlmConfig, MISSING_AUTH_TOKEN_MESSAGE, } from "./config.js";
|
|
7
8
|
import { pickSpinnerVerb } from "./constants/spinner-verbs.js";
|
|
8
9
|
import { QueryEngine } from "./runtime/query-engine.js";
|
|
@@ -23,6 +24,7 @@ let activeVerb = "Thinking";
|
|
|
23
24
|
let assistantResponseStarted = false;
|
|
24
25
|
/** Prevent double green/red outcome lines in one user turn */
|
|
25
26
|
let outcomePrintedThisTurn = false;
|
|
27
|
+
let pastedTextSequence = 0;
|
|
26
28
|
const slashCommands = [
|
|
27
29
|
{ command: "/help", usage: "/help", description: "Show slash commands" },
|
|
28
30
|
{ command: "/skill", usage: "/skill", description: "List installed skills" },
|
|
@@ -1032,6 +1034,7 @@ async function readCliLine(options) {
|
|
|
1032
1034
|
let previousRawMode = false;
|
|
1033
1035
|
let renderedCursorLine = 0;
|
|
1034
1036
|
let pasteBuffer;
|
|
1037
|
+
const pastedTextBlocks = new Map();
|
|
1035
1038
|
const pasteStart = "\x1b[200~";
|
|
1036
1039
|
const pasteEnd = "\x1b[201~";
|
|
1037
1040
|
const complete = (value) => {
|
|
@@ -1055,8 +1058,9 @@ async function readCliLine(options) {
|
|
|
1055
1058
|
if (liveStatusLine)
|
|
1056
1059
|
lines.push(liveStatusLine);
|
|
1057
1060
|
lines.push(renderInputBorder("top", options.color));
|
|
1058
|
-
const view =
|
|
1059
|
-
lines.push(`${renderInputPrompt(options.color)}${view.
|
|
1061
|
+
const view = renderInputView(buffer, cursor, inputContentWidth(), pastedTextBlocks);
|
|
1062
|
+
lines.push(`${renderInputPrompt(options.color)}${view.lines[0] ?? ""}`);
|
|
1063
|
+
lines.push(...view.lines.slice(1).map((line) => ` ${line}`));
|
|
1060
1064
|
if (paletteOpen) {
|
|
1061
1065
|
const filtered = filterSlashCommands(buffer);
|
|
1062
1066
|
lines.push(...renderSlashPaletteLines(selected, options.color, filtered, buffer));
|
|
@@ -1088,21 +1092,23 @@ async function readCliLine(options) {
|
|
|
1088
1092
|
const lines = linesForState();
|
|
1089
1093
|
stdout.write(lines.join("\n"));
|
|
1090
1094
|
renderedLines = lines.length;
|
|
1091
|
-
|
|
1095
|
+
const view = renderInputView(buffer, cursor, inputContentWidth(), pastedTextBlocks);
|
|
1096
|
+
renderedCursorLine = (renderLiveStatusLine(options.color) ? 2 : 1) + view.cursorLine;
|
|
1092
1097
|
const rowsBelowInput = lines.length - 1 - renderedCursorLine;
|
|
1093
|
-
const view = inputView(buffer, cursor, Math.max(20, (stdout.columns ?? 100) - 4));
|
|
1094
1098
|
stdout.write(`\x1b[${rowsBelowInput}A\r\x1b[${3 + view.cursorWidth}G`);
|
|
1095
1099
|
};
|
|
1096
1100
|
const finish = (value) => {
|
|
1097
1101
|
clearRendered();
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1102
|
+
const resolvedValue = resolvePastedText(value, pastedTextBlocks);
|
|
1103
|
+
if (resolvedValue) {
|
|
1104
|
+
if (options.history.at(-1) !== resolvedValue)
|
|
1105
|
+
options.history.push(resolvedValue);
|
|
1101
1106
|
if (options.history.length > 100)
|
|
1102
1107
|
options.history.splice(0, options.history.length - 100);
|
|
1103
|
-
|
|
1108
|
+
const preview = pastedTextPreview(value, pastedTextBlocks).replace(/\n/g, " ↵ ").replace(/\t/g, " ");
|
|
1109
|
+
stdout.write(`${renderInputBorder("top", options.color)}\n${renderInputPrompt(options.color)}${preview === value ? submittedInputLabel(resolvedValue) : preview}\n${renderInputBorder("bottom", options.color)}\n`);
|
|
1104
1110
|
}
|
|
1105
|
-
complete(
|
|
1111
|
+
complete(resolvedValue);
|
|
1106
1112
|
};
|
|
1107
1113
|
const cancelForModal = () => {
|
|
1108
1114
|
clearRendered();
|
|
@@ -1240,11 +1246,11 @@ async function readCliLine(options) {
|
|
|
1240
1246
|
if (historyIndex === options.history.length)
|
|
1241
1247
|
historyDraft = buffer;
|
|
1242
1248
|
historyIndex -= 1;
|
|
1243
|
-
|
|
1249
|
+
setBuffer(options.history[historyIndex] ?? "");
|
|
1244
1250
|
}
|
|
1245
1251
|
else if (chunk === "\x1b[B" && historyIndex < options.history.length) {
|
|
1246
1252
|
historyIndex += 1;
|
|
1247
|
-
|
|
1253
|
+
setBuffer(historyIndex === options.history.length ? historyDraft : options.history[historyIndex] ?? "");
|
|
1248
1254
|
}
|
|
1249
1255
|
cursor = Array.from(buffer).length;
|
|
1250
1256
|
closePalette();
|
|
@@ -1271,13 +1277,31 @@ async function readCliLine(options) {
|
|
|
1271
1277
|
render();
|
|
1272
1278
|
};
|
|
1273
1279
|
const insertText = (text) => {
|
|
1274
|
-
const safeText = stripTerminalEscapes(text)
|
|
1280
|
+
const safeText = stripTerminalEscapes(text)
|
|
1281
|
+
.replace(/\r\n?/g, "\n")
|
|
1282
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
1275
1283
|
const characters = Array.from(buffer);
|
|
1276
|
-
const
|
|
1284
|
+
const block = createPastedTextBlock(safeText, pastedTextSequence + 1);
|
|
1285
|
+
if (block) {
|
|
1286
|
+
pastedTextSequence += 1;
|
|
1287
|
+
pastedTextBlocks.set(block.marker, block);
|
|
1288
|
+
}
|
|
1289
|
+
const inserted = block ? [block.marker] : Array.from(safeText);
|
|
1277
1290
|
characters.splice(cursor, 0, ...inserted);
|
|
1278
1291
|
buffer = characters.join("");
|
|
1279
1292
|
cursor += inserted.length;
|
|
1280
1293
|
};
|
|
1294
|
+
function setBuffer(value) {
|
|
1295
|
+
const block = createPastedTextBlock(value, pastedTextSequence + 1);
|
|
1296
|
+
if (block) {
|
|
1297
|
+
pastedTextSequence += 1;
|
|
1298
|
+
pastedTextBlocks.set(block.marker, block);
|
|
1299
|
+
buffer = block.marker;
|
|
1300
|
+
}
|
|
1301
|
+
else {
|
|
1302
|
+
buffer = value;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1281
1305
|
const handleData = (chunk) => {
|
|
1282
1306
|
let rest = chunk;
|
|
1283
1307
|
while (rest) {
|
|
@@ -1644,30 +1668,8 @@ function truncateDisplay(text, width) {
|
|
|
1644
1668
|
}
|
|
1645
1669
|
return `${result}…`;
|
|
1646
1670
|
}
|
|
1647
|
-
function
|
|
1648
|
-
|
|
1649
|
-
const normalize = (text) => text.replace(/\n/g, " ↵ ").replace(/\t/g, " ");
|
|
1650
|
-
const before = normalize(characters.slice(0, cursor).join(""));
|
|
1651
|
-
const after = normalize(characters.slice(cursor).join(""));
|
|
1652
|
-
if (displayLength(before) >= width) {
|
|
1653
|
-
const visible = takeDisplayEnd(before, width - 1);
|
|
1654
|
-
return { text: `…${visible}`, cursorWidth: 1 + displayLength(visible) };
|
|
1655
|
-
}
|
|
1656
|
-
const remaining = Math.max(0, width - displayLength(before));
|
|
1657
|
-
const visibleAfter = remaining > 0 ? truncateDisplay(after, remaining) : "";
|
|
1658
|
-
return { text: `${before}${visibleAfter}`, cursorWidth: displayLength(before) };
|
|
1659
|
-
}
|
|
1660
|
-
function takeDisplayEnd(text, width) {
|
|
1661
|
-
const result = [];
|
|
1662
|
-
let length = 0;
|
|
1663
|
-
for (const char of Array.from(text).reverse()) {
|
|
1664
|
-
const charWidth = displayLength(char);
|
|
1665
|
-
if (length + charWidth > width)
|
|
1666
|
-
break;
|
|
1667
|
-
result.push(char);
|
|
1668
|
-
length += charWidth;
|
|
1669
|
-
}
|
|
1670
|
-
return result.reverse().join("");
|
|
1671
|
+
function inputContentWidth() {
|
|
1672
|
+
return Math.max(20, Math.min(stdout.columns ?? 100, 100) - 4);
|
|
1671
1673
|
}
|
|
1672
1674
|
function submittedInputLabel(value) {
|
|
1673
1675
|
if (!value.includes("\n"))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/skills/skills.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;gBAE7B,MAAM,EAAE,WAAW;IAEhD,OAAO,CAAC,SAAS;IAMX,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAoBtF,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAY/E,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAaxC,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/skills/skills.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;gBAE7B,MAAM,EAAE,WAAW;IAEhD,OAAO,CAAC,SAAS;IAMX,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAoBtF,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAY/E,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAaxC,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IA2CvG,OAAO,CAAC,OAAO;YAKD,WAAW;CAuB1B"}
|
package/dist/skills/skills.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { truncate } from "../utils.js";
|
|
4
4
|
export class SkillManager {
|
|
5
5
|
config;
|
|
6
6
|
catalogCache;
|
|
@@ -55,9 +55,13 @@ export class SkillManager {
|
|
|
55
55
|
return truncate(body, 20000);
|
|
56
56
|
}
|
|
57
57
|
async installLocal(sourcePath, requestedName) {
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
|
|
58
|
+
// Local skill sources may live outside the active workspace (for example in a
|
|
59
|
+
// package cache or /tmp). Resolve the selected source canonically, while the
|
|
60
|
+
// installation target remains confined to the configured skills directory.
|
|
61
|
+
const requestedSource = path.isAbsolute(sourcePath)
|
|
62
|
+
? sourcePath
|
|
63
|
+
: path.resolve(this.config.workspaceRoot, sourcePath);
|
|
64
|
+
const source = await fs.realpath(requestedSource);
|
|
61
65
|
const stat = await fs.stat(source);
|
|
62
66
|
const skillFile = stat.isDirectory() ? path.join(source, "SKILL.md") : source;
|
|
63
67
|
if (path.basename(skillFile) !== "SKILL.md") {
|
package/dist/tools/builtin.js
CHANGED
|
@@ -331,12 +331,12 @@ export function createBuiltinRegistry(options) {
|
|
|
331
331
|
});
|
|
332
332
|
registry.register({
|
|
333
333
|
name: "skill_install",
|
|
334
|
-
description: "Install a local skill into the first configured skills directory.
|
|
334
|
+
description: "Install a local skill into the first configured skills directory. The source may be outside the workspace and must be a SKILL.md file or a directory containing SKILL.md.",
|
|
335
335
|
risk: "high",
|
|
336
336
|
input_schema: {
|
|
337
337
|
type: "object",
|
|
338
338
|
properties: {
|
|
339
|
-
sourcePath: { type: "string", description: "
|
|
339
|
+
sourcePath: { type: "string", description: "Local path, including outside the workspace, to SKILL.md or a directory containing SKILL.md." },
|
|
340
340
|
name: { type: "string", description: "Optional installed skill name." },
|
|
341
341
|
},
|
|
342
342
|
required: ["sourcePath"],
|