@contextforge/cli 0.1.1 → 0.1.5
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/dist/index.js +192 -87
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,19 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import pc7 from "picocolors";
|
|
6
6
|
|
|
7
7
|
// src/commands/add.ts
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
loadConfig
|
|
13
|
-
loadRegistry,
|
|
14
|
-
packMatchesProject,
|
|
15
|
-
readPackageJson,
|
|
16
|
-
saveConfig,
|
|
17
|
-
syncProject
|
|
9
|
+
fetchRegistry,
|
|
10
|
+
findPackSummary,
|
|
11
|
+
installPackAndSync,
|
|
12
|
+
loadConfig
|
|
18
13
|
} from "@contextforge/core";
|
|
19
14
|
import pc2 from "picocolors";
|
|
20
15
|
|
|
@@ -39,6 +34,7 @@ function formatAnalysis(analysis) {
|
|
|
39
34
|
`shadcn/ui: ${yesNo(analysis.styling.shadcn)}`,
|
|
40
35
|
`Prisma: ${yesNo(analysis.database.prisma)}`,
|
|
41
36
|
`Drizzle: ${yesNo(analysis.database.drizzle)}`,
|
|
37
|
+
`Supabase: ${yesNo(analysis.services.supabase)}`,
|
|
42
38
|
`Testing: ${testing || "not detected"}`,
|
|
43
39
|
""
|
|
44
40
|
].join("\n");
|
|
@@ -68,7 +64,7 @@ function formatDoctorReport(report) {
|
|
|
68
64
|
}
|
|
69
65
|
|
|
70
66
|
// src/registryOptions.ts
|
|
71
|
-
import {
|
|
67
|
+
import { OFFICIAL_REGISTRY_URL } from "@contextforge/core";
|
|
72
68
|
function collectRegistryOption(value, previous = []) {
|
|
73
69
|
return [...previous, value];
|
|
74
70
|
}
|
|
@@ -76,47 +72,55 @@ function envRegistries() {
|
|
|
76
72
|
return (process.env.CONTEXTFORGE_REGISTRY_URL ?? "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
77
73
|
}
|
|
78
74
|
function resolveRegistrySources(config, options) {
|
|
79
|
-
const configured = config?.
|
|
80
|
-
return [.../* @__PURE__ */ new Set([...
|
|
75
|
+
const configured = config?.registry ? [config.registry] : [OFFICIAL_REGISTRY_URL];
|
|
76
|
+
return [.../* @__PURE__ */ new Set([...options?.registry ?? [], ...envRegistries(), ...configured])];
|
|
77
|
+
}
|
|
78
|
+
function resolveRegistryUrl(config, options) {
|
|
79
|
+
return resolveRegistrySources(config, options)[0] ?? OFFICIAL_REGISTRY_URL;
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
// src/commands/add.ts
|
|
83
|
+
async function loadOptionalConfig(root) {
|
|
84
|
+
try {
|
|
85
|
+
return await loadConfig(root);
|
|
86
|
+
} catch {
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
84
90
|
async function addCommand(packName, options = {}) {
|
|
85
91
|
const root = process.cwd();
|
|
86
|
-
const config = await
|
|
87
|
-
const
|
|
88
|
-
const registry = await
|
|
89
|
-
const
|
|
90
|
-
if (!
|
|
91
|
-
throw new Error(`Unknown pack "${packName}".`);
|
|
92
|
-
}
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
await cacheRemotePacks(root, [pack]);
|
|
108
|
-
await saveConfig(root, nextConfig);
|
|
109
|
-
const result = await syncProject(root, nextConfig);
|
|
92
|
+
const config = await loadOptionalConfig(root);
|
|
93
|
+
const registryUrl = resolveRegistryUrl(config, options);
|
|
94
|
+
const registry = await fetchRegistry(registryUrl);
|
|
95
|
+
const summary = findPackSummary(registry, packName);
|
|
96
|
+
if (!summary) {
|
|
97
|
+
throw new Error(`Unknown ContextForge pack "${packName}". Run \`npx @contextforge/cli list\` to see available packs.`);
|
|
98
|
+
}
|
|
99
|
+
const alreadyConfigured = config?.installedPacks.includes(packName) ?? false;
|
|
100
|
+
if (alreadyConfigured && !options.force) {
|
|
101
|
+
console.log(pc2.yellow(`${packName} is already installed. Run \`npx @contextforge/cli sync\` to refresh it.`));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (options.dryRun) {
|
|
105
|
+
console.log(pc2.cyan(`Dry run: ${packName} would be downloaded from ${registryUrl}.`));
|
|
106
|
+
console.log(pc2.dim(`Pack path: ${summary.path}`));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const result = await installPackAndSync(root, registryUrl, packName, {
|
|
110
|
+
force: options.force,
|
|
111
|
+
dryRun: false
|
|
112
|
+
});
|
|
110
113
|
console.log(
|
|
111
|
-
alreadyInstalled ? pc2.
|
|
114
|
+
result.alreadyInstalled ? pc2.green(`Reinstalled ${packName}.`) : pc2.green(`Added ${packName}.`)
|
|
112
115
|
);
|
|
113
116
|
console.log(formatGeneratedFiles(result.generatedFiles));
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
// src/commands/doctor.ts
|
|
117
120
|
import { doctorProject } from "@contextforge/core";
|
|
118
|
-
async function doctorCommand() {
|
|
119
|
-
const
|
|
121
|
+
async function doctorCommand(options = {}) {
|
|
122
|
+
const registry = options.registry?.at(-1);
|
|
123
|
+
const report = await doctorProject(process.cwd(), registry ? { registry } : void 0);
|
|
120
124
|
console.log(formatDoctorReport(report));
|
|
121
125
|
if (report.issues.some((issue) => issue.level === "error")) {
|
|
122
126
|
process.exitCode = 1;
|
|
@@ -124,16 +128,17 @@ async function doctorCommand() {
|
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
// src/commands/init.ts
|
|
127
|
-
import { checkbox } from "@inquirer/prompts";
|
|
131
|
+
import { checkbox, confirm } from "@inquirer/prompts";
|
|
128
132
|
import {
|
|
133
|
+
DEFAULT_CORE_PACKS,
|
|
129
134
|
DEFAULT_TOOLS,
|
|
130
|
-
cacheRemotePacks as cacheRemotePacks2,
|
|
131
135
|
createConfig,
|
|
132
136
|
detectProject,
|
|
133
|
-
|
|
137
|
+
fetchRegistry as fetchRegistry2,
|
|
138
|
+
installPack,
|
|
139
|
+
missingMandatoryCorePacks,
|
|
134
140
|
recommendPacks,
|
|
135
|
-
|
|
136
|
-
syncProject as syncProject2
|
|
141
|
+
syncProject
|
|
137
142
|
} from "@contextforge/core";
|
|
138
143
|
import ora from "ora";
|
|
139
144
|
import pc3 from "picocolors";
|
|
@@ -143,6 +148,12 @@ var TOOL_LABELS = {
|
|
|
143
148
|
cursor: "Cursor",
|
|
144
149
|
copilot: "GitHub Copilot"
|
|
145
150
|
};
|
|
151
|
+
var CAPABILITY_PACKS = [
|
|
152
|
+
"system-design",
|
|
153
|
+
"frontend-system-design",
|
|
154
|
+
"api-design",
|
|
155
|
+
"test-driven-development"
|
|
156
|
+
];
|
|
146
157
|
function canPrompt() {
|
|
147
158
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
148
159
|
}
|
|
@@ -150,92 +161,186 @@ async function selectTools() {
|
|
|
150
161
|
if (!canPrompt()) {
|
|
151
162
|
return DEFAULT_TOOLS;
|
|
152
163
|
}
|
|
153
|
-
|
|
164
|
+
const selected = await checkbox({
|
|
154
165
|
message: "Which AI tools should ContextForge configure?",
|
|
155
166
|
required: true,
|
|
156
|
-
choices:
|
|
157
|
-
name:
|
|
158
|
-
|
|
167
|
+
choices: [
|
|
168
|
+
{ name: "All agents", value: "all", checked: true },
|
|
169
|
+
...DEFAULT_TOOLS.map((tool) => ({
|
|
170
|
+
name: TOOL_LABELS[tool],
|
|
171
|
+
value: tool,
|
|
172
|
+
checked: false
|
|
173
|
+
}))
|
|
174
|
+
]
|
|
175
|
+
});
|
|
176
|
+
return selected.includes("all") ? DEFAULT_TOOLS : selected.filter((tool) => tool !== "all");
|
|
177
|
+
}
|
|
178
|
+
async function selectRecommendedPacks(recommended) {
|
|
179
|
+
if (recommended.length === 0) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
if (!canPrompt()) {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
const wantsRecommendations = await confirm({
|
|
186
|
+
message: "Install recommended stack-specific packs?",
|
|
187
|
+
default: true
|
|
188
|
+
});
|
|
189
|
+
if (!wantsRecommendations) {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
const selected = await checkbox({
|
|
193
|
+
message: "Select recommended packs to install",
|
|
194
|
+
choices: recommended.map((pack) => ({
|
|
195
|
+
name: `${pack.title} (${pack.name})`,
|
|
196
|
+
value: pack.name,
|
|
197
|
+
description: pack.description,
|
|
159
198
|
checked: true
|
|
160
199
|
}))
|
|
161
200
|
});
|
|
201
|
+
return recommended.filter((pack) => selected.includes(pack.name));
|
|
162
202
|
}
|
|
163
|
-
async function
|
|
164
|
-
if (
|
|
203
|
+
async function selectCapabilityPacks(registryPacks) {
|
|
204
|
+
if (!canPrompt()) {
|
|
165
205
|
return [];
|
|
166
206
|
}
|
|
167
|
-
|
|
168
|
-
|
|
207
|
+
const available = CAPABILITY_PACKS.map(
|
|
208
|
+
(packName) => registryPacks.find((pack) => pack.name === packName)
|
|
209
|
+
).filter((pack) => Boolean(pack));
|
|
210
|
+
if (available.length === 0) {
|
|
211
|
+
return [];
|
|
169
212
|
}
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
required: true,
|
|
174
|
-
choices: registry.map((pack) => ({
|
|
213
|
+
const selected = await checkbox({
|
|
214
|
+
message: "Add optional architecture or testing packs?",
|
|
215
|
+
choices: available.map((pack) => ({
|
|
175
216
|
name: `${pack.title} (${pack.name})`,
|
|
176
217
|
value: pack.name,
|
|
177
218
|
description: pack.description,
|
|
178
|
-
checked:
|
|
219
|
+
checked: false
|
|
179
220
|
}))
|
|
180
221
|
});
|
|
181
|
-
return
|
|
222
|
+
return available.filter((pack) => selected.includes(pack.name));
|
|
182
223
|
}
|
|
183
224
|
async function initCommand(options = {}) {
|
|
184
225
|
const root = process.cwd();
|
|
185
|
-
const
|
|
186
|
-
const spinner = ora("Detecting project").start();
|
|
187
|
-
const [analysis, registry] = await Promise.all([
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
]);
|
|
191
|
-
const recommended = recommendPacks(analysis, registry);
|
|
226
|
+
const registryUrl = resolveRegistryUrl(void 0, options);
|
|
227
|
+
const spinner = ora("Detecting project and fetching registry").start();
|
|
228
|
+
const [analysis, registry] = await Promise.all([detectProject(root), fetchRegistry2(registryUrl)]);
|
|
229
|
+
const recommended = await recommendPacks(analysis, registry);
|
|
230
|
+
const missingMandatory = missingMandatoryCorePacks(registry);
|
|
192
231
|
spinner.succeed("Project detected");
|
|
193
232
|
console.log(formatAnalysis(analysis));
|
|
233
|
+
if (missingMandatory.length > 0) {
|
|
234
|
+
console.log(pc3.yellow(`Mandatory packs missing from registry: ${missingMandatory.join(", ")}`));
|
|
235
|
+
console.log("");
|
|
236
|
+
}
|
|
194
237
|
if (recommended.length > 0) {
|
|
195
|
-
console.log(pc3.bold("Recommended packs:"));
|
|
238
|
+
console.log(pc3.bold("Recommended optional packs:"));
|
|
196
239
|
for (const pack of recommended) {
|
|
197
240
|
console.log(`${pc3.green("OK")} ${pack.name}`);
|
|
198
241
|
}
|
|
199
242
|
console.log("");
|
|
200
|
-
} else if (registry.length === 0) {
|
|
201
|
-
console.log(
|
|
202
|
-
pc3.yellow("No packs were available from the official registry. Check your network or run with a private --registry URL.")
|
|
203
|
-
);
|
|
204
|
-
console.log("");
|
|
205
243
|
}
|
|
206
244
|
const tools = await selectTools();
|
|
207
|
-
const
|
|
208
|
-
await
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
245
|
+
const optionalPacks = await selectRecommendedPacks(recommended);
|
|
246
|
+
const capabilityPacks = await selectCapabilityPacks(registry.packs);
|
|
247
|
+
const packNames = [
|
|
248
|
+
.../* @__PURE__ */ new Set([
|
|
249
|
+
...DEFAULT_CORE_PACKS.filter((packName) => registry.packs.some((pack) => pack.name === packName)),
|
|
250
|
+
...optionalPacks.map((pack) => pack.name),
|
|
251
|
+
...capabilityPacks.map((pack) => pack.name)
|
|
252
|
+
])
|
|
253
|
+
];
|
|
254
|
+
for (const packName of packNames) {
|
|
255
|
+
await installPack(root, registryUrl, packName, { force: true });
|
|
256
|
+
}
|
|
257
|
+
const initialConfig = createConfig(
|
|
258
|
+
analysis,
|
|
259
|
+
packNames.map((name) => ({ name })),
|
|
260
|
+
tools,
|
|
261
|
+
registryUrl
|
|
262
|
+
);
|
|
263
|
+
const result = await syncProject(root, initialConfig);
|
|
212
264
|
console.log(pc3.green("ContextForge initialized."));
|
|
213
265
|
console.log(formatGeneratedFiles(result.generatedFiles));
|
|
214
266
|
}
|
|
215
267
|
|
|
216
|
-
// src/commands/
|
|
217
|
-
import {
|
|
268
|
+
// src/commands/list.ts
|
|
269
|
+
import { fetchRegistry as fetchRegistry3, listRegistryPacks } from "@contextforge/core";
|
|
218
270
|
import pc4 from "picocolors";
|
|
271
|
+
async function listCommand(options = {}) {
|
|
272
|
+
const registryUrl = resolveRegistryUrl(void 0, options);
|
|
273
|
+
const registry = await fetchRegistry3(registryUrl);
|
|
274
|
+
const packs = listRegistryPacks(registry);
|
|
275
|
+
const topics = /* @__PURE__ */ new Map();
|
|
276
|
+
for (const pack of packs) {
|
|
277
|
+
const topicPacks = topics.get(pack.topic) ?? [];
|
|
278
|
+
topicPacks.push(pack);
|
|
279
|
+
topics.set(pack.topic, topicPacks);
|
|
280
|
+
}
|
|
281
|
+
console.log(pc4.bold("ContextForge Registry"));
|
|
282
|
+
console.log(pc4.dim(registryUrl));
|
|
283
|
+
console.log("");
|
|
284
|
+
for (const [topic, topicPacks] of topics) {
|
|
285
|
+
console.log(pc4.bold(topic));
|
|
286
|
+
for (const pack of topicPacks) {
|
|
287
|
+
console.log(` ${pc4.green(pack.name)} - ${pack.title}`);
|
|
288
|
+
if (pack.description) {
|
|
289
|
+
console.log(` ${pc4.dim(pack.description)}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
console.log("");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/commands/search.ts
|
|
297
|
+
import { fetchRegistry as fetchRegistry4, searchRegistryPacks } from "@contextforge/core";
|
|
298
|
+
import pc5 from "picocolors";
|
|
299
|
+
async function searchCommand(query, options = {}) {
|
|
300
|
+
const registryUrl = resolveRegistryUrl(void 0, options);
|
|
301
|
+
const registry = await fetchRegistry4(registryUrl);
|
|
302
|
+
const results = searchRegistryPacks(registry, query);
|
|
303
|
+
if (results.length === 0) {
|
|
304
|
+
console.log(pc5.yellow(`No packs found for "${query}".`));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
console.log(pc5.bold(`ContextForge packs matching "${query}"`));
|
|
308
|
+
console.log("");
|
|
309
|
+
for (const pack of results) {
|
|
310
|
+
console.log(`${pc5.green(pack.name)} ${pc5.dim(`[${pack.topic}]`)}`);
|
|
311
|
+
console.log(` ${pack.title}`);
|
|
312
|
+
if (pack.description) {
|
|
313
|
+
console.log(` ${pc5.dim(pack.description)}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/commands/sync.ts
|
|
319
|
+
import { loadConfig as loadConfig2, syncProject as syncProject2 } from "@contextforge/core";
|
|
320
|
+
import pc6 from "picocolors";
|
|
219
321
|
async function syncCommand(options = {}) {
|
|
220
322
|
const root = process.cwd();
|
|
221
323
|
const config = await loadConfig2(root);
|
|
222
|
-
const
|
|
324
|
+
const registry = resolveRegistryUrl(config, options);
|
|
325
|
+
const result = await syncProject2(root, {
|
|
223
326
|
...config,
|
|
224
|
-
|
|
327
|
+
registry
|
|
225
328
|
});
|
|
226
|
-
console.log(
|
|
329
|
+
console.log(pc6.green("ContextForge synced."));
|
|
227
330
|
console.log(formatGeneratedFiles(result.generatedFiles));
|
|
228
331
|
}
|
|
229
332
|
|
|
230
333
|
// src/index.ts
|
|
231
334
|
var program = new Command();
|
|
232
|
-
program.name("contextforge").description("Make existing codebases AI-agent ready").version("0.1.
|
|
335
|
+
program.name("contextforge").description("Make existing codebases AI-agent ready").version("0.1.5");
|
|
233
336
|
program.command("init").description("Initialize AI-agent instructions").option("--registry <url>", "Static remote registry index URL", collectRegistryOption, []).action(initCommand);
|
|
234
|
-
program.command("add").argument("<pack>").description("Add an instruction pack").option("--registry <url>", "Static remote registry index URL", collectRegistryOption, []).action(addCommand);
|
|
337
|
+
program.command("add").argument("<pack>").description("Add an instruction pack").option("--registry <url>", "Static remote registry index URL", collectRegistryOption, []).option("--force", "Re-download and regenerate even when the pack is already installed").option("--dry-run", "Show what would be installed without writing files").action(addCommand);
|
|
235
338
|
program.command("sync").description("Sync generated AI instruction files").option("--registry <url>", "Static remote registry index URL", collectRegistryOption, []).action(syncCommand);
|
|
236
|
-
program.command("doctor").description("Check whether AI instructions match the repo").action(doctorCommand);
|
|
339
|
+
program.command("doctor").description("Check whether AI instructions match the repo").option("--registry <url>", "Static remote registry index URL", collectRegistryOption, []).action(doctorCommand);
|
|
340
|
+
program.command("list").description("List available instruction packs").option("--registry <url>", "Static remote registry index URL", collectRegistryOption, []).action(listCommand);
|
|
341
|
+
program.command("search").argument("<query>").description("Search available instruction packs").option("--registry <url>", "Static remote registry index URL", collectRegistryOption, []).action(searchCommand);
|
|
237
342
|
program.parseAsync().catch((error) => {
|
|
238
343
|
const message = error instanceof Error ? error.message : String(error);
|
|
239
|
-
console.error(
|
|
344
|
+
console.error(pc7.red(`Error: ${message}`));
|
|
240
345
|
process.exitCode = 1;
|
|
241
346
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contextforge/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"contextforge": "dist/index.js"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"commander": "^14.0.0",
|
|
14
14
|
"ora": "^8.2.0",
|
|
15
15
|
"picocolors": "^1.1.1",
|
|
16
|
-
"@contextforge/core": "0.1.
|
|
16
|
+
"@contextforge/core": "0.1.5"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"tsx": "^4.20.0"
|