@swarmvaultai/cli 0.1.25 → 0.1.27
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/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/index.js +258 -57
- package/package.json +10 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SwarmVault
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -78,10 +78,15 @@ Useful flags:
|
|
|
78
78
|
- `--include <glob...>`
|
|
79
79
|
- `--exclude <glob...>`
|
|
80
80
|
- `--max-files <n>`
|
|
81
|
+
- `--include-third-party`
|
|
82
|
+
- `--include-resources`
|
|
83
|
+
- `--include-generated`
|
|
81
84
|
- `--no-gitignore`
|
|
82
85
|
- `--no-include-assets`
|
|
83
86
|
- `--max-asset-size <bytes>`
|
|
84
87
|
|
|
88
|
+
Repo ingest defaults to `first_party` material. The extra `--include-*` flags opt dependency trees, resource bundles, and generated output back in when you actually want them in the vault.
|
|
89
|
+
|
|
85
90
|
### `swarmvault add <url>`
|
|
86
91
|
|
|
87
92
|
Capture supported URLs through a normalized markdown layer before ingesting them into the vault.
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFileSync } from "fs";
|
|
5
|
-
import
|
|
5
|
+
import process2 from "process";
|
|
6
6
|
import {
|
|
7
7
|
acceptApproval,
|
|
8
8
|
addInput,
|
|
@@ -42,45 +42,245 @@ import {
|
|
|
42
42
|
watchVault
|
|
43
43
|
} from "@swarmvaultai/engine";
|
|
44
44
|
import { Command, Option } from "commander";
|
|
45
|
+
|
|
46
|
+
// src/notices.ts
|
|
47
|
+
import { spawn } from "child_process";
|
|
48
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
49
|
+
import os from "os";
|
|
50
|
+
import path from "path";
|
|
51
|
+
var NOTICE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
52
|
+
var NOTICE_TIMEOUT_MS = 2e3;
|
|
53
|
+
var STAR_URL = "https://github.com/swarmclawai/swarmvault";
|
|
54
|
+
var NPM_PACKAGE = "@swarmvaultai/cli";
|
|
55
|
+
var SUPPRESSED_COMMANDS = /* @__PURE__ */ new Set(["graph serve", "mcp", "schedule serve", "watch"]);
|
|
56
|
+
function resolveCliStatePath(env = process.env) {
|
|
57
|
+
const override = env.SWARMVAULT_CLI_STATE_PATH?.trim();
|
|
58
|
+
if (override) {
|
|
59
|
+
return path.resolve(override);
|
|
60
|
+
}
|
|
61
|
+
const homeDir = os.homedir();
|
|
62
|
+
if (!homeDir) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return path.join(homeDir, ".swarmvault", "cli-state.json");
|
|
66
|
+
}
|
|
67
|
+
function shouldEmitCliNotices(options) {
|
|
68
|
+
const env = options.env ?? process.env;
|
|
69
|
+
if (options.json) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
if (env.SWARMVAULT_NO_NOTICES === "1") {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (Boolean(env.CI) && env.CI !== "0") {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (!(options.stdoutIsTTY ?? process.stdout.isTTY) || !(options.stderrIsTTY ?? process.stderr.isTTY)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const commandKey = options.commandPath.join(" ").trim();
|
|
82
|
+
return !SUPPRESSED_COMMANDS.has(commandKey);
|
|
83
|
+
}
|
|
84
|
+
async function collectCliNotices(options) {
|
|
85
|
+
if (!shouldEmitCliNotices(options)) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
const env = options.env ?? process.env;
|
|
89
|
+
const statePath = options.statePath ?? resolveCliStatePath(env);
|
|
90
|
+
if (!statePath) {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
const state = await readNoticeState(statePath);
|
|
94
|
+
const nextState = { ...state };
|
|
95
|
+
const notices = [];
|
|
96
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
97
|
+
const nowMs = now.getTime();
|
|
98
|
+
if (!state.starPromptShown) {
|
|
99
|
+
notices.push(`If SwarmVault is useful, star the repo: ${STAR_URL}`);
|
|
100
|
+
nextState.starPromptShown = true;
|
|
101
|
+
}
|
|
102
|
+
const lastCheckMs = state.lastUpdateCheckAt ? Date.parse(state.lastUpdateCheckAt) : Number.NaN;
|
|
103
|
+
const shouldCheckUpdates = !Number.isFinite(lastCheckMs) || nowMs - lastCheckMs >= NOTICE_CACHE_TTL_MS;
|
|
104
|
+
if (shouldCheckUpdates) {
|
|
105
|
+
const fetchLatestVersion = options.fetchLatestVersion ?? (() => fetchLatestCliVersion(env));
|
|
106
|
+
const latestVersion = await fetchLatestVersion().catch(() => null);
|
|
107
|
+
nextState.lastUpdateCheckAt = now.toISOString();
|
|
108
|
+
if (latestVersion) {
|
|
109
|
+
nextState.lastSeenLatestVersion = latestVersion;
|
|
110
|
+
if (isVersionNewer(latestVersion, options.currentVersion)) {
|
|
111
|
+
notices.unshift(
|
|
112
|
+
`Update available: ${latestVersion} (current ${options.currentVersion}). Upgrade with: npm install -g ${NPM_PACKAGE}@latest`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
await writeNoticeState(statePath, nextState);
|
|
118
|
+
return notices;
|
|
119
|
+
}
|
|
120
|
+
async function readNoticeState(statePath) {
|
|
121
|
+
try {
|
|
122
|
+
const raw = await readFile(statePath, "utf8");
|
|
123
|
+
const parsed = JSON.parse(raw);
|
|
124
|
+
return typeof parsed === "object" && parsed ? parsed : {};
|
|
125
|
+
} catch {
|
|
126
|
+
return {};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function writeNoticeState(statePath, state) {
|
|
130
|
+
try {
|
|
131
|
+
await mkdir(path.dirname(statePath), { recursive: true });
|
|
132
|
+
await writeFile(statePath, `${JSON.stringify(state, null, 2)}
|
|
133
|
+
`, "utf8");
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function fetchLatestCliVersion(env) {
|
|
138
|
+
return await new Promise((resolve) => {
|
|
139
|
+
const child = spawn("npm", ["view", NPM_PACKAGE, "version", "--json"], {
|
|
140
|
+
env: {
|
|
141
|
+
...process.env,
|
|
142
|
+
...env,
|
|
143
|
+
npm_config_audit: "false",
|
|
144
|
+
npm_config_fund: "false",
|
|
145
|
+
npm_config_update_notifier: "false"
|
|
146
|
+
},
|
|
147
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
148
|
+
});
|
|
149
|
+
const chunks = [];
|
|
150
|
+
let settled = false;
|
|
151
|
+
const finish = (value) => {
|
|
152
|
+
if (settled) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
settled = true;
|
|
156
|
+
clearTimeout(timeoutId);
|
|
157
|
+
resolve(value);
|
|
158
|
+
};
|
|
159
|
+
child.stdout.on("data", (chunk) => {
|
|
160
|
+
chunks.push(chunk);
|
|
161
|
+
});
|
|
162
|
+
child.on("error", () => {
|
|
163
|
+
finish(null);
|
|
164
|
+
});
|
|
165
|
+
child.on("exit", (code) => {
|
|
166
|
+
if (code !== 0) {
|
|
167
|
+
finish(null);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
171
|
+
if (!raw) {
|
|
172
|
+
finish(null);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const parsed = JSON.parse(raw);
|
|
177
|
+
finish(typeof parsed === "string" && parsed.trim() ? parsed.trim() : null);
|
|
178
|
+
} catch {
|
|
179
|
+
finish(raw.replace(/^"+|"+$/g, "").trim() || null);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
const timeoutId = setTimeout(() => {
|
|
183
|
+
child.kill("SIGKILL");
|
|
184
|
+
finish(null);
|
|
185
|
+
}, NOTICE_TIMEOUT_MS);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
function isVersionNewer(candidate2, current) {
|
|
189
|
+
return compareVersions(candidate2, current) > 0;
|
|
190
|
+
}
|
|
191
|
+
function compareVersions(left, right) {
|
|
192
|
+
const leftParts = normalizeVersion(left);
|
|
193
|
+
const rightParts = normalizeVersion(right);
|
|
194
|
+
const maxLength = Math.max(leftParts.length, rightParts.length);
|
|
195
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
196
|
+
const leftValue = leftParts[index] ?? 0;
|
|
197
|
+
const rightValue = rightParts[index] ?? 0;
|
|
198
|
+
if (leftValue !== rightValue) {
|
|
199
|
+
return leftValue - rightValue;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
function normalizeVersion(version) {
|
|
205
|
+
const match = version.trim().match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
206
|
+
if (!match) {
|
|
207
|
+
return [0];
|
|
208
|
+
}
|
|
209
|
+
return match.slice(1).map((segment) => Number.parseInt(segment ?? "0", 10) || 0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/index.ts
|
|
45
213
|
var program = new Command();
|
|
46
214
|
var CLI_VERSION = readCliVersion();
|
|
47
215
|
program.name("swarmvault").description("SwarmVault is a local-first LLM wiki compiler with graph outputs and pluggable providers.").version(CLI_VERSION).option("--json", "Emit structured JSON output", false);
|
|
48
216
|
function readCliVersion() {
|
|
49
217
|
try {
|
|
50
218
|
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
51
|
-
return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.1.
|
|
219
|
+
return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.1.27";
|
|
52
220
|
} catch {
|
|
53
|
-
return "0.1.
|
|
221
|
+
return "0.1.27";
|
|
54
222
|
}
|
|
55
223
|
}
|
|
56
224
|
function isJson() {
|
|
57
225
|
return program.opts().json === true;
|
|
58
226
|
}
|
|
59
227
|
function emitJson(data) {
|
|
60
|
-
|
|
228
|
+
process2.stdout.write(`${JSON.stringify(data)}
|
|
61
229
|
`);
|
|
62
230
|
}
|
|
63
231
|
function log(message) {
|
|
64
232
|
if (isJson()) {
|
|
65
|
-
|
|
233
|
+
process2.stderr.write(`${message}
|
|
66
234
|
`);
|
|
67
235
|
} else {
|
|
68
|
-
|
|
236
|
+
process2.stdout.write(`${message}
|
|
69
237
|
`);
|
|
70
238
|
}
|
|
71
239
|
}
|
|
240
|
+
function emitNotice(message) {
|
|
241
|
+
process2.stderr.write(`[swarmvault] ${message}
|
|
242
|
+
`);
|
|
243
|
+
}
|
|
244
|
+
function getCommandPath(command) {
|
|
245
|
+
const names = [];
|
|
246
|
+
let current = command;
|
|
247
|
+
while (current) {
|
|
248
|
+
const name = current.name();
|
|
249
|
+
if (name && name !== "swarmvault") {
|
|
250
|
+
names.unshift(name);
|
|
251
|
+
}
|
|
252
|
+
current = current.parent ?? null;
|
|
253
|
+
}
|
|
254
|
+
return names;
|
|
255
|
+
}
|
|
256
|
+
program.hook("postAction", async (_thisCommand, actionCommand) => {
|
|
257
|
+
const notices = await collectCliNotices({
|
|
258
|
+
commandPath: getCommandPath(actionCommand),
|
|
259
|
+
currentVersion: CLI_VERSION,
|
|
260
|
+
json: isJson()
|
|
261
|
+
});
|
|
262
|
+
for (const notice of notices) {
|
|
263
|
+
emitNotice(notice);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
72
266
|
program.command("init").description("Initialize a SwarmVault workspace in the current directory.").option("--obsidian", "Generate a minimal .obsidian workspace alongside the vault", false).action(async (options) => {
|
|
73
|
-
await initVault(
|
|
267
|
+
await initVault(process2.cwd(), { obsidian: options.obsidian ?? false });
|
|
74
268
|
if (isJson()) {
|
|
75
|
-
emitJson({ status: "initialized", rootDir:
|
|
269
|
+
emitJson({ status: "initialized", rootDir: process2.cwd(), obsidian: options.obsidian ?? false });
|
|
76
270
|
} else {
|
|
77
271
|
log("Initialized SwarmVault workspace.");
|
|
78
272
|
}
|
|
79
273
|
});
|
|
80
|
-
program.command("ingest").description("Ingest a local file path, directory path, or URL into the raw SwarmVault workspace.").argument("<input>", "Local file path, directory path, or URL").option("--include-assets", "Download remote image assets when ingesting URLs", true).option("--no-include-assets", "Skip downloading remote image assets when ingesting URLs").option("--max-asset-size <bytes>", "Maximum number of bytes to fetch for a single remote image asset").option("--repo-root <path>", "Override the detected repo root when ingesting a directory").option("--include <glob...>", "Only ingest files matching one or more glob patterns").option("--exclude <glob...>", "Skip files matching one or more glob patterns").option("--max-files <n>", "Maximum number of files to ingest from a directory").option("--no-gitignore", "Ignore .gitignore rules when ingesting a directory").action(
|
|
274
|
+
program.command("ingest").description("Ingest a local file path, directory path, or URL into the raw SwarmVault workspace.").argument("<input>", "Local file path, directory path, or URL").option("--include-assets", "Download remote image assets when ingesting URLs", true).option("--no-include-assets", "Skip downloading remote image assets when ingesting URLs").option("--max-asset-size <bytes>", "Maximum number of bytes to fetch for a single remote image asset").option("--repo-root <path>", "Override the detected repo root when ingesting a directory").option("--include <glob...>", "Only ingest files matching one or more glob patterns").option("--exclude <glob...>", "Skip files matching one or more glob patterns").option("--max-files <n>", "Maximum number of files to ingest from a directory").option("--include-third-party", "Also ingest repo files classified as third-party", false).option("--include-resources", "Also ingest repo files classified as resources", false).option("--include-generated", "Also ingest repo files classified as generated output", false).option("--no-gitignore", "Ignore .gitignore rules when ingesting a directory").action(
|
|
81
275
|
async (input, options) => {
|
|
82
276
|
const maxAssetSize = typeof options.maxAssetSize === "string" && options.maxAssetSize.trim() ? Number.parseInt(options.maxAssetSize, 10) : void 0;
|
|
83
277
|
const maxFiles = typeof options.maxFiles === "string" && options.maxFiles.trim() ? Number.parseInt(options.maxFiles, 10) : void 0;
|
|
278
|
+
const extractClasses = [
|
|
279
|
+
"first_party",
|
|
280
|
+
...options.includeThirdParty ? ["third_party"] : [],
|
|
281
|
+
...options.includeResources ? ["resource"] : [],
|
|
282
|
+
...options.includeGenerated ? ["generated"] : []
|
|
283
|
+
];
|
|
84
284
|
const commonOptions = {
|
|
85
285
|
includeAssets: options.includeAssets,
|
|
86
286
|
maxAssetSize: Number.isFinite(maxAssetSize) ? maxAssetSize : void 0,
|
|
@@ -88,10 +288,11 @@ program.command("ingest").description("Ingest a local file path, directory path,
|
|
|
88
288
|
include: options.include,
|
|
89
289
|
exclude: options.exclude,
|
|
90
290
|
maxFiles: Number.isFinite(maxFiles) ? maxFiles : void 0,
|
|
91
|
-
gitignore: options.gitignore
|
|
291
|
+
gitignore: options.gitignore,
|
|
292
|
+
extractClasses
|
|
92
293
|
};
|
|
93
294
|
const directoryResult = !/^https?:\/\//i.test(input) ? await import("fs/promises").then(
|
|
94
|
-
(fs) => fs.stat(input).then((stat) => stat.isDirectory() ? ingestDirectory(
|
|
295
|
+
(fs) => fs.stat(input).then((stat) => stat.isDirectory() ? ingestDirectory(process2.cwd(), input, commonOptions) : null).catch(() => null)
|
|
95
296
|
) : null;
|
|
96
297
|
if (directoryResult) {
|
|
97
298
|
if (isJson()) {
|
|
@@ -103,7 +304,7 @@ program.command("ingest").description("Ingest a local file path, directory path,
|
|
|
103
304
|
}
|
|
104
305
|
return;
|
|
105
306
|
}
|
|
106
|
-
const manifest = await ingestInput(
|
|
307
|
+
const manifest = await ingestInput(process2.cwd(), input, commonOptions);
|
|
107
308
|
if (isJson()) {
|
|
108
309
|
emitJson(manifest);
|
|
109
310
|
} else {
|
|
@@ -112,7 +313,7 @@ program.command("ingest").description("Ingest a local file path, directory path,
|
|
|
112
313
|
}
|
|
113
314
|
);
|
|
114
315
|
program.command("add").description("Capture supported URLs into normalized markdown before ingesting them.").argument("<input>", "Supported URL or bare arXiv id").option("--author <name>", "Human author or curator for this capture").option("--contributor <name>", "Additional contributor metadata for this capture").action(async (input, options) => {
|
|
115
|
-
const result = await addInput(
|
|
316
|
+
const result = await addInput(process2.cwd(), input, {
|
|
116
317
|
author: options.author,
|
|
117
318
|
contributor: options.contributor
|
|
118
319
|
});
|
|
@@ -124,7 +325,7 @@ program.command("add").description("Capture supported URLs into normalized markd
|
|
|
124
325
|
});
|
|
125
326
|
var inbox = program.command("inbox").description("Inbox and capture workflows.");
|
|
126
327
|
inbox.command("import").description("Import supported files from the configured inbox directory.").argument("[dir]", "Optional inbox directory override").action(async (dir) => {
|
|
127
|
-
const result = await importInbox(
|
|
328
|
+
const result = await importInbox(process2.cwd(), dir);
|
|
128
329
|
if (isJson()) {
|
|
129
330
|
emitJson(result);
|
|
130
331
|
} else {
|
|
@@ -134,7 +335,7 @@ inbox.command("import").description("Import supported files from the configured
|
|
|
134
335
|
}
|
|
135
336
|
});
|
|
136
337
|
program.command("compile").description("Compile manifests into wiki pages, graph JSON, and search index.").option("--approve", "Stage a review bundle without applying active page changes", false).action(async (options) => {
|
|
137
|
-
const result = await compileVault(
|
|
338
|
+
const result = await compileVault(process2.cwd(), { approve: options.approve ?? false });
|
|
138
339
|
if (isJson()) {
|
|
139
340
|
emitJson(result);
|
|
140
341
|
} else {
|
|
@@ -148,7 +349,7 @@ program.command("compile").description("Compile manifests into wiki pages, graph
|
|
|
148
349
|
program.command("query").description("Query the compiled SwarmVault wiki.").argument("<question>", "Question to ask SwarmVault").option("--no-save", "Do not persist the answer to wiki/outputs").addOption(
|
|
149
350
|
new Option("--format <format>", "Output format").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
|
|
150
351
|
).action(async (question, options) => {
|
|
151
|
-
const result = await queryVault(
|
|
352
|
+
const result = await queryVault(process2.cwd(), {
|
|
152
353
|
question,
|
|
153
354
|
save: options.save ?? true,
|
|
154
355
|
format: options.format
|
|
@@ -166,7 +367,7 @@ program.command("explore").description("Run a save-first multi-step exploration
|
|
|
166
367
|
new Option("--format <format>", "Output format for step pages").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
|
|
167
368
|
).action(async (question, options) => {
|
|
168
369
|
const stepCount = Number.parseInt(options.steps ?? "3", 10);
|
|
169
|
-
const result = await exploreVault(
|
|
370
|
+
const result = await exploreVault(process2.cwd(), {
|
|
170
371
|
question,
|
|
171
372
|
steps: Number.isFinite(stepCount) ? stepCount : 3,
|
|
172
373
|
format: options.format
|
|
@@ -179,7 +380,7 @@ program.command("explore").description("Run a save-first multi-step exploration
|
|
|
179
380
|
}
|
|
180
381
|
});
|
|
181
382
|
program.command("benchmark").description("Measure graph-guided context reduction against a naive full-corpus read.").option("--question <text...>", "Optional custom benchmark question(s)").action(async (options) => {
|
|
182
|
-
const result = await benchmarkVault(
|
|
383
|
+
const result = await benchmarkVault(process2.cwd(), {
|
|
183
384
|
questions: options.question
|
|
184
385
|
});
|
|
185
386
|
if (isJson()) {
|
|
@@ -191,7 +392,7 @@ program.command("benchmark").description("Measure graph-guided context reduction
|
|
|
191
392
|
}
|
|
192
393
|
});
|
|
193
394
|
program.command("lint").description("Run anti-drift and wiki-health checks.").option("--deep", "Run LLM-powered advisory lint", false).option("--web", "Augment deep lint with configured web search", false).action(async (options) => {
|
|
194
|
-
const findings = await lintVault(
|
|
395
|
+
const findings = await lintVault(process2.cwd(), {
|
|
195
396
|
deep: options.deep ?? false,
|
|
196
397
|
web: options.web ?? false
|
|
197
398
|
});
|
|
@@ -210,15 +411,15 @@ program.command("lint").description("Run anti-drift and wiki-health checks.").op
|
|
|
210
411
|
var graph = program.command("graph").description("Graph-related commands.");
|
|
211
412
|
graph.command("serve").description("Serve the local graph viewer.").option("--port <port>", "Port override").action(async (options) => {
|
|
212
413
|
const port = options.port ? Number.parseInt(options.port, 10) : void 0;
|
|
213
|
-
const server = await startGraphServer(
|
|
414
|
+
const server = await startGraphServer(process2.cwd(), port);
|
|
214
415
|
if (isJson()) {
|
|
215
416
|
emitJson({ port: server.port, url: `http://localhost:${server.port}` });
|
|
216
417
|
} else {
|
|
217
418
|
log(`Graph viewer running at http://localhost:${server.port}`);
|
|
218
419
|
}
|
|
219
|
-
|
|
420
|
+
process2.on("SIGINT", async () => {
|
|
220
421
|
await server.close();
|
|
221
|
-
|
|
422
|
+
process2.exit(0);
|
|
222
423
|
});
|
|
223
424
|
});
|
|
224
425
|
graph.command("export").description("Export the graph as HTML, SVG, GraphML, or Cypher.").option("--html <output>", "Output HTML file path").option("--svg <output>", "Output SVG file path").option("--graphml <output>", "Output GraphML file path").option("--cypher <output>", "Output Cypher file path").action(async (options) => {
|
|
@@ -232,7 +433,7 @@ graph.command("export").description("Export the graph as HTML, SVG, GraphML, or
|
|
|
232
433
|
throw new Error("Pass exactly one of --html, --svg, --graphml, or --cypher.");
|
|
233
434
|
}
|
|
234
435
|
const target = targets[0];
|
|
235
|
-
const outputPath = target.format === "html" ? await exportGraphHtml(
|
|
436
|
+
const outputPath = target.format === "html" ? await exportGraphHtml(process2.cwd(), target.outputPath) : (await exportGraphFormat(process2.cwd(), target.format, target.outputPath)).outputPath;
|
|
236
437
|
if (isJson()) {
|
|
237
438
|
emitJson({ format: target.format, outputPath });
|
|
238
439
|
} else {
|
|
@@ -241,7 +442,7 @@ graph.command("export").description("Export the graph as HTML, SVG, GraphML, or
|
|
|
241
442
|
});
|
|
242
443
|
graph.command("query").description("Traverse the compiled graph deterministically from local search seeds.").argument("<question>", "Question or graph search seed").option("--dfs", "Prefer a depth-first traversal instead of breadth-first", false).option("--budget <n>", "Maximum number of graph nodes to summarize").action(async (question, options) => {
|
|
243
444
|
const budget = options.budget ? Number.parseInt(options.budget, 10) : void 0;
|
|
244
|
-
const result = await queryGraphVault(
|
|
445
|
+
const result = await queryGraphVault(process2.cwd(), question, {
|
|
245
446
|
traversal: options.dfs ? "dfs" : "bfs",
|
|
246
447
|
budget: Number.isFinite(budget) ? budget : void 0
|
|
247
448
|
});
|
|
@@ -252,7 +453,7 @@ graph.command("query").description("Traverse the compiled graph deterministicall
|
|
|
252
453
|
log(result.summary);
|
|
253
454
|
});
|
|
254
455
|
graph.command("path").description("Find the shortest graph path between two nodes or pages.").argument("<from>", "Source node/page label or id").argument("<to>", "Target node/page label or id").action(async (from, to) => {
|
|
255
|
-
const result = await pathGraphVault(
|
|
456
|
+
const result = await pathGraphVault(process2.cwd(), from, to);
|
|
256
457
|
if (isJson()) {
|
|
257
458
|
emitJson(result);
|
|
258
459
|
return;
|
|
@@ -260,7 +461,7 @@ graph.command("path").description("Find the shortest graph path between two node
|
|
|
260
461
|
log(result.summary);
|
|
261
462
|
});
|
|
262
463
|
graph.command("explain").description("Explain a graph node, its page, community, and neighbors.").argument("<target>", "Node/page label or id").action(async (target) => {
|
|
263
|
-
const result = await explainGraphVault(
|
|
464
|
+
const result = await explainGraphVault(process2.cwd(), target);
|
|
264
465
|
if (isJson()) {
|
|
265
466
|
emitJson(result);
|
|
266
467
|
return;
|
|
@@ -269,7 +470,7 @@ graph.command("explain").description("Explain a graph node, its page, community,
|
|
|
269
470
|
});
|
|
270
471
|
graph.command("god-nodes").description("List the highest-connectivity non-source graph nodes.").option("--limit <n>", "Maximum number of nodes to return", "10").action(async (options) => {
|
|
271
472
|
const limit = Number.parseInt(options.limit ?? "10", 10);
|
|
272
|
-
const result = await listGodNodes(
|
|
473
|
+
const result = await listGodNodes(process2.cwd(), Number.isFinite(limit) ? limit : 10);
|
|
273
474
|
if (isJson()) {
|
|
274
475
|
emitJson(result);
|
|
275
476
|
return;
|
|
@@ -280,7 +481,7 @@ graph.command("god-nodes").description("List the highest-connectivity non-source
|
|
|
280
481
|
});
|
|
281
482
|
var review = program.command("review").description("Review staged compile approval bundles.");
|
|
282
483
|
review.command("list").description("List staged approval bundles and their resolution status.").action(async () => {
|
|
283
|
-
const approvals = await listApprovals(
|
|
484
|
+
const approvals = await listApprovals(process2.cwd());
|
|
284
485
|
if (isJson()) {
|
|
285
486
|
emitJson(approvals);
|
|
286
487
|
return;
|
|
@@ -296,7 +497,7 @@ review.command("list").description("List staged approval bundles and their resol
|
|
|
296
497
|
}
|
|
297
498
|
});
|
|
298
499
|
review.command("show").description("Show the entries inside a staged approval bundle.").argument("<approvalId>", "Approval bundle identifier").action(async (approvalId) => {
|
|
299
|
-
const approval = await readApproval(
|
|
500
|
+
const approval = await readApproval(process2.cwd(), approvalId);
|
|
300
501
|
if (isJson()) {
|
|
301
502
|
emitJson(approval);
|
|
302
503
|
return;
|
|
@@ -307,7 +508,7 @@ review.command("show").description("Show the entries inside a staged approval bu
|
|
|
307
508
|
}
|
|
308
509
|
});
|
|
309
510
|
review.command("accept").description("Accept all pending entries, or selected entries, from a staged approval bundle.").argument("<approvalId>", "Approval bundle identifier").argument("[targets...]", "Optional page ids or paths to accept").action(async (approvalId, targets) => {
|
|
310
|
-
const result = await acceptApproval(
|
|
511
|
+
const result = await acceptApproval(process2.cwd(), approvalId, targets);
|
|
311
512
|
if (isJson()) {
|
|
312
513
|
emitJson(result);
|
|
313
514
|
} else {
|
|
@@ -315,7 +516,7 @@ review.command("accept").description("Accept all pending entries, or selected en
|
|
|
315
516
|
}
|
|
316
517
|
});
|
|
317
518
|
review.command("reject").description("Reject all pending entries, or selected entries, from a staged approval bundle.").argument("<approvalId>", "Approval bundle identifier").argument("[targets...]", "Optional page ids or paths to reject").action(async (approvalId, targets) => {
|
|
318
|
-
const result = await rejectApproval(
|
|
519
|
+
const result = await rejectApproval(process2.cwd(), approvalId, targets);
|
|
319
520
|
if (isJson()) {
|
|
320
521
|
emitJson(result);
|
|
321
522
|
} else {
|
|
@@ -324,7 +525,7 @@ review.command("reject").description("Reject all pending entries, or selected en
|
|
|
324
525
|
});
|
|
325
526
|
var candidate = program.command("candidate").description("Candidate page workflows.");
|
|
326
527
|
candidate.command("list").description("List staged concept and entity candidates.").action(async () => {
|
|
327
|
-
const candidates = await listCandidates(
|
|
528
|
+
const candidates = await listCandidates(process2.cwd());
|
|
328
529
|
if (isJson()) {
|
|
329
530
|
emitJson(candidates);
|
|
330
531
|
return;
|
|
@@ -338,7 +539,7 @@ candidate.command("list").description("List staged concept and entity candidates
|
|
|
338
539
|
}
|
|
339
540
|
});
|
|
340
541
|
candidate.command("promote").description("Promote a candidate into its active concept or entity path.").argument("<target>", "Candidate page id or path").action(async (target) => {
|
|
341
|
-
const result = await promoteCandidate(
|
|
542
|
+
const result = await promoteCandidate(process2.cwd(), target);
|
|
342
543
|
if (isJson()) {
|
|
343
544
|
emitJson(result);
|
|
344
545
|
} else {
|
|
@@ -346,7 +547,7 @@ candidate.command("promote").description("Promote a candidate into its active co
|
|
|
346
547
|
}
|
|
347
548
|
});
|
|
348
549
|
candidate.command("archive").description("Archive a candidate by removing it from the active candidate set.").argument("<target>", "Candidate page id or path").action(async (target) => {
|
|
349
|
-
const result = await archiveCandidate(
|
|
550
|
+
const result = await archiveCandidate(process2.cwd(), target);
|
|
350
551
|
if (isJson()) {
|
|
351
552
|
emitJson(result);
|
|
352
553
|
} else {
|
|
@@ -356,7 +557,7 @@ candidate.command("archive").description("Archive a candidate by removing it fro
|
|
|
356
557
|
var watch = program.command("watch").description("Watch the inbox directory and optionally tracked repos, or run one refresh cycle immediately.").option("--lint", "Run lint after each compile cycle", false).option("--repo", "Also refresh tracked repo sources and watch their repo roots", false).option("--once", "Run one import/refresh cycle immediately instead of starting a watcher", false).option("--debounce <ms>", "Debounce window in milliseconds", "900").action(async (options) => {
|
|
357
558
|
const debounceMs = Number.parseInt(options.debounce ?? "900", 10);
|
|
358
559
|
if (options.once) {
|
|
359
|
-
const result = await runWatchCycle(
|
|
560
|
+
const result = await runWatchCycle(process2.cwd(), {
|
|
360
561
|
lint: options.lint ?? false,
|
|
361
562
|
repo: options.repo ?? false,
|
|
362
563
|
debounceMs: Number.isFinite(debounceMs) ? debounceMs : 900
|
|
@@ -370,8 +571,8 @@ var watch = program.command("watch").description("Watch the inbox directory and
|
|
|
370
571
|
}
|
|
371
572
|
return;
|
|
372
573
|
}
|
|
373
|
-
const { paths } = await loadVaultConfig(
|
|
374
|
-
const controller = await watchVault(
|
|
574
|
+
const { paths } = await loadVaultConfig(process2.cwd());
|
|
575
|
+
const controller = await watchVault(process2.cwd(), {
|
|
375
576
|
lint: options.lint ?? false,
|
|
376
577
|
repo: options.repo ?? false,
|
|
377
578
|
debounceMs: Number.isFinite(debounceMs) ? debounceMs : 900
|
|
@@ -381,13 +582,13 @@ var watch = program.command("watch").description("Watch the inbox directory and
|
|
|
381
582
|
} else {
|
|
382
583
|
log(`Watching inbox${options.repo ? " and tracked repos" : ""} for changes. Press Ctrl+C to stop.`);
|
|
383
584
|
}
|
|
384
|
-
|
|
585
|
+
process2.on("SIGINT", async () => {
|
|
385
586
|
await controller.close();
|
|
386
|
-
|
|
587
|
+
process2.exit(0);
|
|
387
588
|
});
|
|
388
589
|
});
|
|
389
590
|
watch.command("status").description("Show the latest watch run plus pending semantic refresh entries.").action(async () => {
|
|
390
|
-
const result = await getWatchStatus(
|
|
591
|
+
const result = await getWatchStatus(process2.cwd());
|
|
391
592
|
if (isJson()) {
|
|
392
593
|
emitJson(result);
|
|
393
594
|
return;
|
|
@@ -399,7 +600,7 @@ watch.command("status").description("Show the latest watch run plus pending sema
|
|
|
399
600
|
}
|
|
400
601
|
});
|
|
401
602
|
program.command("watch-status").description("Show the latest watch run plus pending semantic refresh entries.").action(async () => {
|
|
402
|
-
const result = await getWatchStatus(
|
|
603
|
+
const result = await getWatchStatus(process2.cwd());
|
|
403
604
|
if (isJson()) {
|
|
404
605
|
emitJson(result);
|
|
405
606
|
return;
|
|
@@ -412,7 +613,7 @@ program.command("watch-status").description("Show the latest watch run plus pend
|
|
|
412
613
|
});
|
|
413
614
|
var hook = program.command("hook").description("Install local git hooks that keep tracked repos and the vault in sync.");
|
|
414
615
|
hook.command("install").description("Install post-commit and post-checkout hooks for the nearest git repository.").action(async () => {
|
|
415
|
-
const status = await installGitHooks(
|
|
616
|
+
const status = await installGitHooks(process2.cwd());
|
|
416
617
|
if (isJson()) {
|
|
417
618
|
emitJson(status);
|
|
418
619
|
return;
|
|
@@ -420,7 +621,7 @@ hook.command("install").description("Install post-commit and post-checkout hooks
|
|
|
420
621
|
log(`Installed hooks in ${status.repoRoot}`);
|
|
421
622
|
});
|
|
422
623
|
hook.command("uninstall").description("Remove the SwarmVault-managed git hook blocks from the nearest git repository.").action(async () => {
|
|
423
|
-
const status = await uninstallGitHooks(
|
|
624
|
+
const status = await uninstallGitHooks(process2.cwd());
|
|
424
625
|
if (isJson()) {
|
|
425
626
|
emitJson(status);
|
|
426
627
|
return;
|
|
@@ -428,7 +629,7 @@ hook.command("uninstall").description("Remove the SwarmVault-managed git hook bl
|
|
|
428
629
|
log(`Removed SwarmVault hook blocks from ${status.repoRoot ?? "the current workspace"}`);
|
|
429
630
|
});
|
|
430
631
|
hook.command("status").description("Show whether SwarmVault-managed git hooks are installed.").action(async () => {
|
|
431
|
-
const status = await getGitHookStatus(
|
|
632
|
+
const status = await getGitHookStatus(process2.cwd());
|
|
432
633
|
if (isJson()) {
|
|
433
634
|
emitJson(status);
|
|
434
635
|
return;
|
|
@@ -443,7 +644,7 @@ hook.command("status").description("Show whether SwarmVault-managed git hooks ar
|
|
|
443
644
|
});
|
|
444
645
|
var schedule = program.command("schedule").description("Run scheduled vault maintenance jobs.");
|
|
445
646
|
schedule.command("list").description("List configured schedule jobs and their next run state.").action(async () => {
|
|
446
|
-
const schedules = await listSchedules(
|
|
647
|
+
const schedules = await listSchedules(process2.cwd());
|
|
447
648
|
if (isJson()) {
|
|
448
649
|
emitJson(schedules);
|
|
449
650
|
return;
|
|
@@ -459,7 +660,7 @@ schedule.command("list").description("List configured schedule jobs and their ne
|
|
|
459
660
|
}
|
|
460
661
|
});
|
|
461
662
|
schedule.command("run").description("Run one configured schedule job immediately.").argument("<jobId>", "Schedule identifier").action(async (jobId) => {
|
|
462
|
-
const result = await runSchedule(
|
|
663
|
+
const result = await runSchedule(process2.cwd(), jobId);
|
|
463
664
|
if (isJson()) {
|
|
464
665
|
emitJson(result);
|
|
465
666
|
return;
|
|
@@ -470,46 +671,46 @@ schedule.command("run").description("Run one configured schedule job immediately
|
|
|
470
671
|
});
|
|
471
672
|
schedule.command("serve").description("Run the local schedule loop.").option("--poll <ms>", "Polling interval in milliseconds", "30000").action(async (options) => {
|
|
472
673
|
const pollMs = Number.parseInt(options.poll ?? "30000", 10);
|
|
473
|
-
const controller = await serveSchedules(
|
|
674
|
+
const controller = await serveSchedules(process2.cwd(), Number.isFinite(pollMs) ? pollMs : 3e4);
|
|
474
675
|
if (isJson()) {
|
|
475
676
|
emitJson({ status: "serving", pollMs: Number.isFinite(pollMs) ? pollMs : 3e4 });
|
|
476
677
|
} else {
|
|
477
678
|
log("Serving schedules. Press Ctrl+C to stop.");
|
|
478
679
|
}
|
|
479
|
-
|
|
680
|
+
process2.on("SIGINT", async () => {
|
|
480
681
|
await controller.close();
|
|
481
|
-
|
|
682
|
+
process2.exit(0);
|
|
482
683
|
});
|
|
483
684
|
});
|
|
484
685
|
program.command("mcp").description("Run SwarmVault as a local MCP server over stdio.").action(async () => {
|
|
485
686
|
if (isJson()) {
|
|
486
|
-
|
|
687
|
+
process2.stderr.write(`${JSON.stringify({ status: "running", transport: "stdio" })}
|
|
487
688
|
`);
|
|
488
689
|
}
|
|
489
|
-
const controller = await startMcpServer(
|
|
490
|
-
|
|
690
|
+
const controller = await startMcpServer(process2.cwd());
|
|
691
|
+
process2.on("SIGINT", async () => {
|
|
491
692
|
await controller.close();
|
|
492
|
-
|
|
693
|
+
process2.exit(0);
|
|
493
694
|
});
|
|
494
695
|
});
|
|
495
696
|
program.command("install").description("Install SwarmVault instructions for an agent in the current project.").requiredOption("--agent <agent>", "codex, claude, cursor, goose, pi, gemini, or opencode").option("--hook", "Also install the recommended Claude pre-search hook when agent=claude", false).action(async (options) => {
|
|
496
697
|
if (options.hook && options.agent !== "claude") {
|
|
497
698
|
throw new Error("--hook is only supported for --agent claude");
|
|
498
699
|
}
|
|
499
|
-
const target = await installAgent(
|
|
700
|
+
const target = await installAgent(process2.cwd(), options.agent, { claudeHook: options.hook ?? false });
|
|
500
701
|
if (isJson()) {
|
|
501
702
|
emitJson({ agent: options.agent, target, hook: options.hook ?? false });
|
|
502
703
|
} else {
|
|
503
704
|
log(`Installed rules into ${target}`);
|
|
504
705
|
}
|
|
505
706
|
});
|
|
506
|
-
program.parseAsync(
|
|
707
|
+
program.parseAsync(process2.argv).catch((error) => {
|
|
507
708
|
const message = error instanceof Error ? error.message : String(error);
|
|
508
709
|
if (isJson()) {
|
|
509
710
|
emitJson({ error: message });
|
|
510
711
|
} else {
|
|
511
|
-
|
|
712
|
+
process2.stderr.write(`${message}
|
|
512
713
|
`);
|
|
513
714
|
}
|
|
514
|
-
|
|
715
|
+
process2.exit(1);
|
|
515
716
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmvaultai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"description": "Global CLI for SwarmVault.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,17 +37,18 @@
|
|
|
37
37
|
"engines": {
|
|
38
38
|
"node": ">=24.0.0"
|
|
39
39
|
},
|
|
40
|
-
"scripts": {
|
|
41
|
-
"build": "tsup src/index.ts --format esm --dts",
|
|
42
|
-
"test": "node -e \"process.exit(0)\"",
|
|
43
|
-
"typecheck": "tsc --noEmit"
|
|
44
|
-
},
|
|
45
40
|
"dependencies": {
|
|
46
|
-
"@swarmvaultai/engine": "0.1.
|
|
41
|
+
"@swarmvaultai/engine": "0.1.27",
|
|
47
42
|
"commander": "^14.0.1"
|
|
48
43
|
},
|
|
49
44
|
"devDependencies": {
|
|
50
45
|
"@types/node": "^24.6.0",
|
|
51
|
-
"tsup": "^8.5.0"
|
|
46
|
+
"tsup": "^8.5.0",
|
|
47
|
+
"vitest": "^3.2.4"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsup src/index.ts --format esm --dts",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"typecheck": "tsc --noEmit"
|
|
52
53
|
}
|
|
53
|
-
}
|
|
54
|
+
}
|