@yep-ai/cli 0.0.0-stage → 1.0.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/README.md +91 -2
- package/bin/clients.js +77 -0
- package/bin/market.js +1621 -0
- package/bin/yep.js +11 -0
- package/lib/device/client.js +472 -0
- package/lib/device/command.js +93 -0
- package/lib/device/tools.js +343 -0
- package/package.json +30 -4
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { constants as FS } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { StringDecoder } from "node:string_decoder";
|
|
6
|
+
const MAX_FILE = 10 * 1024 * 1024;
|
|
7
|
+
const MAX_OUTPUT = 64 * 1024;
|
|
8
|
+
function safeRelative(value = "") {
|
|
9
|
+
if (typeof value !== "string" || value.length > 4096 || /[\\:\0\r\n]/u.test(value) || value.startsWith("/") || value.split("/").some((x) => x === ".." || x === ".")) throw new Error("Unsafe relative path");
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
function samePath(a, b) {
|
|
13
|
+
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
14
|
+
}
|
|
15
|
+
function within(root, candidate) {
|
|
16
|
+
const rel = path.relative(root, candidate);
|
|
17
|
+
return rel === "" || !rel.startsWith(".." + path.sep) && rel !== ".." && !path.isAbsolute(rel);
|
|
18
|
+
}
|
|
19
|
+
async function rootDescriptor(id, location, privateDir) {
|
|
20
|
+
const absolute = path.resolve(location);
|
|
21
|
+
const stat = await fs.lstat(absolute);
|
|
22
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !samePath(await fs.realpath(absolute), absolute)) throw new Error("Root must be an ordinary directory without links");
|
|
23
|
+
const privatePath = privateDir && path.resolve(privateDir);
|
|
24
|
+
if (privatePath && within(privatePath, absolute)) throw new Error("Private CLI directory cannot be shared");
|
|
25
|
+
return { id, absolute, name: path.basename(absolute) || "文件系统", dev: stat.dev, ino: stat.ino, privateDir: privatePath };
|
|
26
|
+
}
|
|
27
|
+
async function resolveSafe(root, relative = "", kind) {
|
|
28
|
+
safeRelative(relative);
|
|
29
|
+
const rootStat = await fs.lstat(root.absolute);
|
|
30
|
+
if (rootStat.isSymbolicLink() || rootStat.dev !== root.dev || rootStat.ino !== root.ino || !samePath(await fs.realpath(root.absolute), root.absolute)) throw new Error("Authorized directory was replaced");
|
|
31
|
+
let candidate = root.absolute;
|
|
32
|
+
for (const segment of relative.split("/").filter(Boolean)) {
|
|
33
|
+
candidate = path.join(candidate, segment);
|
|
34
|
+
const st2 = await fs.lstat(candidate);
|
|
35
|
+
if (st2.isSymbolicLink() || !st2.isDirectory() && !st2.isFile()) throw new Error("Links and special files are forbidden");
|
|
36
|
+
}
|
|
37
|
+
const st = await fs.lstat(candidate);
|
|
38
|
+
const real = await fs.realpath(candidate);
|
|
39
|
+
if (!samePath(real, candidate) || !within(root.absolute, real) || root.privateDir && within(root.privateDir, real)) throw new Error("Path escapes authorization");
|
|
40
|
+
if (kind === "file" && !st.isFile() || kind === "directory" && !st.isDirectory()) throw new Error("Unexpected file type");
|
|
41
|
+
return { absolute: candidate, stat: st };
|
|
42
|
+
}
|
|
43
|
+
async function pinnedOpen(root, relative, directory = false) {
|
|
44
|
+
const checked = await resolveSafe(root, relative, directory ? "directory" : "file");
|
|
45
|
+
if (process.platform !== "linux") return fs.open(checked.absolute, FS.O_RDONLY | (FS.O_NOFOLLOW || 0) | (directory ? FS.O_DIRECTORY || 0 : 0));
|
|
46
|
+
const parts = relative.split("/").filter(Boolean);
|
|
47
|
+
const held = [];
|
|
48
|
+
try {
|
|
49
|
+
let parent = await fs.open(root.absolute, FS.O_RDONLY | FS.O_NOFOLLOW | FS.O_DIRECTORY);
|
|
50
|
+
held.push(parent);
|
|
51
|
+
const rootIdentity = await parent.stat();
|
|
52
|
+
if (rootIdentity.dev !== root.dev || rootIdentity.ino !== root.ino) throw new Error("Authorized directory was replaced");
|
|
53
|
+
for (let i = 0; i < parts.length; i++) {
|
|
54
|
+
const isDirectory = i < parts.length - 1 || directory;
|
|
55
|
+
parent = await fs.open(`/proc/self/fd/${parent.fd}/${parts[i]}`, FS.O_RDONLY | FS.O_NOFOLLOW | (isDirectory ? FS.O_DIRECTORY : 0));
|
|
56
|
+
held.push(parent);
|
|
57
|
+
const st = await parent.stat();
|
|
58
|
+
if (isDirectory ? !st.isDirectory() : !st.isFile()) throw new Error("Links and special files are forbidden");
|
|
59
|
+
}
|
|
60
|
+
const result = held.pop();
|
|
61
|
+
return result;
|
|
62
|
+
} finally {
|
|
63
|
+
await Promise.all(held.map((handle) => handle.close()));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function readFile(root, relative, maximum = MAX_FILE) {
|
|
67
|
+
const resolved = await resolveSafe(root, relative, "file");
|
|
68
|
+
if (resolved.stat.size > maximum) throw new Error("File exceeds size limit");
|
|
69
|
+
const fd = await pinnedOpen(root, relative);
|
|
70
|
+
try {
|
|
71
|
+
const before = await fd.stat();
|
|
72
|
+
if (before.ino !== resolved.stat.ino || before.dev !== resolved.stat.dev || !before.isFile()) throw new Error("File changed during authorization");
|
|
73
|
+
const buffer = Buffer.alloc(Math.min(maximum + 1, before.size + 1));
|
|
74
|
+
const { bytesRead } = await fd.read(buffer, 0, buffer.length, 0);
|
|
75
|
+
if (bytesRead > maximum) throw new Error("File exceeds size limit");
|
|
76
|
+
await resolveSafe(root, relative, "file");
|
|
77
|
+
return buffer.subarray(0, bytesRead);
|
|
78
|
+
} finally {
|
|
79
|
+
await fd.close();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function list(root, relative = "", offset = 0, includeFiles = true) {
|
|
83
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset > 1e4) throw new Error("Invalid offset");
|
|
84
|
+
const { absolute } = await resolveSafe(root, relative, "directory");
|
|
85
|
+
let entries;
|
|
86
|
+
const pinned = await pinnedOpen(root, relative, true);
|
|
87
|
+
try {
|
|
88
|
+
entries = await fs.readdir(process.platform === "linux" ? `/proc/self/fd/${pinned.fd}` : absolute, { withFileTypes: true });
|
|
89
|
+
await resolveSafe(root, relative, "directory");
|
|
90
|
+
} finally {
|
|
91
|
+
await pinned.close();
|
|
92
|
+
}
|
|
93
|
+
;
|
|
94
|
+
if (entries.length > 1e4) throw new Error("Directory exceeds 10000 entries");
|
|
95
|
+
const filtered = entries.filter((e) => !e.isSymbolicLink() && (e.isDirectory() || includeFiles && e.isFile()) && !/[\\:\0\r\n]/u.test(e.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
96
|
+
const page = [];
|
|
97
|
+
for (const e of filtered.slice(offset, offset + 200)) {
|
|
98
|
+
const p = [relative, e.name].filter(Boolean).join("/");
|
|
99
|
+
try {
|
|
100
|
+
await resolveSafe(root, p);
|
|
101
|
+
page.push({ name: e.name, kind: e.isDirectory() ? "directory" : "file", path: p });
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { folderId: root.id, path: relative, parentPath: relative ? relative.split("/").slice(0, -1).join("/") : null, entries: page, offset, hasMore: offset + 200 < filtered.length };
|
|
106
|
+
}
|
|
107
|
+
function utf8(data) {
|
|
108
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(data);
|
|
109
|
+
if (text.includes("\0")) throw new Error("File is not UTF-8 text");
|
|
110
|
+
return text;
|
|
111
|
+
}
|
|
112
|
+
function bounded(text) {
|
|
113
|
+
const b = Buffer.from(text);
|
|
114
|
+
return { text: b.subarray(0, MAX_OUTPUT).toString("utf8"), truncated: b.length > MAX_OUTPUT };
|
|
115
|
+
}
|
|
116
|
+
async function fileOperation(root, request) {
|
|
117
|
+
const op = request.operation;
|
|
118
|
+
const relative = request.path ?? "";
|
|
119
|
+
if (op === "list") return list(root, relative, request.offset ?? 0, true);
|
|
120
|
+
if (op === "read") {
|
|
121
|
+
const data = await readFile(root, relative);
|
|
122
|
+
return { name: path.basename(relative), data: data.toString("base64"), size: data.length };
|
|
123
|
+
}
|
|
124
|
+
const text = utf8(await readFile(root, relative));
|
|
125
|
+
const lines = text.split("\n");
|
|
126
|
+
if (op === "read_lines") {
|
|
127
|
+
const start = request.startLine ?? 1;
|
|
128
|
+
const end = request.endLine ?? Math.min(start + 499, lines.length);
|
|
129
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 1 || end < start || end - start >= 500) throw new Error("Invalid line range (maximum 500 lines)");
|
|
130
|
+
return { name: path.basename(relative), ...bounded(lines.slice(start - 1, end).join("\n")), startLine: start, endLine: Math.min(end, lines.length) };
|
|
131
|
+
}
|
|
132
|
+
if (op === "search") {
|
|
133
|
+
if (typeof request.query !== "string" || !request.query || request.query.length > 1e3) throw new Error("Literal query required");
|
|
134
|
+
let matches = [];
|
|
135
|
+
let bytes = 0;
|
|
136
|
+
for (let i = 0; i < lines.length; i++) {
|
|
137
|
+
if (lines[i].length > 65536) continue;
|
|
138
|
+
if (lines[i].includes(request.query)) {
|
|
139
|
+
const item = { line: i + 1, text: lines[i] };
|
|
140
|
+
bytes += Buffer.byteLength(item.text);
|
|
141
|
+
if (matches.length >= 50 || bytes > MAX_OUTPUT) break;
|
|
142
|
+
matches.push(item);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { matches };
|
|
146
|
+
}
|
|
147
|
+
throw new Error("Unknown file operation");
|
|
148
|
+
}
|
|
149
|
+
function tokenize(command) {
|
|
150
|
+
if (typeof command !== "string" || !command.trim() || command.length > 4096 || /[|&;<>$`\n\r\0%!*?()[\]{}]/u.test(command)) throw new Error("Only restricted built-in commands are allowed");
|
|
151
|
+
const tokens = [];
|
|
152
|
+
const regex = /"([^"\\]*)"|'([^'\\]*)'|([^\s"']+)/gu;
|
|
153
|
+
let match;
|
|
154
|
+
let end = 0;
|
|
155
|
+
while (match = regex.exec(command)) {
|
|
156
|
+
if (command.slice(end, match.index).trim()) throw new Error("Invalid command quoting");
|
|
157
|
+
tokens.push(match[1] ?? match[2] ?? match[3]);
|
|
158
|
+
end = regex.lastIndex;
|
|
159
|
+
}
|
|
160
|
+
if (command.slice(end).trim()) throw new Error("Invalid command quoting");
|
|
161
|
+
return tokens;
|
|
162
|
+
}
|
|
163
|
+
async function builtin(root, cwd, command) {
|
|
164
|
+
const [cmd, ...args] = tokenize(command);
|
|
165
|
+
await resolveSafe(root, cwd, "directory");
|
|
166
|
+
const relative = (v = "") => [cwd, v].filter(Boolean).join("/");
|
|
167
|
+
const read = async (v) => utf8(await readFile(root, relative(v)));
|
|
168
|
+
if (cmd === "pwd" && args.length === 0) return { text: cwd || "." };
|
|
169
|
+
if (["ls", "dir"].includes(cmd) && args.length <= 1) {
|
|
170
|
+
const r = await list(root, relative(args[0]), 0, true);
|
|
171
|
+
return bounded(r.entries.map((e) => e.name + (e.kind === "directory" ? "/" : "")).join("\n"));
|
|
172
|
+
}
|
|
173
|
+
if (cmd === "echo") return bounded(args.join(" ") + "\n");
|
|
174
|
+
if (["cat", "type"].includes(cmd) && args.length === 1) return bounded(await read(args[0]));
|
|
175
|
+
if (["head", "tail"].includes(cmd)) {
|
|
176
|
+
let count = 10, file;
|
|
177
|
+
if (args.length === 1) file = args[0];
|
|
178
|
+
else if (args.length === 3 && args[0] === "-n" && /^\d+$/u.test(args[1])) {
|
|
179
|
+
count = Number(args[1]);
|
|
180
|
+
file = args[2];
|
|
181
|
+
} else throw new Error("Usage: head/tail [-n count] file");
|
|
182
|
+
if (count > 500) throw new Error("Maximum 500 lines");
|
|
183
|
+
const lines = (await read(file)).split("\n");
|
|
184
|
+
return bounded((cmd === "head" ? lines.slice(0, count) : lines.slice(-count)).join("\n"));
|
|
185
|
+
}
|
|
186
|
+
if (["grep", "findstr"].includes(cmd) && args.length === 2) {
|
|
187
|
+
const lines = (await read(args[1])).split("\n");
|
|
188
|
+
return bounded(lines.filter((l) => l.includes(args[0])).slice(0, 50).join("\n"));
|
|
189
|
+
}
|
|
190
|
+
if (cmd === "wc" && args.length === 2 && args[0] === "-l") return { text: String((await read(args[1])).split("\n").length - 1) + "\n" };
|
|
191
|
+
throw new Error("Unsupported restricted command");
|
|
192
|
+
}
|
|
193
|
+
function parseTerminalDirectory(command, windows = process.platform === "win32") {
|
|
194
|
+
if (!/^\s*cd(?:\s|$)/iu.test(command)) return null;
|
|
195
|
+
if (/[\0\r\n]/u.test(command)) throw new Error("Usage: cd [directory]; run other commands separately");
|
|
196
|
+
let target = command.trim().slice(2).trim();
|
|
197
|
+
if (windows && /^\/d(?:\s|$)/iu.test(target)) target = target.slice(2).trim();
|
|
198
|
+
if (!target) return "";
|
|
199
|
+
const quote = target[0];
|
|
200
|
+
if (quote === '"' || quote === "'") {
|
|
201
|
+
// A directory name is interpreted literally, never as shell source.
|
|
202
|
+
// Windows backslashes must survive quotation rather than become escapes.
|
|
203
|
+
if (target.at(-1) !== quote || target.slice(1, -1).includes(quote)) throw new Error("Usage: cd [directory]; run other commands separately");
|
|
204
|
+
return target.slice(1, -1);
|
|
205
|
+
}
|
|
206
|
+
if (/[\s|&;<>$`%!*?()[\]{}"']/u.test(target)) throw new Error("Usage: cd [directory]; run other commands separately");
|
|
207
|
+
return target;
|
|
208
|
+
}
|
|
209
|
+
async function terminalDirectory(root, cwd, command, restricted) {
|
|
210
|
+
let target;
|
|
211
|
+
try {
|
|
212
|
+
target = parseTerminalDirectory(command);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
// Only a standalone literal cd changes the simulated terminal directory.
|
|
215
|
+
// In system mode, compound commands and shell expressions run normally in
|
|
216
|
+
// their own shell, whose working directory lasts for that command only.
|
|
217
|
+
if (!restricted) return null;
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
if (target === null) return null;
|
|
221
|
+
await resolveSafe(root, cwd, "directory");
|
|
222
|
+
if (!restricted && (target === "~" || target.startsWith("~/"))) {
|
|
223
|
+
target = path.join(process.env.USERPROFILE || process.env.HOME || root.absolute, target.slice(2));
|
|
224
|
+
}
|
|
225
|
+
let relative;
|
|
226
|
+
if (path.isAbsolute(target)) {
|
|
227
|
+
if (restricted) throw new Error("Use a relative directory inside the authorized root");
|
|
228
|
+
relative = path.relative(root.absolute, path.resolve(target)).split(path.sep).join("/");
|
|
229
|
+
} else {
|
|
230
|
+
if (process.platform === "win32") target = target.replaceAll("\\", "/");
|
|
231
|
+
relative = target ? path.posix.normalize([cwd, target].filter(Boolean).join("/")) : "";
|
|
232
|
+
}
|
|
233
|
+
if (relative === ".") relative = "";
|
|
234
|
+
await resolveSafe(root, relative, "directory");
|
|
235
|
+
return relative;
|
|
236
|
+
}
|
|
237
|
+
// Retain an unfinished secret prefix across chunks so streaming cannot expose a
|
|
238
|
+
// token that happened to cross a stdout/stderr packet boundary.
|
|
239
|
+
function outputRedactor(secrets, emit) {
|
|
240
|
+
const values = [...new Set(secrets.filter(value => typeof value === "string" && value))].sort((a, b) => b.length - a.length);
|
|
241
|
+
let pending = "";
|
|
242
|
+
return (text, final = false, truncated = false) => {
|
|
243
|
+
pending += text;
|
|
244
|
+
for (const secret of values) pending = pending.split(secret).join("[REDACTED]");
|
|
245
|
+
let tail = 0;
|
|
246
|
+
for (const secret of values) {
|
|
247
|
+
for (let length = Math.min(secret.length - 1, pending.length); length > tail; length--) {
|
|
248
|
+
if (pending.endsWith(secret.slice(0, length))) { tail = length; break; }
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (final) {
|
|
252
|
+
if (truncated && tail) pending = pending.slice(0, -tail) + "[REDACTED]";
|
|
253
|
+
if (pending) emit(pending);
|
|
254
|
+
pending = "";
|
|
255
|
+
} else {
|
|
256
|
+
const ready = pending.slice(0, pending.length - tail);
|
|
257
|
+
pending = pending.slice(pending.length - tail);
|
|
258
|
+
if (ready) emit(ready);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
async function execute(root, request, { restricted = true, signal, secrets = [], onOutput } = {}) {
|
|
263
|
+
if (signal?.aborted) throw new Error("Operation canceled");
|
|
264
|
+
if (request.operation !== "exec") return fileOperation(root, request);
|
|
265
|
+
if (restricted) {
|
|
266
|
+
const result = await builtin(root, request.path ?? "", request.command);
|
|
267
|
+
if (signal?.aborted) throw new Error("Operation canceled");
|
|
268
|
+
let stdout = "";
|
|
269
|
+
outputRedactor(secrets, text => { stdout += text; onOutput?.("stdout", text); })(result.text, true, result.truncated);
|
|
270
|
+
return { stdout, stderr: "", exitCode: 0, truncated: result.truncated ?? false };
|
|
271
|
+
}
|
|
272
|
+
const { absolute } = await resolveSafe(root, request.path ?? "", "directory");
|
|
273
|
+
if (typeof request.command !== "string" || request.command.length > 4096 || request.command.includes("\0")) throw new Error("Invalid command");
|
|
274
|
+
return new Promise((resolve, reject) => {
|
|
275
|
+
const env = { ...process.env };
|
|
276
|
+
for (const k of Object.keys(env)) if (/TOKEN|SECRET|PASSWORD|API_KEY|AUTHORIZATION/iu.test(k)) delete env[k];
|
|
277
|
+
const child = spawn(process.platform === "win32" ? process.env.ComSpec || "cmd.exe" : "/bin/sh", process.platform === "win32" ? ["/d", "/s", "/c", request.command] : ["-c", request.command], { cwd: absolute, env, detached: process.platform !== "win32", windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
278
|
+
let stdout = "", stderr = "", bytes = 0, truncated = false, timedOut = false, streamError;
|
|
279
|
+
const kill = () => {
|
|
280
|
+
if (process.platform === "win32") {
|
|
281
|
+
spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
|
|
282
|
+
} else {
|
|
283
|
+
try {
|
|
284
|
+
process.kill(-child.pid, "SIGKILL");
|
|
285
|
+
} catch {
|
|
286
|
+
child.kill("SIGKILL");
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
const timer = setTimeout(() => {
|
|
291
|
+
timedOut = true;
|
|
292
|
+
kill();
|
|
293
|
+
}, 3e4);
|
|
294
|
+
const abort = () => kill();
|
|
295
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
296
|
+
if (signal?.aborted) kill();
|
|
297
|
+
const decoders = { stdout: new StringDecoder("utf8"), stderr: new StringDecoder("utf8") };
|
|
298
|
+
const redact = Object.fromEntries(["stdout", "stderr"].map(which => [which, outputRedactor(secrets, text => {
|
|
299
|
+
if (which === "stdout") stdout += text;
|
|
300
|
+
else stderr += text;
|
|
301
|
+
if (!signal?.aborted && !streamError) {
|
|
302
|
+
try { onOutput?.(which, text); } catch (error) { streamError = error; kill(); }
|
|
303
|
+
}
|
|
304
|
+
})]));
|
|
305
|
+
const append = (which, chunk) => {
|
|
306
|
+
const room = Math.max(0, MAX_OUTPUT - bytes);
|
|
307
|
+
bytes += chunk.length;
|
|
308
|
+
if (bytes > MAX_OUTPUT) truncated = true;
|
|
309
|
+
redact[which](decoders[which].write(chunk.subarray(0, room)));
|
|
310
|
+
};
|
|
311
|
+
child.stdout.on("data", (c) => append("stdout", c));
|
|
312
|
+
child.stderr.on("data", (c) => append("stderr", c));
|
|
313
|
+
child.once("error", (e) => {
|
|
314
|
+
clearTimeout(timer);
|
|
315
|
+
signal?.removeEventListener("abort", abort);
|
|
316
|
+
reject(e);
|
|
317
|
+
});
|
|
318
|
+
child.once("close", (code, sig) => {
|
|
319
|
+
clearTimeout(timer);
|
|
320
|
+
signal?.removeEventListener("abort", abort);
|
|
321
|
+
for (const which of ["stdout", "stderr"]) redact[which](decoders[which].end(), true, truncated);
|
|
322
|
+
if (signal?.aborted) {
|
|
323
|
+
const error = new Error("Operation canceled");
|
|
324
|
+
error.output = { stdout, stderr, exitCode: null, canceled: true, truncated, timedOut };
|
|
325
|
+
return reject(error);
|
|
326
|
+
}
|
|
327
|
+
if (streamError) return reject(streamError);
|
|
328
|
+
resolve({ stdout, stderr, exitCode: code, signal: sig, truncated, timedOut });
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
export {
|
|
333
|
+
MAX_FILE,
|
|
334
|
+
builtin,
|
|
335
|
+
execute,
|
|
336
|
+
fileOperation,
|
|
337
|
+
list,
|
|
338
|
+
resolveSafe,
|
|
339
|
+
rootDescriptor,
|
|
340
|
+
safeRelative,
|
|
341
|
+
terminalDirectory,
|
|
342
|
+
parseTerminalDirectory
|
|
343
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yep-ai/cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Yep Marketplace unified CLI: skills, agents, plugins and authorized device nodes.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"yep": "bin/yep.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/yep.js",
|
|
11
|
+
"bin/market.js",
|
|
12
|
+
"bin/clients.js",
|
|
13
|
+
"lib",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22.0.0"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"ws": "^8.21.0"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public",
|
|
25
|
+
"registry": "https://registry.npmjs.org"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "node bin/yep.js --version",
|
|
29
|
+
"check": "node --check bin/yep.js && node --check bin/market.js && node --check lib/device/command.js && node --check lib/device/client.js && node --check lib/device/tools.js",
|
|
30
|
+
"test": "node --test"
|
|
31
|
+
}
|
|
32
|
+
}
|