facult 2.3.1 → 2.5.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 +30 -10
- package/bin/fclt.cjs +86 -3
- package/package.json +1 -1
- package/src/adapters/factory.ts +228 -0
- package/src/adapters/index.ts +2 -0
- package/src/adapters/types.ts +22 -0
- package/src/autosync.ts +192 -8
- package/src/index-builder.ts +12 -3
- package/src/manage.ts +487 -13
- package/src/paths.ts +69 -0
- package/src/scan.ts +26 -1
- package/src/self-update.ts +4 -2
package/README.md
CHANGED
|
@@ -260,9 +260,17 @@ fclt index
|
|
|
260
260
|
|
|
261
261
|
Why `keep-current`: it is deterministic and non-interactive for duplicate sources.
|
|
262
262
|
|
|
263
|
-
Canonical source root: `~/.ai` for global work, or `<repo>/.ai` for project-local work.
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
Canonical source root: `~/.ai` for global work, or `<repo>/.ai` for project-local work.
|
|
264
|
+
|
|
265
|
+
Generated AI state that belongs with the canonical root lives inside that root:
|
|
266
|
+
- global: `~/.ai/.facult/ai/...`
|
|
267
|
+
- project: `<repo>/.ai/.facult/ai/...`
|
|
268
|
+
|
|
269
|
+
Machine-local operational state lives outside the canonical root:
|
|
270
|
+
- macOS state: `~/Library/Application Support/fclt/...`
|
|
271
|
+
- macOS cache: `~/Library/Caches/fclt/...`
|
|
272
|
+
- Linux/other state: `${XDG_STATE_HOME:-~/.local/state}/fclt/...`
|
|
273
|
+
- Linux/other cache: `${XDG_CACHE_HOME:-~/.cache}/fclt/...`
|
|
266
274
|
|
|
267
275
|
### 3b. Bootstrap a repo-local `.ai`
|
|
268
276
|
|
|
@@ -420,7 +428,8 @@ Typical layout:
|
|
|
420
428
|
|
|
421
429
|
Important split:
|
|
422
430
|
- `.ai/` is canonical source
|
|
423
|
-
- `.ai/.facult/` is
|
|
431
|
+
- `.ai/.facult/ai/` is generated AI state that belongs with the canonical root
|
|
432
|
+
- machine-local Facult state such as managed-tool state, autosync runtime/config, install metadata, and launcher caches lives outside `.ai/`
|
|
424
433
|
- tool homes such as `.codex/` and `.claude/` are rendered outputs
|
|
425
434
|
- the generated capability graph lives at `.ai/.facult/ai/graph.json`
|
|
426
435
|
|
|
@@ -714,6 +723,13 @@ Files are written to:
|
|
|
714
723
|
- `~/.codex/automations/<name>/automation.toml`
|
|
715
724
|
- `~/.codex/automations/<name>/memory.md`
|
|
716
725
|
|
|
726
|
+
When Codex is in managed mode, canonical automation sources live under:
|
|
727
|
+
|
|
728
|
+
- `~/.ai/automations/<name>/...` for global automation state
|
|
729
|
+
- `<repo>/.ai/automations/<name>/...` for project-scoped canonical state
|
|
730
|
+
|
|
731
|
+
Managed sync renders those canonical automation directories into the shared live Codex automation store at `~/.codex/automations/` and only removes automation files that were previously rendered by the same canonical root.
|
|
732
|
+
|
|
717
733
|
Example project automation:
|
|
718
734
|
|
|
719
735
|
```bash
|
|
@@ -774,17 +790,21 @@ fclt <command> --help
|
|
|
774
790
|
|
|
775
791
|
### State and report files
|
|
776
792
|
|
|
777
|
-
Under `~/.ai/.facult
|
|
793
|
+
Under canonical generated AI state (`~/.ai/.facult/` or `<repo>/.ai/.facult/`):
|
|
778
794
|
- `sources.json` (latest inventory scan state)
|
|
779
795
|
- `consolidated.json` (consolidation state)
|
|
780
|
-
- `managed.json` (managed tool state)
|
|
781
796
|
- `ai/index.json` (generated canonical AI inventory)
|
|
782
797
|
- `audit/static-latest.json` (latest static audit report)
|
|
783
798
|
- `audit/agent-latest.json` (latest agent audit report)
|
|
784
799
|
- `trust/sources.json` (source trust policy state)
|
|
785
|
-
|
|
786
|
-
-
|
|
787
|
-
- `
|
|
800
|
+
|
|
801
|
+
Under machine-local Facult state:
|
|
802
|
+
- `install.json` (machine-local install metadata)
|
|
803
|
+
- `global/managed.json` or `projects/<slug-hash>/managed.json` (managed tool state)
|
|
804
|
+
- `.../autosync/services/*.json` (autosync service configs)
|
|
805
|
+
- `.../autosync/state/*.json` (autosync runtime state)
|
|
806
|
+
- `.../autosync/logs/*` (autosync service logs)
|
|
807
|
+
- `runtime/<version>/<platform-arch>/...` under the machine-local cache root (npm launcher binary cache)
|
|
788
808
|
|
|
789
809
|
### Config reference
|
|
790
810
|
|
|
@@ -890,7 +910,7 @@ Release behavior:
|
|
|
890
910
|
4. npm publish runs only after binary asset upload succeeds (`publish-npm` depends on `publish-assets`).
|
|
891
911
|
5. Published release assets include platform binaries, `fclt-install.sh`, `facult-install.sh`, and `SHA256SUMS`.
|
|
892
912
|
6. When `HOMEBREW_TAP_TOKEN` is configured, the release workflow also updates the Homebrew tap at `hack-dance/homebrew-tap`.
|
|
893
|
-
7. The npm package launcher resolves your platform, downloads the matching release binary, caches it under
|
|
913
|
+
7. The npm package launcher resolves your platform, downloads the matching release binary, caches it under the machine-local cache root (`~/Library/Caches/fclt/runtime/...` on macOS or `${XDG_CACHE_HOME:-~/.cache}/fclt/runtime/...` elsewhere), and runs it.
|
|
894
914
|
|
|
895
915
|
Current prebuilt binary targets:
|
|
896
916
|
- `darwin-x64`
|
package/bin/fclt.cjs
CHANGED
|
@@ -16,6 +16,43 @@ const PACKAGE_NAME = "facult";
|
|
|
16
16
|
const DOWNLOAD_RETRIES = 12;
|
|
17
17
|
const DOWNLOAD_RETRY_DELAY_MS = 5000;
|
|
18
18
|
|
|
19
|
+
function isHelpLikeArgs(args) {
|
|
20
|
+
return (
|
|
21
|
+
args.length === 0 ||
|
|
22
|
+
args.includes("--help") ||
|
|
23
|
+
args.includes("-h") ||
|
|
24
|
+
args[0] === "help"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function localStateRoot(home) {
|
|
29
|
+
const override = String(process.env.FACULT_LOCAL_STATE_DIR || "").trim();
|
|
30
|
+
if (override) {
|
|
31
|
+
return path.resolve(override);
|
|
32
|
+
}
|
|
33
|
+
if (process.platform === "darwin") {
|
|
34
|
+
return path.join(home, "Library", "Application Support", "fclt");
|
|
35
|
+
}
|
|
36
|
+
const xdg = String(process.env.XDG_STATE_HOME || "").trim();
|
|
37
|
+
return xdg
|
|
38
|
+
? path.join(path.resolve(xdg), "fclt")
|
|
39
|
+
: path.join(home, ".local", "state", "fclt");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function localCacheRoot(home) {
|
|
43
|
+
const override = String(process.env.FACULT_CACHE_DIR || "").trim();
|
|
44
|
+
if (override) {
|
|
45
|
+
return path.resolve(override);
|
|
46
|
+
}
|
|
47
|
+
if (process.platform === "darwin") {
|
|
48
|
+
return path.join(home, "Library", "Caches", "fclt");
|
|
49
|
+
}
|
|
50
|
+
const xdg = String(process.env.XDG_CACHE_HOME || "").trim();
|
|
51
|
+
return xdg
|
|
52
|
+
? path.join(path.resolve(xdg), "fclt")
|
|
53
|
+
: path.join(home, ".cache", "fclt");
|
|
54
|
+
}
|
|
55
|
+
|
|
19
56
|
async function main() {
|
|
20
57
|
const resolved = resolveTarget();
|
|
21
58
|
if (!resolved.ok) {
|
|
@@ -30,7 +67,7 @@ async function main() {
|
|
|
30
67
|
}
|
|
31
68
|
|
|
32
69
|
const home = os.homedir();
|
|
33
|
-
const cacheRoot = path.join(home, "
|
|
70
|
+
const cacheRoot = path.join(localCacheRoot(home), "runtime");
|
|
34
71
|
const installDir = path.join(
|
|
35
72
|
cacheRoot,
|
|
36
73
|
version,
|
|
@@ -39,9 +76,34 @@ async function main() {
|
|
|
39
76
|
const binaryName = resolved.platform === "windows" ? "fclt.exe" : "fclt";
|
|
40
77
|
const binaryPath = path.join(installDir, binaryName);
|
|
41
78
|
const sourceEntry = path.join(__dirname, "..", "src", "index.ts");
|
|
79
|
+
const args = process.argv.slice(2);
|
|
42
80
|
let installedBinaryThisRun = false;
|
|
43
81
|
|
|
44
82
|
if (!(await fileExists(binaryPath))) {
|
|
83
|
+
const packageManager = detectPackageManager();
|
|
84
|
+
const hasSourceFallback = await canUseSourceFallback(sourceEntry);
|
|
85
|
+
const incompleteCache = await hasIncompleteRuntimeCache({
|
|
86
|
+
installDir,
|
|
87
|
+
binaryName,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (incompleteCache) {
|
|
91
|
+
await removeIncompleteRuntimeTemps({ installDir, binaryName });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (hasSourceFallback && (incompleteCache || isHelpLikeArgs(args))) {
|
|
95
|
+
return runSourceFallback({
|
|
96
|
+
sourceEntry,
|
|
97
|
+
version,
|
|
98
|
+
packageManager,
|
|
99
|
+
reason: new Error(
|
|
100
|
+
incompleteCache
|
|
101
|
+
? "incomplete cached runtime download"
|
|
102
|
+
: "runtime binary missing for help-like command"
|
|
103
|
+
),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
45
107
|
const tag = `v${version}`;
|
|
46
108
|
const assetName = `${PACKAGE_NAME}-${version}-${resolved.platform}-${resolved.arch}${resolved.ext}`;
|
|
47
109
|
const url = `https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/${tag}/${assetName}`;
|
|
@@ -95,7 +157,6 @@ async function main() {
|
|
|
95
157
|
});
|
|
96
158
|
}
|
|
97
159
|
|
|
98
|
-
const args = process.argv.slice(2);
|
|
99
160
|
const result = spawnSync(binaryPath, args, {
|
|
100
161
|
stdio: "inherit",
|
|
101
162
|
env: {
|
|
@@ -281,6 +342,28 @@ async function fileExists(filePath) {
|
|
|
281
342
|
}
|
|
282
343
|
}
|
|
283
344
|
|
|
345
|
+
async function hasIncompleteRuntimeCache({ installDir, binaryName }) {
|
|
346
|
+
try {
|
|
347
|
+
const entries = await fsp.readdir(installDir);
|
|
348
|
+
return entries.some((entry) => entry.startsWith(`${binaryName}.tmp-`));
|
|
349
|
+
} catch {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function removeIncompleteRuntimeTemps({ installDir, binaryName }) {
|
|
355
|
+
try {
|
|
356
|
+
const entries = await fsp.readdir(installDir);
|
|
357
|
+
await Promise.all(
|
|
358
|
+
entries
|
|
359
|
+
.filter((entry) => entry.startsWith(`${binaryName}.tmp-`))
|
|
360
|
+
.map((entry) => safeUnlink(path.join(installDir, entry)))
|
|
361
|
+
);
|
|
362
|
+
} catch {
|
|
363
|
+
// Ignore missing runtime dirs while cleaning stale temp files.
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
284
367
|
async function safeUnlink(filePath) {
|
|
285
368
|
try {
|
|
286
369
|
await fsp.unlink(filePath);
|
|
@@ -295,7 +378,7 @@ function sleep(ms) {
|
|
|
295
378
|
|
|
296
379
|
async function writeInstallState(state) {
|
|
297
380
|
const home = os.homedir();
|
|
298
|
-
const installStateDir =
|
|
381
|
+
const installStateDir = localStateRoot(home);
|
|
299
382
|
const installStatePath = path.join(installStateDir, "install.json");
|
|
300
383
|
await fsp.mkdir(installStateDir, { recursive: true });
|
|
301
384
|
await fsp.writeFile(
|
package/package.json
CHANGED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { basename, extname } from "node:path";
|
|
2
|
+
import { renderCanonicalText } from "../agents";
|
|
3
|
+
import { generateMcpConfig, parseMcpConfig } from "./mcp";
|
|
4
|
+
import { parseSkillsDir } from "./skills";
|
|
5
|
+
import type {
|
|
6
|
+
CanonicalMcpConfig,
|
|
7
|
+
CanonicalMcpServer,
|
|
8
|
+
ParsedManagedAgentFile,
|
|
9
|
+
RenderManagedAgentOptions,
|
|
10
|
+
ToolAdapter,
|
|
11
|
+
} from "./types";
|
|
12
|
+
import { detectExplicitVersion } from "./version";
|
|
13
|
+
|
|
14
|
+
const FRONTMATTER_LINE_SPLIT_REGEX = /\r?\n/;
|
|
15
|
+
const FACTORY_AGENT_FRONTMATTER_REGEX =
|
|
16
|
+
/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
17
|
+
const LEADING_WHITESPACE_REGEX = /^\s+/;
|
|
18
|
+
const TRAILING_WHITESPACE_REGEX = /\s+$/;
|
|
19
|
+
|
|
20
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
21
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function escapeTomlMultiline(value: string): string {
|
|
25
|
+
return value.replace(/"""/g, '\\"""');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function escapeYamlString(value: string): string {
|
|
29
|
+
return JSON.stringify(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function stringifyFrontmatter(values: Record<string, string>): string {
|
|
33
|
+
return Object.entries(values)
|
|
34
|
+
.map(([key, value]) => `${key}: ${escapeYamlString(value)}`)
|
|
35
|
+
.join("\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseFrontmatterScalar(value: string): string {
|
|
39
|
+
const trimmed = value.trim();
|
|
40
|
+
if (!trimmed) {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
if (
|
|
44
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
45
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
46
|
+
) {
|
|
47
|
+
const quote = trimmed[0];
|
|
48
|
+
const inner = trimmed.slice(1, -1);
|
|
49
|
+
if (quote === '"') {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(trimmed);
|
|
52
|
+
} catch {
|
|
53
|
+
return inner;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return inner;
|
|
57
|
+
}
|
|
58
|
+
return trimmed;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseFrontmatter(text: string): Record<string, string> {
|
|
62
|
+
const out: Record<string, string> = {};
|
|
63
|
+
for (const line of text.split(FRONTMATTER_LINE_SPLIT_REGEX)) {
|
|
64
|
+
const trimmed = line.trim();
|
|
65
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const separator = trimmed.indexOf(":");
|
|
69
|
+
if (separator === -1) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const key = trimmed.slice(0, separator).trim();
|
|
73
|
+
const value = parseFrontmatterScalar(trimmed.slice(separator + 1));
|
|
74
|
+
if (key) {
|
|
75
|
+
out[key] = value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function normalizeFactoryServer(
|
|
82
|
+
server: CanonicalMcpServer
|
|
83
|
+
): CanonicalMcpServer {
|
|
84
|
+
if (!isPlainObject(server.vendorExtensions)) {
|
|
85
|
+
return server;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const { type, ...vendorExtensions } = server.vendorExtensions;
|
|
89
|
+
return {
|
|
90
|
+
...server,
|
|
91
|
+
transport:
|
|
92
|
+
typeof type === "string" && !server.transport ? type : server.transport,
|
|
93
|
+
vendorExtensions:
|
|
94
|
+
Object.keys(vendorExtensions).length > 0 ? vendorExtensions : undefined,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseFactoryMcp(config: unknown): CanonicalMcpConfig {
|
|
99
|
+
const parsed = parseMcpConfig(config);
|
|
100
|
+
for (const [name, server] of Object.entries(parsed.servers)) {
|
|
101
|
+
parsed.servers[name] = normalizeFactoryServer({ ...server });
|
|
102
|
+
}
|
|
103
|
+
return parsed;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function generateFactoryMcp(
|
|
107
|
+
canonical: CanonicalMcpConfig
|
|
108
|
+
): Record<string, unknown> {
|
|
109
|
+
const generated = generateMcpConfig(canonical, "mcpServers");
|
|
110
|
+
const servers = generated.mcpServers;
|
|
111
|
+
if (!isPlainObject(servers)) {
|
|
112
|
+
return generated;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const [name, value] of Object.entries(servers)) {
|
|
116
|
+
if (!isPlainObject(value)) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const { transport, ...server } = value as Record<string, unknown>;
|
|
120
|
+
const inferredType =
|
|
121
|
+
(typeof transport === "string" ? transport : undefined) ??
|
|
122
|
+
(typeof server.url === "string"
|
|
123
|
+
? "http"
|
|
124
|
+
: typeof server.command === "string"
|
|
125
|
+
? "stdio"
|
|
126
|
+
: undefined);
|
|
127
|
+
if (inferredType && typeof server.type !== "string") {
|
|
128
|
+
server.type = inferredType;
|
|
129
|
+
}
|
|
130
|
+
if (typeof server.disabled !== "boolean") {
|
|
131
|
+
server.disabled = false;
|
|
132
|
+
}
|
|
133
|
+
servers[name] = server;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return generated;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function renderFactoryAgent(
|
|
140
|
+
options: RenderManagedAgentOptions
|
|
141
|
+
): Promise<string> {
|
|
142
|
+
const parsed = Bun.TOML.parse(options.raw) as Record<string, unknown>;
|
|
143
|
+
const name =
|
|
144
|
+
typeof parsed.name === "string"
|
|
145
|
+
? parsed.name
|
|
146
|
+
: basename(options.targetPath, extname(options.targetPath));
|
|
147
|
+
const description =
|
|
148
|
+
typeof parsed.description === "string" ? parsed.description : undefined;
|
|
149
|
+
const instructions =
|
|
150
|
+
typeof parsed.developer_instructions === "string"
|
|
151
|
+
? parsed.developer_instructions
|
|
152
|
+
: "";
|
|
153
|
+
const renderedInstructions = await renderCanonicalText(instructions, {
|
|
154
|
+
homeDir: options.homeDir,
|
|
155
|
+
rootDir: options.rootDir,
|
|
156
|
+
projectRoot: options.projectRoot,
|
|
157
|
+
targetTool: options.tool,
|
|
158
|
+
targetPath: options.targetPath,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const frontmatter = stringifyFrontmatter({
|
|
162
|
+
name,
|
|
163
|
+
...(description ? { description } : {}),
|
|
164
|
+
model: "inherit",
|
|
165
|
+
});
|
|
166
|
+
const body = renderedInstructions.trim();
|
|
167
|
+
|
|
168
|
+
return body
|
|
169
|
+
? `---\n${frontmatter}\n---\n\n${body}\n`
|
|
170
|
+
: `---\n${frontmatter}\n---\n`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function parseFactoryManagedAgentFile(
|
|
174
|
+
path: string
|
|
175
|
+
): Promise<ParsedManagedAgentFile | null> {
|
|
176
|
+
const file = Bun.file(path);
|
|
177
|
+
if (!(await file.exists())) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const raw = await file.text();
|
|
182
|
+
const match = raw.match(FACTORY_AGENT_FRONTMATTER_REGEX);
|
|
183
|
+
if (!match) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const [, frontmatterRaw, bodyRaw] = match;
|
|
188
|
+
const frontmatter = parseFrontmatter(frontmatterRaw ?? "");
|
|
189
|
+
const name = frontmatter.name || basename(path, extname(path));
|
|
190
|
+
const description = frontmatter.description || undefined;
|
|
191
|
+
const body = (bodyRaw ?? "")
|
|
192
|
+
.replace(LEADING_WHITESPACE_REGEX, "")
|
|
193
|
+
.replace(TRAILING_WHITESPACE_REGEX, "");
|
|
194
|
+
const lines = [`name = ${JSON.stringify(name)}`];
|
|
195
|
+
if (description) {
|
|
196
|
+
lines.push(`description = ${JSON.stringify(description)}`);
|
|
197
|
+
}
|
|
198
|
+
lines.push("", 'developer_instructions = """');
|
|
199
|
+
if (body) {
|
|
200
|
+
lines.push(escapeTomlMultiline(body));
|
|
201
|
+
}
|
|
202
|
+
lines.push('"""', "");
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
name,
|
|
206
|
+
raw: lines.join("\n"),
|
|
207
|
+
sourcePath: path,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export const factoryAdapter: ToolAdapter = {
|
|
212
|
+
id: "factory",
|
|
213
|
+
name: "Factory",
|
|
214
|
+
versions: ["v1"],
|
|
215
|
+
detectVersion: detectExplicitVersion,
|
|
216
|
+
getDefaultPaths: () => ({
|
|
217
|
+
mcp: "~/.factory/mcp.json",
|
|
218
|
+
skills: ["~/.factory/skills", ".factory/skills"],
|
|
219
|
+
agents: ["~/.factory/droids", ".factory/droids"],
|
|
220
|
+
}),
|
|
221
|
+
parseMcp: (config) => parseFactoryMcp(config),
|
|
222
|
+
generateMcp: (canonical) => generateFactoryMcp(canonical),
|
|
223
|
+
parseSkills: async (skillsDir) => await parseSkillsDir(skillsDir),
|
|
224
|
+
agentFileExtension: ".md",
|
|
225
|
+
renderAgent: async (options) => await renderFactoryAgent(options),
|
|
226
|
+
parseManagedAgentFile: async (path) =>
|
|
227
|
+
await parseFactoryManagedAgentFile(path),
|
|
228
|
+
};
|
package/src/adapters/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { claudeDesktopAdapter } from "./claude-desktop";
|
|
|
3
3
|
import { clawdbotAdapter } from "./clawdbot";
|
|
4
4
|
import { codexAdapter } from "./codex";
|
|
5
5
|
import { cursorAdapter } from "./cursor";
|
|
6
|
+
import { factoryAdapter } from "./factory";
|
|
6
7
|
import { referenceAdapter } from "./reference";
|
|
7
8
|
import type { ResolveVersionOptions, ToolAdapter } from "./types";
|
|
8
9
|
|
|
@@ -64,6 +65,7 @@ export async function resolveAdapterVersion(
|
|
|
64
65
|
registerAdapter(referenceAdapter);
|
|
65
66
|
registerAdapter(cursorAdapter);
|
|
66
67
|
registerAdapter(codexAdapter);
|
|
68
|
+
registerAdapter(factoryAdapter);
|
|
67
69
|
registerAdapter(claudeCliAdapter);
|
|
68
70
|
registerAdapter(claudeDesktopAdapter);
|
|
69
71
|
registerAdapter(clawdbotAdapter);
|
package/src/adapters/types.ts
CHANGED
|
@@ -18,6 +18,21 @@ export interface CanonicalSkill {
|
|
|
18
18
|
path?: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export interface RenderManagedAgentOptions {
|
|
22
|
+
raw: string;
|
|
23
|
+
rootDir: string;
|
|
24
|
+
tool: string;
|
|
25
|
+
targetPath: string;
|
|
26
|
+
homeDir?: string;
|
|
27
|
+
projectRoot?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ParsedManagedAgentFile {
|
|
31
|
+
name: string;
|
|
32
|
+
raw: string;
|
|
33
|
+
sourcePath: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
export interface AdapterDefaultPaths {
|
|
22
37
|
mcp?: string;
|
|
23
38
|
skills?: string | string[];
|
|
@@ -34,6 +49,13 @@ export interface ToolAdapter {
|
|
|
34
49
|
parseSkills?: (skillsDir: string) => Promise<CanonicalSkill[]>;
|
|
35
50
|
generateMcp?: (canonical: CanonicalMcpConfig, version?: string) => unknown;
|
|
36
51
|
generateSkillsDir?: (skills: CanonicalSkill[]) => Promise<void>;
|
|
52
|
+
agentFileExtension?: string;
|
|
53
|
+
renderAgent?: (
|
|
54
|
+
options: RenderManagedAgentOptions
|
|
55
|
+
) => Promise<string> | string;
|
|
56
|
+
parseManagedAgentFile?: (
|
|
57
|
+
path: string
|
|
58
|
+
) => Promise<ParsedManagedAgentFile | null>;
|
|
37
59
|
getDefaultPaths?: () => AdapterDefaultPaths;
|
|
38
60
|
}
|
|
39
61
|
|