facult 2.4.0 → 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/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/index-builder.ts +10 -1
- package/src/manage.ts +95 -7
- package/src/scan.ts +26 -1
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
|
|
package/src/index-builder.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, readdir } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, join, relative } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { getAdapter } from "./adapters";
|
|
4
5
|
import { parseCliContextArgs, resolveCliContextRoot } from "./cli-context";
|
|
5
6
|
import {
|
|
6
7
|
type AssetScope,
|
|
@@ -31,6 +32,10 @@ interface AssetEntryBase {
|
|
|
31
32
|
shadow?: boolean;
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
function managedAgentFileExtension(tool: string): string {
|
|
36
|
+
return getAdapter(tool)?.agentFileExtension ?? ".toml";
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
export interface SkillEntry {
|
|
35
40
|
name: string;
|
|
36
41
|
path: string;
|
|
@@ -1213,6 +1218,7 @@ function registerManagedRenderedTargets(args: {
|
|
|
1213
1218
|
const nodes = args.graph.nodes;
|
|
1214
1219
|
for (const toolState of toolStates) {
|
|
1215
1220
|
if (toolState.agentsDir) {
|
|
1221
|
+
const extension = managedAgentFileExtension(toolState.tool);
|
|
1216
1222
|
for (const entry of Object.values(args.index.agents)) {
|
|
1217
1223
|
const sourceNodeId = sourceNodeIdForEntry({
|
|
1218
1224
|
kind: "agent",
|
|
@@ -1221,7 +1227,10 @@ function registerManagedRenderedTargets(args: {
|
|
|
1221
1227
|
if (!nodes[sourceNodeId]) {
|
|
1222
1228
|
continue;
|
|
1223
1229
|
}
|
|
1224
|
-
const targetPath = join(
|
|
1230
|
+
const targetPath = join(
|
|
1231
|
+
toolState.agentsDir,
|
|
1232
|
+
`${entry.name}${extension}`
|
|
1233
|
+
);
|
|
1225
1234
|
registerRenderedTargetNode({
|
|
1226
1235
|
graph: args.graph,
|
|
1227
1236
|
currentScope: args.currentScope,
|
package/src/manage.ts
CHANGED
|
@@ -203,6 +203,19 @@ function defaultToolPaths(
|
|
|
203
203
|
skillsDir: toolBase(".antigravity", "skills"),
|
|
204
204
|
mcpConfig: toolBase(".antigravity", "mcp.json"),
|
|
205
205
|
},
|
|
206
|
+
factory: {
|
|
207
|
+
tool: "factory",
|
|
208
|
+
skillsDir: projectRoot
|
|
209
|
+
? join(projectRoot, ".factory", "skills")
|
|
210
|
+
: homePath(home, ".factory", "skills"),
|
|
211
|
+
mcpConfig: projectRoot
|
|
212
|
+
? join(projectRoot, ".factory", "mcp.json")
|
|
213
|
+
: homePath(home, ".factory", "mcp.json"),
|
|
214
|
+
agentsDir: projectRoot
|
|
215
|
+
? join(projectRoot, ".factory", "droids")
|
|
216
|
+
: homePath(home, ".factory", "droids"),
|
|
217
|
+
toolHome: projectRoot ? undefined : homePath(home, ".factory"),
|
|
218
|
+
},
|
|
206
219
|
};
|
|
207
220
|
|
|
208
221
|
const adapterDefaults = (tool: string): ToolPaths | null => {
|
|
@@ -436,6 +449,69 @@ async function loadCanonicalAgents(
|
|
|
436
449
|
return await loadAgentsFromRoot(homePath(rootDir, "agents"));
|
|
437
450
|
}
|
|
438
451
|
|
|
452
|
+
function managedAgentFileExtension(tool: string): string {
|
|
453
|
+
return getAdapter(tool)?.agentFileExtension ?? ".toml";
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async function renderManagedAgentFile(args: {
|
|
457
|
+
agent: { name: string; sourcePath: string; raw: string };
|
|
458
|
+
homeDir: string;
|
|
459
|
+
rootDir: string;
|
|
460
|
+
tool: string;
|
|
461
|
+
targetPath: string;
|
|
462
|
+
}): Promise<string> {
|
|
463
|
+
const adapter = getAdapter(args.tool);
|
|
464
|
+
if (adapter?.renderAgent) {
|
|
465
|
+
return await adapter.renderAgent({
|
|
466
|
+
raw: args.agent.raw,
|
|
467
|
+
homeDir: args.homeDir,
|
|
468
|
+
rootDir: args.rootDir,
|
|
469
|
+
projectRoot:
|
|
470
|
+
projectRootFromAiRoot(args.rootDir, args.homeDir) ?? undefined,
|
|
471
|
+
tool: args.tool,
|
|
472
|
+
targetPath: args.targetPath,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return await renderCanonicalText(args.agent.raw, {
|
|
477
|
+
homeDir: args.homeDir,
|
|
478
|
+
rootDir: args.rootDir,
|
|
479
|
+
projectRoot: projectRootFromAiRoot(args.rootDir, args.homeDir) ?? undefined,
|
|
480
|
+
targetTool: args.tool,
|
|
481
|
+
targetPath: args.targetPath,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async function loadManagedAgentsFromTool(args: {
|
|
486
|
+
tool: string;
|
|
487
|
+
agentsDir: string;
|
|
488
|
+
}): Promise<{ name: string; sourcePath: string; raw: string }[]> {
|
|
489
|
+
const adapter = getAdapter(args.tool);
|
|
490
|
+
if (!adapter?.parseManagedAgentFile) {
|
|
491
|
+
return await loadAgentsFromRoot(args.agentsDir);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const extension = managedAgentFileExtension(args.tool);
|
|
495
|
+
const entries = await readdir(args.agentsDir, { withFileTypes: true }).catch(
|
|
496
|
+
() => [] as import("node:fs").Dirent[]
|
|
497
|
+
);
|
|
498
|
+
const out: { name: string; sourcePath: string; raw: string }[] = [];
|
|
499
|
+
|
|
500
|
+
for (const entry of entries) {
|
|
501
|
+
if (!(entry.isFile() && entry.name.endsWith(extension))) {
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
const sourcePath = join(args.agentsDir, entry.name);
|
|
505
|
+
const parsed = await adapter.parseManagedAgentFile(sourcePath);
|
|
506
|
+
if (!parsed) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
out.push(parsed);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
513
|
+
}
|
|
514
|
+
|
|
439
515
|
interface AutomationEntry {
|
|
440
516
|
name: string;
|
|
441
517
|
sourceDir: string;
|
|
@@ -637,14 +713,15 @@ async function planAgentFileChanges({
|
|
|
637
713
|
const contents = new Map<string, string>();
|
|
638
714
|
const sources = new Map<string, string>();
|
|
639
715
|
const desiredPaths = new Set<string>();
|
|
716
|
+
const extension = managedAgentFileExtension(tool);
|
|
640
717
|
|
|
641
718
|
for (const agent of agents) {
|
|
642
|
-
const target = homePath(agentsDir, `${agent.name}
|
|
643
|
-
const rendered = await
|
|
719
|
+
const target = homePath(agentsDir, `${agent.name}${extension}`);
|
|
720
|
+
const rendered = await renderManagedAgentFile({
|
|
721
|
+
agent,
|
|
644
722
|
homeDir,
|
|
645
723
|
rootDir,
|
|
646
|
-
|
|
647
|
-
targetTool: tool,
|
|
724
|
+
tool,
|
|
648
725
|
targetPath: target,
|
|
649
726
|
});
|
|
650
727
|
desiredPaths.add(target);
|
|
@@ -659,7 +736,7 @@ async function planAgentFileChanges({
|
|
|
659
736
|
const remove = new Set<string>();
|
|
660
737
|
|
|
661
738
|
for (const entry of existing) {
|
|
662
|
-
if (!(entry.isFile() && entry.name.endsWith(
|
|
739
|
+
if (!(entry.isFile() && entry.name.endsWith(extension))) {
|
|
663
740
|
continue;
|
|
664
741
|
}
|
|
665
742
|
const p = homePath(agentsDir, entry.name);
|
|
@@ -1214,11 +1291,15 @@ function logManagedImportPlan(tool: string, plan: ExistingManagedImportPlan) {
|
|
|
1214
1291
|
}
|
|
1215
1292
|
|
|
1216
1293
|
async function planExistingToolAgentAdoption(args: {
|
|
1294
|
+
tool: string;
|
|
1217
1295
|
rootDir: string;
|
|
1218
1296
|
agentsDir: string;
|
|
1219
1297
|
}): Promise<ExistingManagedImportPlan> {
|
|
1220
1298
|
const plan = emptyManagedImportPlan();
|
|
1221
|
-
const agents = await
|
|
1299
|
+
const agents = await loadManagedAgentsFromTool({
|
|
1300
|
+
tool: args.tool,
|
|
1301
|
+
agentsDir: args.agentsDir,
|
|
1302
|
+
});
|
|
1222
1303
|
for (const agent of agents) {
|
|
1223
1304
|
const canonicalPath = join(
|
|
1224
1305
|
args.rootDir,
|
|
@@ -1245,12 +1326,16 @@ async function planExistingToolAgentAdoption(args: {
|
|
|
1245
1326
|
}
|
|
1246
1327
|
|
|
1247
1328
|
async function adoptExistingToolAgents(args: {
|
|
1329
|
+
tool: string;
|
|
1248
1330
|
rootDir: string;
|
|
1249
1331
|
agentsDir: string;
|
|
1250
1332
|
conflictMode: "keep-canonical" | "keep-existing";
|
|
1251
1333
|
}): Promise<ExistingManagedItem[]> {
|
|
1252
1334
|
const adopted: ExistingManagedItem[] = [];
|
|
1253
|
-
const agents = await
|
|
1335
|
+
const agents = await loadManagedAgentsFromTool({
|
|
1336
|
+
tool: args.tool,
|
|
1337
|
+
agentsDir: args.agentsDir,
|
|
1338
|
+
});
|
|
1254
1339
|
for (const agent of agents) {
|
|
1255
1340
|
const canonicalPath = join(
|
|
1256
1341
|
args.rootDir,
|
|
@@ -2019,6 +2104,7 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2019
2104
|
asManagedSkillPlan(existingSkillPlan),
|
|
2020
2105
|
toolPaths.agentsDir
|
|
2021
2106
|
? await planExistingToolAgentAdoption({
|
|
2107
|
+
tool,
|
|
2022
2108
|
rootDir,
|
|
2023
2109
|
agentsDir: toolPaths.agentsDir,
|
|
2024
2110
|
})
|
|
@@ -2142,6 +2228,7 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2142
2228
|
}
|
|
2143
2229
|
if (toolPaths.agentsDir && opts.adoptExisting) {
|
|
2144
2230
|
const result = await adoptExistingToolAgents({
|
|
2231
|
+
tool,
|
|
2145
2232
|
rootDir,
|
|
2146
2233
|
agentsDir: toolPaths.agentsDir,
|
|
2147
2234
|
conflictMode: importConflictMode,
|
|
@@ -3006,6 +3093,7 @@ async function repairManagedCanonicalContent(args: {
|
|
|
3006
3093
|
|
|
3007
3094
|
if (args.entry.agentsBackup) {
|
|
3008
3095
|
const items = await adoptExistingToolAgents({
|
|
3096
|
+
tool: args.entry.tool,
|
|
3009
3097
|
rootDir: args.rootDir,
|
|
3010
3098
|
agentsDir: args.entry.agentsBackup,
|
|
3011
3099
|
conflictMode: "keep-canonical",
|
package/src/scan.ts
CHANGED
|
@@ -601,6 +601,19 @@ function defaultSourceSpecs(
|
|
|
601
601
|
"~/.codex/mcp.json",
|
|
602
602
|
],
|
|
603
603
|
},
|
|
604
|
+
{
|
|
605
|
+
id: "factory",
|
|
606
|
+
name: "Factory",
|
|
607
|
+
candidates: ["~/.factory", "~/.factory/mcp.json"],
|
|
608
|
+
skillDirs: ["~/.factory/skills"],
|
|
609
|
+
configFiles: ["~/.factory/mcp.json"],
|
|
610
|
+
assets: [
|
|
611
|
+
{
|
|
612
|
+
kind: "agents-instructions",
|
|
613
|
+
patterns: ["~/.factory/AGENTS.md"],
|
|
614
|
+
},
|
|
615
|
+
],
|
|
616
|
+
},
|
|
604
617
|
{
|
|
605
618
|
id: "claude",
|
|
606
619
|
name: "Claude (CLI)",
|
|
@@ -784,6 +797,13 @@ function defaultSourceSpecs(
|
|
|
784
797
|
},
|
|
785
798
|
],
|
|
786
799
|
},
|
|
800
|
+
{
|
|
801
|
+
id: "factory-project",
|
|
802
|
+
name: "Factory (project)",
|
|
803
|
+
candidates: [join(cwd, ".factory")],
|
|
804
|
+
skillDirs: [join(cwd, ".factory", "skills")],
|
|
805
|
+
configFiles: [join(cwd, ".factory", "mcp.json")],
|
|
806
|
+
},
|
|
787
807
|
];
|
|
788
808
|
|
|
789
809
|
if (includeGitHooks) {
|
|
@@ -1288,7 +1308,12 @@ async function buildFromRootResult(args: {
|
|
|
1288
1308
|
}
|
|
1289
1309
|
continue;
|
|
1290
1310
|
}
|
|
1291
|
-
if (
|
|
1311
|
+
if (
|
|
1312
|
+
name === ".codex" ||
|
|
1313
|
+
name === ".agents" ||
|
|
1314
|
+
name === ".clawdbot" ||
|
|
1315
|
+
name === ".factory"
|
|
1316
|
+
) {
|
|
1292
1317
|
await scanToolDotDir(child);
|
|
1293
1318
|
continue;
|
|
1294
1319
|
}
|