@vellumai/cli 0.4.11 → 0.4.13
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/package.json +1 -1
- package/src/commands/sleep.ts +3 -9
- package/src/index.ts +7 -11
- package/src/commands/skills.ts +0 -355
package/package.json
CHANGED
package/src/commands/sleep.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { homedir } from "os";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
|
|
4
|
-
import { loadAllAssistants } from "../lib/assistant-config";
|
|
5
4
|
import { stopProcessByPidFile } from "../lib/process";
|
|
6
5
|
|
|
7
6
|
export async function sleep(): Promise<void> {
|
|
@@ -13,20 +12,15 @@ export async function sleep(): Promise<void> {
|
|
|
13
12
|
process.exit(0);
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
const assistants = loadAllAssistants();
|
|
17
|
-
const hasLocal = assistants.some((a) => a.cloud === "local");
|
|
18
|
-
if (!hasLocal) {
|
|
19
|
-
console.error("Error: No local assistant found in lock file. Run 'vellum hatch local' first.");
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
15
|
const vellumDir = join(homedir(), ".vellum");
|
|
24
16
|
const daemonPidFile = join(vellumDir, "vellum.pid");
|
|
25
17
|
const socketFile = join(vellumDir, "vellum.sock");
|
|
26
18
|
const gatewayPidFile = join(vellumDir, "gateway.pid");
|
|
27
19
|
|
|
28
20
|
// Stop daemon
|
|
29
|
-
const daemonStopped = await stopProcessByPidFile(daemonPidFile, "daemon", [
|
|
21
|
+
const daemonStopped = await stopProcessByPidFile(daemonPidFile, "daemon", [
|
|
22
|
+
socketFile,
|
|
23
|
+
]);
|
|
30
24
|
if (!daemonStopped) {
|
|
31
25
|
console.log("Daemon is not running.");
|
|
32
26
|
} else {
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,6 @@ import { pair } from "./commands/pair";
|
|
|
18
18
|
import { ps } from "./commands/ps";
|
|
19
19
|
import { recover } from "./commands/recover";
|
|
20
20
|
import { retire } from "./commands/retire";
|
|
21
|
-
import { skills } from "./commands/skills";
|
|
22
21
|
import { sleep } from "./commands/sleep";
|
|
23
22
|
import { ssh } from "./commands/ssh";
|
|
24
23
|
import { tunnel } from "./commands/tunnel";
|
|
@@ -37,7 +36,6 @@ const commands = {
|
|
|
37
36
|
ps,
|
|
38
37
|
recover,
|
|
39
38
|
retire,
|
|
40
|
-
skills,
|
|
41
39
|
sleep,
|
|
42
40
|
ssh,
|
|
43
41
|
tunnel,
|
|
@@ -51,9 +49,8 @@ function resolveAssistantEntry(): string | undefined {
|
|
|
51
49
|
// When installed globally, resolve from node_modules
|
|
52
50
|
try {
|
|
53
51
|
const require = createRequire(import.meta.url);
|
|
54
|
-
const assistantPkgPath =
|
|
55
|
-
"@vellumai/assistant/package.json"
|
|
56
|
-
);
|
|
52
|
+
const assistantPkgPath =
|
|
53
|
+
require.resolve("@vellumai/assistant/package.json");
|
|
57
54
|
return join(dirname(assistantPkgPath), "src", "index.ts");
|
|
58
55
|
} catch {
|
|
59
56
|
// For local development, resolve from sibling directory
|
|
@@ -64,7 +61,7 @@ function resolveAssistantEntry(): string | undefined {
|
|
|
64
61
|
"..",
|
|
65
62
|
"assistant",
|
|
66
63
|
"src",
|
|
67
|
-
"index.ts"
|
|
64
|
+
"index.ts",
|
|
68
65
|
);
|
|
69
66
|
if (existsSync(localPath)) {
|
|
70
67
|
return localPath;
|
|
@@ -95,10 +92,11 @@ async function main() {
|
|
|
95
92
|
console.log(" login Log in to the Vellum platform");
|
|
96
93
|
console.log(" logout Log out of the Vellum platform");
|
|
97
94
|
console.log(" pair Pair with a remote assistant via QR code");
|
|
98
|
-
console.log(
|
|
95
|
+
console.log(
|
|
96
|
+
" ps List assistants (or processes for a specific assistant)",
|
|
97
|
+
);
|
|
99
98
|
console.log(" recover Restore a previously retired local assistant");
|
|
100
99
|
console.log(" retire Delete an assistant instance");
|
|
101
|
-
console.log(" skills Browse and install skills from the Vellum catalog");
|
|
102
100
|
console.log(" sleep Stop the daemon process");
|
|
103
101
|
console.log(" ssh SSH into a remote assistant instance");
|
|
104
102
|
console.log(" tunnel Create a tunnel for a locally hosted assistant");
|
|
@@ -120,9 +118,7 @@ async function main() {
|
|
|
120
118
|
});
|
|
121
119
|
} else {
|
|
122
120
|
console.error(`Unknown command: ${commandName}`);
|
|
123
|
-
console.error(
|
|
124
|
-
"Install the full stack with: bun install -g vellum"
|
|
125
|
-
);
|
|
121
|
+
console.error("Install the full stack with: bun install -g vellum");
|
|
126
122
|
process.exit(1);
|
|
127
123
|
}
|
|
128
124
|
return;
|
package/src/commands/skills.ts
DELETED
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
existsSync,
|
|
3
|
-
mkdirSync,
|
|
4
|
-
readFileSync,
|
|
5
|
-
renameSync,
|
|
6
|
-
writeFileSync,
|
|
7
|
-
} from "node:fs";
|
|
8
|
-
import { homedir } from "node:os";
|
|
9
|
-
import { join, dirname } from "node:path";
|
|
10
|
-
import { gunzipSync } from "node:zlib";
|
|
11
|
-
import { randomUUID } from "node:crypto";
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Path helpers
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
function getRootDir(): string {
|
|
18
|
-
return join(process.env.BASE_DATA_DIR?.trim() || homedir(), ".vellum");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function getSkillsDir(): string {
|
|
22
|
-
return join(getRootDir(), "workspace", "skills");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function getSkillsIndexPath(): string {
|
|
26
|
-
return join(getSkillsDir(), "SKILLS.md");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Platform API client
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
function getConfigPlatformUrl(): string | undefined {
|
|
34
|
-
try {
|
|
35
|
-
const configPath = join(getRootDir(), "workspace", "config.json");
|
|
36
|
-
if (!existsSync(configPath)) return undefined;
|
|
37
|
-
const raw = JSON.parse(readFileSync(configPath, "utf-8")) as Record<
|
|
38
|
-
string,
|
|
39
|
-
unknown
|
|
40
|
-
>;
|
|
41
|
-
const platform = raw.platform as Record<string, unknown> | undefined;
|
|
42
|
-
const baseUrl = platform?.baseUrl;
|
|
43
|
-
if (typeof baseUrl === "string" && baseUrl.trim()) return baseUrl.trim();
|
|
44
|
-
} catch {
|
|
45
|
-
// ignore
|
|
46
|
-
}
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function getPlatformUrl(): string {
|
|
51
|
-
return (
|
|
52
|
-
process.env.VELLUM_ASSISTANT_PLATFORM_URL ??
|
|
53
|
-
getConfigPlatformUrl() ??
|
|
54
|
-
"https://platform.vellum.ai"
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function getPlatformToken(): string | null {
|
|
59
|
-
try {
|
|
60
|
-
return readFileSync(join(getRootDir(), "platform-token"), "utf-8").trim();
|
|
61
|
-
} catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function buildHeaders(): Record<string, string> {
|
|
67
|
-
const headers: Record<string, string> = {};
|
|
68
|
-
const token = getPlatformToken();
|
|
69
|
-
if (token) {
|
|
70
|
-
headers["X-Session-Token"] = token;
|
|
71
|
-
}
|
|
72
|
-
return headers;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
// Types
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
|
|
79
|
-
interface CatalogSkill {
|
|
80
|
-
id: string;
|
|
81
|
-
name: string;
|
|
82
|
-
description: string;
|
|
83
|
-
emoji?: string;
|
|
84
|
-
includes?: string[];
|
|
85
|
-
version?: string;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
interface CatalogManifest {
|
|
89
|
-
version: number;
|
|
90
|
-
skills: CatalogSkill[];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
// Catalog operations
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
|
|
97
|
-
async function fetchCatalog(): Promise<CatalogSkill[]> {
|
|
98
|
-
const url = `${getPlatformUrl()}/v1/skills/`;
|
|
99
|
-
const response = await fetch(url, {
|
|
100
|
-
headers: buildHeaders(),
|
|
101
|
-
signal: AbortSignal.timeout(10000),
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
if (!response.ok) {
|
|
105
|
-
throw new Error(`Platform API error ${response.status}: ${response.statusText}`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const manifest = (await response.json()) as CatalogManifest;
|
|
109
|
-
if (!Array.isArray(manifest.skills)) {
|
|
110
|
-
throw new Error("Platform catalog has invalid skills array");
|
|
111
|
-
}
|
|
112
|
-
return manifest.skills;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Extract SKILL.md content from a tar archive (uncompressed).
|
|
117
|
-
*/
|
|
118
|
-
function extractSkillMdFromTar(tarBuffer: Buffer): string | null {
|
|
119
|
-
let offset = 0;
|
|
120
|
-
while (offset + 512 <= tarBuffer.length) {
|
|
121
|
-
const header = tarBuffer.subarray(offset, offset + 512);
|
|
122
|
-
|
|
123
|
-
// End-of-archive (two consecutive zero blocks)
|
|
124
|
-
if (header.every((b) => b === 0)) break;
|
|
125
|
-
|
|
126
|
-
// Filename (bytes 0-99, null-terminated)
|
|
127
|
-
const nameEnd = header.indexOf(0, 0);
|
|
128
|
-
const name = header
|
|
129
|
-
.subarray(0, Math.min(nameEnd >= 0 ? nameEnd : 100, 100))
|
|
130
|
-
.toString("utf-8");
|
|
131
|
-
|
|
132
|
-
// File size (bytes 124-135, octal)
|
|
133
|
-
const sizeStr = header.subarray(124, 136).toString("utf-8").trim();
|
|
134
|
-
const size = parseInt(sizeStr, 8) || 0;
|
|
135
|
-
|
|
136
|
-
offset += 512; // past header
|
|
137
|
-
|
|
138
|
-
if (name.endsWith("SKILL.md") || name === "SKILL.md") {
|
|
139
|
-
return tarBuffer.subarray(offset, offset + size).toString("utf-8");
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Skip to next header (data padded to 512 bytes)
|
|
143
|
-
offset += Math.ceil(size / 512) * 512;
|
|
144
|
-
}
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async function fetchSkillContent(skillId: string): Promise<string> {
|
|
149
|
-
const url = `${getPlatformUrl()}/v1/skills/${encodeURIComponent(skillId)}/`;
|
|
150
|
-
const response = await fetch(url, {
|
|
151
|
-
headers: buildHeaders(),
|
|
152
|
-
signal: AbortSignal.timeout(15000),
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
if (!response.ok) {
|
|
156
|
-
throw new Error(
|
|
157
|
-
`Failed to fetch skill "${skillId}": HTTP ${response.status}`,
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const gzipBuffer = Buffer.from(await response.arrayBuffer());
|
|
162
|
-
const tarBuffer = gunzipSync(gzipBuffer);
|
|
163
|
-
const skillMd = extractSkillMdFromTar(tarBuffer);
|
|
164
|
-
|
|
165
|
-
if (!skillMd) {
|
|
166
|
-
throw new Error(`SKILL.md not found in archive for "${skillId}"`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return skillMd;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// ---------------------------------------------------------------------------
|
|
173
|
-
// Managed skill installation
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
|
-
|
|
176
|
-
function atomicWriteFile(filePath: string, content: string): void {
|
|
177
|
-
const dir = dirname(filePath);
|
|
178
|
-
mkdirSync(dir, { recursive: true });
|
|
179
|
-
const tmpPath = join(dir, `.tmp-${randomUUID()}`);
|
|
180
|
-
writeFileSync(tmpPath, content, "utf-8");
|
|
181
|
-
renameSync(tmpPath, filePath);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function upsertSkillsIndex(id: string): void {
|
|
185
|
-
const indexPath = getSkillsIndexPath();
|
|
186
|
-
let lines: string[] = [];
|
|
187
|
-
if (existsSync(indexPath)) {
|
|
188
|
-
lines = readFileSync(indexPath, "utf-8").split("\n");
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
192
|
-
const pattern = new RegExp(`^[-*]\\s+(?:\`)?${escaped}(?:\`)?\\s*$`);
|
|
193
|
-
if (lines.some((line) => pattern.test(line))) return;
|
|
194
|
-
|
|
195
|
-
const nonEmpty = lines.filter((l) => l.trim());
|
|
196
|
-
nonEmpty.push(`- ${id}`);
|
|
197
|
-
const content = nonEmpty.join("\n");
|
|
198
|
-
atomicWriteFile(indexPath, content.endsWith("\n") ? content : content + "\n");
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function installSkillLocally(
|
|
202
|
-
skillId: string,
|
|
203
|
-
skillMdContent: string,
|
|
204
|
-
catalogEntry: CatalogSkill,
|
|
205
|
-
overwrite: boolean,
|
|
206
|
-
): void {
|
|
207
|
-
const skillDir = join(getSkillsDir(), skillId);
|
|
208
|
-
const skillFilePath = join(skillDir, "SKILL.md");
|
|
209
|
-
|
|
210
|
-
if (existsSync(skillFilePath) && !overwrite) {
|
|
211
|
-
throw new Error(
|
|
212
|
-
`Skill "${skillId}" is already installed. Use --overwrite to replace it.`,
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
mkdirSync(skillDir, { recursive: true });
|
|
217
|
-
atomicWriteFile(skillFilePath, skillMdContent);
|
|
218
|
-
|
|
219
|
-
// Write version metadata
|
|
220
|
-
if (catalogEntry.version) {
|
|
221
|
-
const meta = {
|
|
222
|
-
version: catalogEntry.version,
|
|
223
|
-
installedAt: new Date().toISOString(),
|
|
224
|
-
};
|
|
225
|
-
atomicWriteFile(
|
|
226
|
-
join(skillDir, "version.json"),
|
|
227
|
-
JSON.stringify(meta, null, 2) + "\n",
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
upsertSkillsIndex(skillId);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// ---------------------------------------------------------------------------
|
|
235
|
-
// Helpers
|
|
236
|
-
// ---------------------------------------------------------------------------
|
|
237
|
-
|
|
238
|
-
function hasFlag(args: string[], flag: string): boolean {
|
|
239
|
-
return args.includes(flag);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// ---------------------------------------------------------------------------
|
|
243
|
-
// Usage
|
|
244
|
-
// ---------------------------------------------------------------------------
|
|
245
|
-
|
|
246
|
-
function printUsage(): void {
|
|
247
|
-
console.log("Usage: vellum skills <subcommand> [options]");
|
|
248
|
-
console.log("");
|
|
249
|
-
console.log("Subcommands:");
|
|
250
|
-
console.log(" list List available catalog skills");
|
|
251
|
-
console.log(
|
|
252
|
-
" install <skill-id> [--overwrite] Install a skill from the catalog",
|
|
253
|
-
);
|
|
254
|
-
console.log("");
|
|
255
|
-
console.log("Options:");
|
|
256
|
-
console.log(" --json Machine-readable JSON output");
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// ---------------------------------------------------------------------------
|
|
260
|
-
// Command entry point
|
|
261
|
-
// ---------------------------------------------------------------------------
|
|
262
|
-
|
|
263
|
-
export async function skills(): Promise<void> {
|
|
264
|
-
const args = process.argv.slice(3);
|
|
265
|
-
const subcommand = args[0];
|
|
266
|
-
const json = hasFlag(args, "--json");
|
|
267
|
-
|
|
268
|
-
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
269
|
-
printUsage();
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
switch (subcommand) {
|
|
274
|
-
case "list": {
|
|
275
|
-
try {
|
|
276
|
-
const catalog = await fetchCatalog();
|
|
277
|
-
|
|
278
|
-
if (json) {
|
|
279
|
-
console.log(JSON.stringify({ ok: true, skills: catalog }));
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (catalog.length === 0) {
|
|
284
|
-
console.log("No skills available in the catalog.");
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
console.log(`Available skills (${catalog.length}):\n`);
|
|
289
|
-
for (const s of catalog) {
|
|
290
|
-
const emoji = s.emoji ? `${s.emoji} ` : "";
|
|
291
|
-
const deps = s.includes?.length
|
|
292
|
-
? ` (requires: ${s.includes.join(", ")})`
|
|
293
|
-
: "";
|
|
294
|
-
console.log(` ${emoji}${s.id}`);
|
|
295
|
-
console.log(` ${s.name} — ${s.description}${deps}`);
|
|
296
|
-
}
|
|
297
|
-
} catch (err) {
|
|
298
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
299
|
-
if (json) {
|
|
300
|
-
console.log(JSON.stringify({ ok: false, error: msg }));
|
|
301
|
-
} else {
|
|
302
|
-
console.error(`Error: ${msg}`);
|
|
303
|
-
}
|
|
304
|
-
process.exitCode = 1;
|
|
305
|
-
}
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
case "install": {
|
|
310
|
-
const skillId = args.find((a) => !a.startsWith("--") && a !== "install");
|
|
311
|
-
if (!skillId) {
|
|
312
|
-
console.error("Usage: vellum skills install <skill-id>");
|
|
313
|
-
process.exit(1);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const overwrite = hasFlag(args, "--overwrite");
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
// Verify skill exists in catalog
|
|
320
|
-
const catalog = await fetchCatalog();
|
|
321
|
-
const entry = catalog.find((s) => s.id === skillId);
|
|
322
|
-
if (!entry) {
|
|
323
|
-
throw new Error(`Skill "${skillId}" not found in the Vellum catalog`);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Fetch SKILL.md from platform
|
|
327
|
-
const content = await fetchSkillContent(skillId);
|
|
328
|
-
|
|
329
|
-
// Install locally
|
|
330
|
-
installSkillLocally(skillId, content, entry, overwrite);
|
|
331
|
-
|
|
332
|
-
if (json) {
|
|
333
|
-
console.log(JSON.stringify({ ok: true, skillId }));
|
|
334
|
-
} else {
|
|
335
|
-
console.log(`Installed skill "${skillId}".`);
|
|
336
|
-
}
|
|
337
|
-
} catch (err) {
|
|
338
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
339
|
-
if (json) {
|
|
340
|
-
console.log(JSON.stringify({ ok: false, error: msg }));
|
|
341
|
-
} else {
|
|
342
|
-
console.error(`Error: ${msg}`);
|
|
343
|
-
}
|
|
344
|
-
process.exitCode = 1;
|
|
345
|
-
}
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
default: {
|
|
350
|
-
console.error(`Unknown skills subcommand: ${subcommand}`);
|
|
351
|
-
printUsage();
|
|
352
|
-
process.exit(1);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|