facult 1.3.0 → 2.1.0
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 +198 -178
- package/bin/facult.cjs +1 -268
- package/bin/fclt.cjs +264 -0
- package/package.json +6 -3
- package/src/ai.ts +23 -23
- package/src/audit/index.ts +11 -11
- package/src/audit/tui.ts +6 -6
- package/src/autosync.ts +11 -8
- package/src/consolidate.ts +3 -3
- package/src/doctor.ts +5 -5
- package/src/enable-disable.ts +7 -7
- package/src/graph-query.ts +1 -1
- package/src/index-builder.ts +2 -2
- package/src/index.ts +52 -52
- package/src/manage.ts +9 -9
- package/src/migrate.ts +3 -3
- package/src/paths.ts +4 -4
- package/src/query.ts +4 -4
- package/src/remote-source-policy.ts +8 -8
- package/src/remote.ts +25 -25
- package/src/scan.ts +8 -8
- package/src/schema.ts +1 -1
- package/src/self-update.ts +40 -16
- package/src/snippets-cli.ts +6 -6
- package/src/trust.ts +8 -8
- package/src/tui.ts +1 -1
package/src/scan.ts
CHANGED
|
@@ -512,7 +512,7 @@ function defaultSourceSpecs(
|
|
|
512
512
|
const specs: SourceSpec[] = [
|
|
513
513
|
{
|
|
514
514
|
id: "facult",
|
|
515
|
-
name: "
|
|
515
|
+
name: "fclt (canonical)",
|
|
516
516
|
candidates: [canonicalRoot],
|
|
517
517
|
skillDirs: [join(canonicalRoot, "skills")],
|
|
518
518
|
configFiles: [
|
|
@@ -1677,7 +1677,7 @@ function printSourceAssets(assets: SourceResult["assets"]) {
|
|
|
1677
1677
|
}
|
|
1678
1678
|
|
|
1679
1679
|
function printHuman(res: ScanResult) {
|
|
1680
|
-
console.log(`
|
|
1680
|
+
console.log(`fclt scan — ${res.scannedAt}`);
|
|
1681
1681
|
console.log("");
|
|
1682
1682
|
|
|
1683
1683
|
const foundSources = res.sources.filter((s) => s.found);
|
|
@@ -1976,7 +1976,7 @@ async function computeAssetContentDuplicates(
|
|
|
1976
1976
|
function _printSkillsTable(res: ScanResult) {
|
|
1977
1977
|
const all = computeSkillOccurrences(res);
|
|
1978
1978
|
|
|
1979
|
-
console.log(`
|
|
1979
|
+
console.log(`fclt scan — ${res.scannedAt}`);
|
|
1980
1980
|
console.log("Skills (deduplicated by SKILL.md parent directory name):");
|
|
1981
1981
|
|
|
1982
1982
|
if (all.length === 0) {
|
|
@@ -2009,7 +2009,7 @@ function _printSkillsTable(res: ScanResult) {
|
|
|
2009
2009
|
function printSkillDuplicatesTable(res: ScanResult) {
|
|
2010
2010
|
const all = computeSkillOccurrences(res).filter((d) => d.count > 1);
|
|
2011
2011
|
|
|
2012
|
-
console.log(`
|
|
2012
|
+
console.log(`fclt scan — ${res.scannedAt}`);
|
|
2013
2013
|
console.log("Duplicate skills (same skill name appears in multiple places):");
|
|
2014
2014
|
|
|
2015
2015
|
if (all.length === 0) {
|
|
@@ -2232,14 +2232,14 @@ export async function writeState(res: ScanResult) {
|
|
|
2232
2232
|
}
|
|
2233
2233
|
|
|
2234
2234
|
function printScanHelp() {
|
|
2235
|
-
console.log(`
|
|
2235
|
+
console.log(`fclt scan — inventory local agent configs across tools
|
|
2236
2236
|
|
|
2237
2237
|
Usage:
|
|
2238
|
-
|
|
2239
|
-
|
|
2238
|
+
fclt scan [--json] [--show-duplicates] [--tui]
|
|
2239
|
+
fclt scan --from <path> [--from <path> ...]
|
|
2240
2240
|
|
|
2241
2241
|
Notes:
|
|
2242
|
-
- If no --from roots are provided and no scanFrom is configured,
|
|
2242
|
+
- If no --from roots are provided and no scanFrom is configured, fclt defaults to scanning ~.
|
|
2243
2243
|
|
|
2244
2244
|
Options:
|
|
2245
2245
|
--json Print full JSON (ScanResult)
|
package/src/schema.ts
CHANGED
|
@@ -3,7 +3,7 @@ export interface Provenance {
|
|
|
3
3
|
sourceId: string;
|
|
4
4
|
/** Path to the config file this item came from (if applicable). */
|
|
5
5
|
sourcePath: string;
|
|
6
|
-
/** ISO timestamp when
|
|
6
|
+
/** ISO timestamp when fclt imported/consolidated this item. */
|
|
7
7
|
importedAt: string;
|
|
8
8
|
/** Optional source file mtime at import time (ISO). */
|
|
9
9
|
sourceModifiedAt?: string;
|
package/src/self-update.ts
CHANGED
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
|
|
9
9
|
const REPO_OWNER = "hack-dance";
|
|
10
10
|
const REPO_NAME = "facult";
|
|
11
|
+
const PACKAGE_NAME = "facult";
|
|
11
12
|
const DOWNLOAD_RETRIES = 12;
|
|
12
13
|
const DOWNLOAD_RETRY_DELAY_MS = 5000;
|
|
14
|
+
const CLI_BASENAME_PATTERN = /^(fclt|facult)(\.exe)?$/;
|
|
13
15
|
|
|
14
16
|
type InstallMethod =
|
|
15
17
|
| "script-dev"
|
|
@@ -40,11 +42,11 @@ interface DetectInstallMethodContext {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
function printHelp() {
|
|
43
|
-
console.log(`
|
|
45
|
+
console.log(`fclt self-update — update fclt itself based on install method
|
|
44
46
|
|
|
45
47
|
Usage:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
fclt self-update [--version <x.y.z|latest>] [--dry-run]
|
|
49
|
+
fclt update --self [--version <x.y.z|latest>] [--dry-run]
|
|
48
50
|
|
|
49
51
|
Options:
|
|
50
52
|
--version Target version (defaults to latest)
|
|
@@ -131,7 +133,8 @@ export function detectInstallMethod(
|
|
|
131
133
|
if (
|
|
132
134
|
facultBins.some(
|
|
133
135
|
(facultBin) =>
|
|
134
|
-
exec.startsWith(facultBin + sep) &&
|
|
136
|
+
exec.startsWith(facultBin + sep) &&
|
|
137
|
+
CLI_BASENAME_PATTERN.test(basename(exec))
|
|
135
138
|
)
|
|
136
139
|
) {
|
|
137
140
|
return "release-script";
|
|
@@ -173,7 +176,7 @@ async function resolveLatestTag(): Promise<string> {
|
|
|
173
176
|
const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
|
|
174
177
|
const res = await fetch(url, {
|
|
175
178
|
headers: {
|
|
176
|
-
"user-agent": "
|
|
179
|
+
"user-agent": "fclt-self-update",
|
|
177
180
|
accept: "application/vnd.github+json",
|
|
178
181
|
},
|
|
179
182
|
});
|
|
@@ -232,11 +235,16 @@ async function selfUpdateBinary(args: {
|
|
|
232
235
|
const explicitTag = normalizeVersionTag(args.requestedVersion);
|
|
233
236
|
const tag = explicitTag ?? (await resolveLatestTag());
|
|
234
237
|
const version = stripTagPrefix(tag);
|
|
235
|
-
const
|
|
236
|
-
|
|
238
|
+
const assetNames = [
|
|
239
|
+
`${PACKAGE_NAME}-${version}-${target.platform}-${target.arch}${target.ext}`,
|
|
240
|
+
`facult-${version}-${target.platform}-${target.arch}${target.ext}`,
|
|
241
|
+
];
|
|
242
|
+
const urls = assetNames.map(
|
|
243
|
+
(assetName) =>
|
|
244
|
+
`https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/${tag}/${assetName}`
|
|
245
|
+
);
|
|
237
246
|
|
|
238
|
-
const defaultBinaryName =
|
|
239
|
-
target.platform === "windows" ? "facult.exe" : "facult";
|
|
247
|
+
const defaultBinaryName = target.platform === "windows" ? "fclt.exe" : "fclt";
|
|
240
248
|
const fallbackPath = join(
|
|
241
249
|
preferredGlobalFacultStateDir(args.home),
|
|
242
250
|
"bin",
|
|
@@ -245,17 +253,19 @@ async function selfUpdateBinary(args: {
|
|
|
245
253
|
const currentExec = process.execPath;
|
|
246
254
|
const preferredPath =
|
|
247
255
|
args.state?.binaryPath ||
|
|
248
|
-
(basename(currentExec)
|
|
256
|
+
(CLI_BASENAME_PATTERN.test(basename(currentExec))
|
|
257
|
+
? currentExec
|
|
258
|
+
: fallbackPath);
|
|
249
259
|
const binaryPath = resolve(preferredPath);
|
|
250
260
|
|
|
251
261
|
if (args.dryRun) {
|
|
252
|
-
console.log(`[dry-run] Would download ${
|
|
262
|
+
console.log(`[dry-run] Would download ${urls[0]}`);
|
|
253
263
|
console.log(`[dry-run] Would replace ${binaryPath}`);
|
|
254
264
|
return;
|
|
255
265
|
}
|
|
256
266
|
|
|
257
267
|
await mkdir(dirname(binaryPath), { recursive: true });
|
|
258
|
-
const bytes = await
|
|
268
|
+
const bytes = await fetchFirstReleaseBinaryWithRetry(urls);
|
|
259
269
|
const tmpPath = `${binaryPath}.tmp-${Date.now()}`;
|
|
260
270
|
await Bun.write(tmpPath, Buffer.from(bytes));
|
|
261
271
|
if (target.platform !== "windows") {
|
|
@@ -268,7 +278,7 @@ async function selfUpdateBinary(args: {
|
|
|
268
278
|
packageVersion: version,
|
|
269
279
|
binaryPath,
|
|
270
280
|
});
|
|
271
|
-
console.log(`Updated
|
|
281
|
+
console.log(`Updated fclt binary to ${version}`);
|
|
272
282
|
console.log(`Path: ${binaryPath}`);
|
|
273
283
|
}
|
|
274
284
|
|
|
@@ -307,7 +317,7 @@ async function selfUpdateViaPackageManager(args: {
|
|
|
307
317
|
? stripTagPrefix(args.requestedVersion)
|
|
308
318
|
: "latest";
|
|
309
319
|
|
|
310
|
-
const installSpec =
|
|
320
|
+
const installSpec = `${PACKAGE_NAME}@${targetVersion}`;
|
|
311
321
|
const cmd =
|
|
312
322
|
pm === "npm"
|
|
313
323
|
? ["npm", "install", "-g", installSpec]
|
|
@@ -329,7 +339,7 @@ async function selfUpdateViaPackageManager(args: {
|
|
|
329
339
|
if (code !== 0) {
|
|
330
340
|
throw new Error(`Self-update failed via ${pm} (exit ${code}).`);
|
|
331
341
|
}
|
|
332
|
-
console.log(`Updated
|
|
342
|
+
console.log(`Updated fclt via ${pm}: ${installSpec}`);
|
|
333
343
|
}
|
|
334
344
|
|
|
335
345
|
async function fetchReleaseBinaryWithRetry(url: string): Promise<ArrayBuffer> {
|
|
@@ -340,7 +350,7 @@ async function fetchReleaseBinaryWithRetry(url: string): Promise<ArrayBuffer> {
|
|
|
340
350
|
try {
|
|
341
351
|
const response = await fetch(url, {
|
|
342
352
|
headers: {
|
|
343
|
-
"user-agent": "
|
|
353
|
+
"user-agent": "fclt-self-update",
|
|
344
354
|
accept: "application/octet-stream",
|
|
345
355
|
},
|
|
346
356
|
});
|
|
@@ -375,6 +385,20 @@ async function fetchReleaseBinaryWithRetry(url: string): Promise<ArrayBuffer> {
|
|
|
375
385
|
throw new Error(`Failed to download ${url}.${statusDetail}`);
|
|
376
386
|
}
|
|
377
387
|
|
|
388
|
+
async function fetchFirstReleaseBinaryWithRetry(
|
|
389
|
+
urls: string[]
|
|
390
|
+
): Promise<ArrayBuffer> {
|
|
391
|
+
let lastError: unknown;
|
|
392
|
+
for (const url of urls) {
|
|
393
|
+
try {
|
|
394
|
+
return await fetchReleaseBinaryWithRetry(url);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
lastError = error;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
throw lastError instanceof Error ? lastError : new Error("Download failed.");
|
|
400
|
+
}
|
|
401
|
+
|
|
378
402
|
function sleep(ms: number): Promise<void> {
|
|
379
403
|
return new Promise((resolve) => {
|
|
380
404
|
setTimeout(resolve, ms);
|
package/src/snippets-cli.ts
CHANGED
|
@@ -10,14 +10,14 @@ import {
|
|
|
10
10
|
const EDITOR_SPLIT_RE = /\s+/;
|
|
11
11
|
|
|
12
12
|
function printSnippetsHelp() {
|
|
13
|
-
console.log(`
|
|
13
|
+
console.log(`fclt snippets — sync reusable blocks across config files
|
|
14
14
|
|
|
15
15
|
Usage:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
fclt snippets list [--json]
|
|
17
|
+
fclt snippets show <name> [--json]
|
|
18
|
+
fclt snippets create <name>
|
|
19
|
+
fclt snippets edit <name>
|
|
20
|
+
fclt snippets sync [--dry-run] [file...]
|
|
21
21
|
|
|
22
22
|
Notes:
|
|
23
23
|
- <name> is the snippet marker name (e.g. codingstyle, global/codingstyle, myproject/context)
|
package/src/trust.ts
CHANGED
|
@@ -36,7 +36,7 @@ async function loadIndex(homeDir: string): Promise<FacultIndex> {
|
|
|
36
36
|
});
|
|
37
37
|
const file = Bun.file(indexPath);
|
|
38
38
|
if (!(await file.exists())) {
|
|
39
|
-
throw new Error(`Index not found at ${indexPath}. Run "
|
|
39
|
+
throw new Error(`Index not found at ${indexPath}. Run "fclt index".`);
|
|
40
40
|
}
|
|
41
41
|
const raw = await file.text();
|
|
42
42
|
return JSON.parse(raw) as FacultIndex;
|
|
@@ -130,11 +130,11 @@ function parseNamesFromArgv(argv: string[]): string[] {
|
|
|
130
130
|
|
|
131
131
|
export async function trustCommand(argv: string[]) {
|
|
132
132
|
if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
|
|
133
|
-
console.log(`
|
|
133
|
+
console.log(`fclt trust — mark skills or MCP servers as trusted (annotation only)
|
|
134
134
|
|
|
135
135
|
Usage:
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
fclt trust <name> [moreNames...]
|
|
137
|
+
fclt trust mcp:<name> [moreNames...]
|
|
138
138
|
`);
|
|
139
139
|
return;
|
|
140
140
|
}
|
|
@@ -143,7 +143,7 @@ Usage:
|
|
|
143
143
|
await applyTrust({ names, mode: "trust" });
|
|
144
144
|
console.log(`Marked as trusted: ${names.join(", ")}`);
|
|
145
145
|
console.log(
|
|
146
|
-
'Note: Trust is an annotation. Run "
|
|
146
|
+
'Note: Trust is an annotation. Run "fclt audit" for security review.'
|
|
147
147
|
);
|
|
148
148
|
} catch (err) {
|
|
149
149
|
console.error(err instanceof Error ? err.message : String(err));
|
|
@@ -153,11 +153,11 @@ Usage:
|
|
|
153
153
|
|
|
154
154
|
export async function untrustCommand(argv: string[]) {
|
|
155
155
|
if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
|
|
156
|
-
console.log(`
|
|
156
|
+
console.log(`fclt untrust — remove trusted annotation
|
|
157
157
|
|
|
158
158
|
Usage:
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
fclt untrust <name> [moreNames...]
|
|
160
|
+
fclt untrust mcp:<name> [moreNames...]
|
|
161
161
|
`);
|
|
162
162
|
return;
|
|
163
163
|
}
|
package/src/tui.ts
CHANGED
|
@@ -29,7 +29,7 @@ export async function runSkillsTui(res: ScanResult): Promise<void> {
|
|
|
29
29
|
height,
|
|
30
30
|
borderStyle: "double",
|
|
31
31
|
borderColor: "#4CC9F0",
|
|
32
|
-
title: "
|
|
32
|
+
title: "fclt scan — skills",
|
|
33
33
|
titleAlignment: "center",
|
|
34
34
|
backgroundColor: "#001122",
|
|
35
35
|
});
|