facult 2.10.0 → 2.12.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 +62 -10
- package/assets/packs/facult-operating-model/AGENTS.global.md +2 -0
- package/assets/packs/facult-operating-model/instructions/CAPABILITY_COMPOSITION.md +76 -0
- package/assets/packs/facult-operating-model/instructions/EVOLUTION.md +19 -1
- package/assets/packs/facult-operating-model/instructions/LEARNING_AND_WRITEBACK.md +10 -0
- package/assets/packs/facult-operating-model/instructions/WORK_UNITS.md +61 -0
- package/docs/README.md +19 -0
- package/docs/built-in-pack.md +93 -0
- package/docs/composable-capability.md +170 -0
- package/docs/concepts.md +125 -0
- package/docs/managed-mode.md +73 -0
- package/docs/project-ai.md +91 -0
- package/docs/roadmap.md +56 -0
- package/docs/writeback-evolution.md +97 -0
- package/package.json +2 -1
- package/src/builtin-assets.ts +1 -1
- package/src/remote-types.ts +18 -2
- package/src/remote.ts +291 -18
package/src/remote.ts
CHANGED
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
relative,
|
|
10
10
|
resolve,
|
|
11
11
|
} from "node:path";
|
|
12
|
-
import { fileURLToPath } from "node:url";
|
|
13
12
|
import { isCancel, multiselect, select, text } from "@clack/prompts";
|
|
13
|
+
import { facultBuiltinPackRoot } from "./builtin";
|
|
14
|
+
import { parseCliContextArgs, resolveCliContextRoot } from "./cli-context";
|
|
14
15
|
import {
|
|
15
16
|
renderBullets,
|
|
16
17
|
renderCatalog,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
type RemoteAgentItem,
|
|
43
44
|
type RemoteIndexItem,
|
|
44
45
|
type RemoteIndexManifest,
|
|
46
|
+
type RemoteInstructionItem,
|
|
45
47
|
type RemoteItemType,
|
|
46
48
|
type RemoteMcpItem,
|
|
47
49
|
type RemoteSkillItem,
|
|
@@ -59,6 +61,8 @@ const QUERY_SPLIT_RE = /\s+/;
|
|
|
59
61
|
const MD_EXT_RE = /\.md$/i;
|
|
60
62
|
const FILE_EXT_RE = /\.[A-Za-z0-9]+$/;
|
|
61
63
|
const TRAILING_SLASH_RE = /\/+$/;
|
|
64
|
+
const LEADING_SLASH_RE = /^\/+/;
|
|
65
|
+
const INSTRUCTION_TITLE_SPLIT_RE = /[-_.\s]+/;
|
|
62
66
|
const PROMPT_PATH_SPLIT_RE = /[,\n]/;
|
|
63
67
|
const GIT_WORKTREE_LINE_RE = /\r?\n/;
|
|
64
68
|
|
|
@@ -221,6 +225,52 @@ Use this skill when the task repeatedly follows a known workflow and you want co
|
|
|
221
225
|
},
|
|
222
226
|
},
|
|
223
227
|
},
|
|
228
|
+
{
|
|
229
|
+
id: "instruction-template",
|
|
230
|
+
type: "instruction",
|
|
231
|
+
title: "Instruction Template",
|
|
232
|
+
description:
|
|
233
|
+
"Reusable markdown instruction scaffold with ref and snippet composition examples.",
|
|
234
|
+
version: "1.0.0",
|
|
235
|
+
tags: ["template", "dx", "instruction"],
|
|
236
|
+
instruction: {
|
|
237
|
+
name: "WORKFLOW.md",
|
|
238
|
+
content: `---
|
|
239
|
+
description: "{{name}} reusable instruction"
|
|
240
|
+
tags: [instruction, workflow]
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
# {{title}}
|
|
244
|
+
|
|
245
|
+
Use this instruction when the task needs repeatable guidance that should be discoverable, targetable by writeback, and composable into agent docs.
|
|
246
|
+
|
|
247
|
+
## Scope
|
|
248
|
+
|
|
249
|
+
- Applies to:
|
|
250
|
+
- Does not apply to:
|
|
251
|
+
- Project-specific overrides:
|
|
252
|
+
|
|
253
|
+
## Guidance
|
|
254
|
+
|
|
255
|
+
- Keep the rule concrete enough that another agent can follow it without chat context.
|
|
256
|
+
- Link deeper reusable guidance with canonical refs such as \`@ai/instructions/VERIFICATION.md\` or \`@project/instructions/TESTING.md\`.
|
|
257
|
+
- Reuse stable partials with snippet markers when the same block appears in more than one rendered doc.
|
|
258
|
+
|
|
259
|
+
## Composition
|
|
260
|
+
|
|
261
|
+
<!-- fclty:global/team/example -->
|
|
262
|
+
<!-- /fclty:global/team/example -->
|
|
263
|
+
|
|
264
|
+
## Writeback Targeting
|
|
265
|
+
|
|
266
|
+
Record durable friction against this instruction with:
|
|
267
|
+
|
|
268
|
+
\`\`\`bash
|
|
269
|
+
fclt ai writeback add --kind missing_context --summary "<what was missing>" --asset instruction:{{assetName}}
|
|
270
|
+
\`\`\`
|
|
271
|
+
`,
|
|
272
|
+
},
|
|
273
|
+
},
|
|
224
274
|
{
|
|
225
275
|
id: "mcp-stdio-template",
|
|
226
276
|
type: "mcp",
|
|
@@ -1221,11 +1271,6 @@ updated_at = ${timestamp}
|
|
|
1221
1271
|
};
|
|
1222
1272
|
}
|
|
1223
1273
|
|
|
1224
|
-
function builtinPackRoot(packName: string): string {
|
|
1225
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
1226
|
-
return join(here, "..", "assets", "packs", packName);
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
1274
|
async function pathExists(pathValue: string): Promise<boolean> {
|
|
1230
1275
|
try {
|
|
1231
1276
|
await Bun.file(pathValue).stat();
|
|
@@ -1258,15 +1303,15 @@ async function listFilesRecursive(rootDir: string): Promise<string[]> {
|
|
|
1258
1303
|
return out.sort();
|
|
1259
1304
|
}
|
|
1260
1305
|
|
|
1261
|
-
async function
|
|
1262
|
-
|
|
1306
|
+
async function scaffoldBuiltinOperatingModelPack(args: {
|
|
1307
|
+
rootDir: string;
|
|
1263
1308
|
homeDir?: string;
|
|
1264
1309
|
dryRun?: boolean;
|
|
1265
1310
|
force?: boolean;
|
|
1311
|
+
installedAs?: string;
|
|
1266
1312
|
}): Promise<InstallResult> {
|
|
1267
|
-
const
|
|
1268
|
-
const
|
|
1269
|
-
const packRoot = builtinPackRoot("facult-operating-model");
|
|
1313
|
+
const rootDir = resolve(args.rootDir);
|
|
1314
|
+
const packRoot = facultBuiltinPackRoot("facult-operating-model");
|
|
1270
1315
|
const files = await listFilesRecursive(packRoot);
|
|
1271
1316
|
const changedPaths: string[] = [];
|
|
1272
1317
|
|
|
@@ -1307,7 +1352,7 @@ async function scaffoldBuiltinProjectAiPack(args: {
|
|
|
1307
1352
|
return {
|
|
1308
1353
|
ref: `${BUILTIN_INDEX_NAME}:facult-operating-model`,
|
|
1309
1354
|
type: "skill",
|
|
1310
|
-
installedAs: "
|
|
1355
|
+
installedAs: args.installedAs ?? "operating-model",
|
|
1311
1356
|
path: rootDir,
|
|
1312
1357
|
sourceTrustLevel: "trusted",
|
|
1313
1358
|
dryRun: Boolean(args.dryRun),
|
|
@@ -1315,6 +1360,22 @@ async function scaffoldBuiltinProjectAiPack(args: {
|
|
|
1315
1360
|
};
|
|
1316
1361
|
}
|
|
1317
1362
|
|
|
1363
|
+
async function scaffoldBuiltinProjectAiPack(args: {
|
|
1364
|
+
cwd?: string;
|
|
1365
|
+
homeDir?: string;
|
|
1366
|
+
dryRun?: boolean;
|
|
1367
|
+
force?: boolean;
|
|
1368
|
+
}): Promise<InstallResult> {
|
|
1369
|
+
const cwd = resolve(args.cwd ?? process.cwd());
|
|
1370
|
+
return await scaffoldBuiltinOperatingModelPack({
|
|
1371
|
+
rootDir: join(cwd, ".ai"),
|
|
1372
|
+
homeDir: args.homeDir,
|
|
1373
|
+
dryRun: args.dryRun,
|
|
1374
|
+
force: args.force,
|
|
1375
|
+
installedAs: "project-ai",
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1318
1379
|
function compareVersions(a: string, b: string): number {
|
|
1319
1380
|
const aTokens = (a.match(VERSION_TOKEN_RE) ?? []).map((t) => t.toLowerCase());
|
|
1320
1381
|
const bTokens = (b.match(VERSION_TOKEN_RE) ?? []).map((t) => t.toLowerCase());
|
|
@@ -1449,7 +1510,8 @@ function parseIndexItem(raw: unknown): RemoteIndexItem | null {
|
|
|
1449
1510
|
type !== "skill" &&
|
|
1450
1511
|
type !== "mcp" &&
|
|
1451
1512
|
type !== "agent" &&
|
|
1452
|
-
type !== "snippet"
|
|
1513
|
+
type !== "snippet" &&
|
|
1514
|
+
type !== "instruction"
|
|
1453
1515
|
) {
|
|
1454
1516
|
return null;
|
|
1455
1517
|
}
|
|
@@ -1548,6 +1610,30 @@ function parseIndexItem(raw: unknown): RemoteIndexItem | null {
|
|
|
1548
1610
|
};
|
|
1549
1611
|
}
|
|
1550
1612
|
|
|
1613
|
+
if (type === "instruction") {
|
|
1614
|
+
const instructionRaw = obj.instruction;
|
|
1615
|
+
if (!isPlainObject(instructionRaw)) {
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
const name =
|
|
1619
|
+
typeof instructionRaw.name === "string" ? instructionRaw.name.trim() : "";
|
|
1620
|
+
const content =
|
|
1621
|
+
typeof instructionRaw.content === "string" ? instructionRaw.content : "";
|
|
1622
|
+
if (!(name && content)) {
|
|
1623
|
+
return null;
|
|
1624
|
+
}
|
|
1625
|
+
return {
|
|
1626
|
+
id,
|
|
1627
|
+
type,
|
|
1628
|
+
title,
|
|
1629
|
+
description,
|
|
1630
|
+
version,
|
|
1631
|
+
sourceUrl,
|
|
1632
|
+
tags,
|
|
1633
|
+
instruction: { name, content },
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1551
1637
|
const snippetRaw = obj.snippet;
|
|
1552
1638
|
if (!isPlainObject(snippetRaw)) {
|
|
1553
1639
|
return null;
|
|
@@ -1719,7 +1805,8 @@ async function loadInstalledState(
|
|
|
1719
1805
|
type !== "skill" &&
|
|
1720
1806
|
type !== "mcp" &&
|
|
1721
1807
|
type !== "agent" &&
|
|
1722
|
-
type !== "snippet"
|
|
1808
|
+
type !== "snippet" &&
|
|
1809
|
+
type !== "instruction"
|
|
1723
1810
|
) {
|
|
1724
1811
|
continue;
|
|
1725
1812
|
}
|
|
@@ -2023,6 +2110,69 @@ async function installSnippetItem(args: {
|
|
|
2023
2110
|
};
|
|
2024
2111
|
}
|
|
2025
2112
|
|
|
2113
|
+
function normalizeInstructionName(value: string): string {
|
|
2114
|
+
const normalized = value
|
|
2115
|
+
.trim()
|
|
2116
|
+
.replaceAll("\\", "/")
|
|
2117
|
+
.replace(LEADING_SLASH_RE, "");
|
|
2118
|
+
if (!normalized) {
|
|
2119
|
+
throw new Error("Instruction name is required");
|
|
2120
|
+
}
|
|
2121
|
+
const withExt = MD_EXT_RE.test(normalized) ? normalized : `${normalized}.md`;
|
|
2122
|
+
if (!isSafeRelativePath(withExt)) {
|
|
2123
|
+
throw new Error(`Invalid instruction name: ${value}`);
|
|
2124
|
+
}
|
|
2125
|
+
return withExt;
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
function instructionTitleFromFileName(fileName: string): string {
|
|
2129
|
+
const base = basename(fileName).replace(MD_EXT_RE, "");
|
|
2130
|
+
return base
|
|
2131
|
+
.split(INSTRUCTION_TITLE_SPLIT_RE)
|
|
2132
|
+
.filter(Boolean)
|
|
2133
|
+
.map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
|
|
2134
|
+
.join(" ");
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
function instructionAssetName(fileName: string): string {
|
|
2138
|
+
return basename(fileName).replace(MD_EXT_RE, "");
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
async function installInstructionItem(args: {
|
|
2142
|
+
item: RemoteInstructionItem;
|
|
2143
|
+
installAs?: string;
|
|
2144
|
+
rootDir: string;
|
|
2145
|
+
force: boolean;
|
|
2146
|
+
dryRun: boolean;
|
|
2147
|
+
}): Promise<{ installedAs: string; path: string; changedPaths: string[] }> {
|
|
2148
|
+
const fileName = normalizeInstructionName(
|
|
2149
|
+
args.installAs ?? args.item.instruction.name
|
|
2150
|
+
);
|
|
2151
|
+
const instructionPath = join(args.rootDir, "instructions", fileName);
|
|
2152
|
+
assertInstallPath(instructionPath, join(args.rootDir, "instructions"));
|
|
2153
|
+
if ((await fileExists(instructionPath)) && !args.force) {
|
|
2154
|
+
throw new Error(
|
|
2155
|
+
`Instruction already exists: ${fileName} (use --force to overwrite)`
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
if (!args.dryRun) {
|
|
2159
|
+
await mkdir(dirname(instructionPath), { recursive: true });
|
|
2160
|
+
await Bun.write(
|
|
2161
|
+
instructionPath,
|
|
2162
|
+
renderTemplate(args.item.instruction.content, {
|
|
2163
|
+
name: fileName,
|
|
2164
|
+
title: instructionTitleFromFileName(fileName),
|
|
2165
|
+
assetName: instructionAssetName(fileName),
|
|
2166
|
+
})
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
return {
|
|
2170
|
+
installedAs: fileName,
|
|
2171
|
+
path: instructionPath,
|
|
2172
|
+
changedPaths: [instructionPath],
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2026
2176
|
async function installParsedItem(args: {
|
|
2027
2177
|
parsedRef: { index: string; itemId: string };
|
|
2028
2178
|
item: RemoteIndexItem;
|
|
@@ -2064,7 +2214,7 @@ async function installParsedItem(args: {
|
|
|
2064
2214
|
force: args.force,
|
|
2065
2215
|
dryRun: args.dryRun,
|
|
2066
2216
|
});
|
|
2067
|
-
} else {
|
|
2217
|
+
} else if (args.item.type === "snippet") {
|
|
2068
2218
|
writeResult = await installSnippetItem({
|
|
2069
2219
|
item: args.item,
|
|
2070
2220
|
installAs: args.installAs,
|
|
@@ -2072,6 +2222,14 @@ async function installParsedItem(args: {
|
|
|
2072
2222
|
force: args.force,
|
|
2073
2223
|
dryRun: args.dryRun,
|
|
2074
2224
|
});
|
|
2225
|
+
} else {
|
|
2226
|
+
writeResult = await installInstructionItem({
|
|
2227
|
+
item: args.item,
|
|
2228
|
+
installAs: args.installAs,
|
|
2229
|
+
rootDir: args.rootDir,
|
|
2230
|
+
force: args.force,
|
|
2231
|
+
dryRun: args.dryRun,
|
|
2232
|
+
});
|
|
2075
2233
|
}
|
|
2076
2234
|
|
|
2077
2235
|
const result: InstallResult = {
|
|
@@ -2654,10 +2812,16 @@ function printTemplatesHelp() {
|
|
|
2654
2812
|
renderCode(
|
|
2655
2813
|
"fclt templates init agent <name> [--force] [--dry-run]"
|
|
2656
2814
|
),
|
|
2815
|
+
renderCode(
|
|
2816
|
+
"fclt templates init instruction <name> [--force] [--dry-run]"
|
|
2817
|
+
),
|
|
2657
2818
|
renderCode(
|
|
2658
2819
|
"fclt templates init snippet <marker> [--force] [--dry-run]"
|
|
2659
2820
|
),
|
|
2660
2821
|
renderCode("fclt templates init agents [--force] [--dry-run]"),
|
|
2822
|
+
renderCode(
|
|
2823
|
+
"fclt templates init operating-model [--global|--project|--root PATH] [--force] [--dry-run]"
|
|
2824
|
+
),
|
|
2661
2825
|
renderCode("fclt templates init project-ai [--force] [--dry-run]"),
|
|
2662
2826
|
renderCode(
|
|
2663
2827
|
"fclt templates init automation <template-id> [--scope global|project|wide] [--name <name>] [--project-root <path>] [--cwds <path1,path2>] [--rrule <RRULE>] [--status PAUSED|ACTIVE] [--yes] [--dry-run]"
|
|
@@ -2719,6 +2883,53 @@ function parseLongFlag(argv: string[], flag: string): string | null {
|
|
|
2719
2883
|
return null;
|
|
2720
2884
|
}
|
|
2721
2885
|
|
|
2886
|
+
const TEMPLATE_INIT_VALUE_FLAGS = new Set([
|
|
2887
|
+
"--automation-status",
|
|
2888
|
+
"--cwds",
|
|
2889
|
+
"--name",
|
|
2890
|
+
"--project-root",
|
|
2891
|
+
"--root",
|
|
2892
|
+
"--rrule",
|
|
2893
|
+
"--scope",
|
|
2894
|
+
"--status",
|
|
2895
|
+
]);
|
|
2896
|
+
|
|
2897
|
+
function parseTemplateInitArgs(argv: string[]): {
|
|
2898
|
+
positional: string[];
|
|
2899
|
+
rootArg?: string;
|
|
2900
|
+
} {
|
|
2901
|
+
const positional: string[] = [];
|
|
2902
|
+
let rootArg: string | undefined;
|
|
2903
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
2904
|
+
const arg = argv[i];
|
|
2905
|
+
if (!arg) {
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
if (arg === "--root") {
|
|
2909
|
+
rootArg = argv[i + 1] ?? undefined;
|
|
2910
|
+
i += 1;
|
|
2911
|
+
continue;
|
|
2912
|
+
}
|
|
2913
|
+
if (arg.startsWith("--root=")) {
|
|
2914
|
+
rootArg = arg.slice("--root=".length);
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
2917
|
+
if (TEMPLATE_INIT_VALUE_FLAGS.has(arg)) {
|
|
2918
|
+
i += 1;
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
const equalFlag = arg.startsWith("--") ? arg.split("=", 1)[0] : "";
|
|
2922
|
+
if (equalFlag && TEMPLATE_INIT_VALUE_FLAGS.has(equalFlag)) {
|
|
2923
|
+
continue;
|
|
2924
|
+
}
|
|
2925
|
+
if (arg.startsWith("-")) {
|
|
2926
|
+
continue;
|
|
2927
|
+
}
|
|
2928
|
+
positional.push(arg);
|
|
2929
|
+
}
|
|
2930
|
+
return { positional, rootArg };
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2722
2933
|
export async function sourcesCommand(
|
|
2723
2934
|
argv: string[],
|
|
2724
2935
|
ctx: RemoteCommandContext = {}
|
|
@@ -3052,6 +3263,14 @@ export async function templatesCommand(
|
|
|
3052
3263
|
description: item.description ?? "",
|
|
3053
3264
|
version: item.version ?? "",
|
|
3054
3265
|
})),
|
|
3266
|
+
{
|
|
3267
|
+
id: "operating-model",
|
|
3268
|
+
type: "pack",
|
|
3269
|
+
title: "Operating Model Pack",
|
|
3270
|
+
description:
|
|
3271
|
+
"Install the built-in Facult operating-model pack into the active canonical root.",
|
|
3272
|
+
version: "1.0.0",
|
|
3273
|
+
},
|
|
3055
3274
|
{
|
|
3056
3275
|
id: "project-ai",
|
|
3057
3276
|
type: "pack",
|
|
@@ -3101,7 +3320,7 @@ export async function templatesCommand(
|
|
|
3101
3320
|
const [kind, ...args] = rest;
|
|
3102
3321
|
if (!kind) {
|
|
3103
3322
|
console.error(
|
|
3104
|
-
"templates init requires a kind (skill|mcp|agent|snippet|agents|claude|project-ai|automation)"
|
|
3323
|
+
"templates init requires a kind (skill|mcp|agent|instruction|snippet|agents|claude|operating-model|project-ai|automation)"
|
|
3105
3324
|
);
|
|
3106
3325
|
process.exitCode = 2;
|
|
3107
3326
|
return;
|
|
@@ -3109,7 +3328,8 @@ export async function templatesCommand(
|
|
|
3109
3328
|
const dryRun = args.includes("--dry-run");
|
|
3110
3329
|
const force = args.includes("--force");
|
|
3111
3330
|
const json = args.includes("--json");
|
|
3112
|
-
const
|
|
3331
|
+
const parsedArgs = parseTemplateInitArgs(args);
|
|
3332
|
+
const positional = parsedArgs.positional;
|
|
3113
3333
|
|
|
3114
3334
|
if (kind === "project-ai") {
|
|
3115
3335
|
try {
|
|
@@ -3144,6 +3364,51 @@ export async function templatesCommand(
|
|
|
3144
3364
|
}
|
|
3145
3365
|
}
|
|
3146
3366
|
|
|
3367
|
+
if (kind === "operating-model") {
|
|
3368
|
+
try {
|
|
3369
|
+
const context = parseCliContextArgs(args, { allowScope: false });
|
|
3370
|
+
const cwd = resolve(ctx.cwd ?? process.cwd());
|
|
3371
|
+
const rootDir = ctx.rootDir
|
|
3372
|
+
? resolve(ctx.rootDir)
|
|
3373
|
+
: context.scope === "project" && !context.rootArg
|
|
3374
|
+
? join(findGitRootFromPath(cwd) ?? cwd, ".ai")
|
|
3375
|
+
: resolveCliContextRoot({
|
|
3376
|
+
rootArg: context.rootArg,
|
|
3377
|
+
scope: context.scope,
|
|
3378
|
+
homeDir: ctx.homeDir,
|
|
3379
|
+
cwd,
|
|
3380
|
+
});
|
|
3381
|
+
const result = await scaffoldBuiltinOperatingModelPack({
|
|
3382
|
+
rootDir,
|
|
3383
|
+
homeDir: ctx.homeDir,
|
|
3384
|
+
dryRun,
|
|
3385
|
+
force,
|
|
3386
|
+
});
|
|
3387
|
+
if (json) {
|
|
3388
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
const action = dryRun ? "Would install" : "Installed";
|
|
3392
|
+
console.log(
|
|
3393
|
+
renderPage({
|
|
3394
|
+
title: `fclt templates init ${kind}`,
|
|
3395
|
+
subtitle: `${action} ${result.installedAs} into ${result.path}`,
|
|
3396
|
+
sections: [
|
|
3397
|
+
{
|
|
3398
|
+
title: "Changed Paths",
|
|
3399
|
+
lines: renderBullets(result.changedPaths),
|
|
3400
|
+
},
|
|
3401
|
+
],
|
|
3402
|
+
})
|
|
3403
|
+
);
|
|
3404
|
+
return;
|
|
3405
|
+
} catch (err) {
|
|
3406
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
3407
|
+
process.exitCode = 1;
|
|
3408
|
+
return;
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3147
3412
|
let ref = "";
|
|
3148
3413
|
let as: string | undefined;
|
|
3149
3414
|
if (kind === "skill") {
|
|
@@ -3176,6 +3441,14 @@ export async function templatesCommand(
|
|
|
3176
3441
|
as = normalizedName.endsWith(".toml")
|
|
3177
3442
|
? normalizedName
|
|
3178
3443
|
: `${normalizedName}/agent.toml`;
|
|
3444
|
+
} else if (kind === "instruction") {
|
|
3445
|
+
ref = `${BUILTIN_INDEX_NAME}:instruction-template`;
|
|
3446
|
+
as = positional[0];
|
|
3447
|
+
if (!as) {
|
|
3448
|
+
console.error("templates init instruction requires a <name>");
|
|
3449
|
+
process.exitCode = 2;
|
|
3450
|
+
return;
|
|
3451
|
+
}
|
|
3179
3452
|
} else if (kind === "snippet") {
|
|
3180
3453
|
ref = `${BUILTIN_INDEX_NAME}:snippet-template`;
|
|
3181
3454
|
as = positional[0];
|
|
@@ -3285,7 +3558,7 @@ export async function templatesCommand(
|
|
|
3285
3558
|
dryRun,
|
|
3286
3559
|
force,
|
|
3287
3560
|
homeDir: ctx.homeDir,
|
|
3288
|
-
rootDir: ctx.rootDir,
|
|
3561
|
+
rootDir: parsedArgs.rootArg ?? ctx.rootDir,
|
|
3289
3562
|
cwd: ctx.cwd,
|
|
3290
3563
|
fetchJson: ctx.fetchJson,
|
|
3291
3564
|
fetchText: ctx.fetchText,
|