@contextforge/cli 0.1.2 → 0.1.6
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 +150 -97
- 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;
|
|
@@ -126,14 +130,15 @@ async function doctorCommand() {
|
|
|
126
130
|
// src/commands/init.ts
|
|
127
131
|
import { checkbox } 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";
|
|
@@ -150,92 +155,140 @@ async function selectTools() {
|
|
|
150
155
|
if (!canPrompt()) {
|
|
151
156
|
return DEFAULT_TOOLS;
|
|
152
157
|
}
|
|
153
|
-
|
|
158
|
+
const selected = await checkbox({
|
|
154
159
|
message: "Which AI tools should ContextForge configure?",
|
|
155
160
|
required: true,
|
|
156
|
-
choices:
|
|
157
|
-
name:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
choices: [
|
|
162
|
+
{ name: "All agents", value: "all", checked: true },
|
|
163
|
+
...DEFAULT_TOOLS.map((tool) => ({
|
|
164
|
+
name: TOOL_LABELS[tool],
|
|
165
|
+
value: tool,
|
|
166
|
+
checked: false
|
|
167
|
+
}))
|
|
168
|
+
]
|
|
161
169
|
});
|
|
170
|
+
return selected.includes("all") ? DEFAULT_TOOLS : selected.filter((tool) => tool !== "all");
|
|
162
171
|
}
|
|
163
|
-
|
|
164
|
-
if (
|
|
165
|
-
return
|
|
172
|
+
function printRecommendedAddCommands(recommended) {
|
|
173
|
+
if (recommended.length === 0) {
|
|
174
|
+
return;
|
|
166
175
|
}
|
|
167
|
-
|
|
168
|
-
|
|
176
|
+
console.log("");
|
|
177
|
+
console.log(pc3.bold("Recommended packs:"));
|
|
178
|
+
for (const pack of recommended) {
|
|
179
|
+
console.log(`npx @contextforge/cli add ${pack.name}`);
|
|
169
180
|
}
|
|
170
|
-
const recommendedNames = new Set(recommended.map((pack) => pack.name));
|
|
171
|
-
const selectedNames = await checkbox({
|
|
172
|
-
message: "Which instruction packs should be installed?",
|
|
173
|
-
required: true,
|
|
174
|
-
choices: registry.map((pack) => ({
|
|
175
|
-
name: `${pack.title} (${pack.name})`,
|
|
176
|
-
value: pack.name,
|
|
177
|
-
description: pack.description,
|
|
178
|
-
checked: recommendedNames.has(pack.name)
|
|
179
|
-
}))
|
|
180
|
-
});
|
|
181
|
-
return registry.filter((pack) => selectedNames.includes(pack.name));
|
|
182
181
|
}
|
|
183
182
|
async function initCommand(options = {}) {
|
|
184
183
|
const root = process.cwd();
|
|
185
|
-
const
|
|
186
|
-
const spinner = ora("Detecting project").start();
|
|
187
|
-
const [analysis, registry] = await Promise.all([
|
|
188
|
-
detectProject(root),
|
|
189
|
-
loadRegistry2({ root, sources: registrySources })
|
|
190
|
-
]);
|
|
184
|
+
const registryUrl = resolveRegistryUrl(void 0, options);
|
|
185
|
+
const spinner = ora("Detecting project and fetching registry").start();
|
|
186
|
+
const [analysis, registry] = await Promise.all([detectProject(root), fetchRegistry2(registryUrl)]);
|
|
191
187
|
const recommended = await recommendPacks(analysis, registry);
|
|
188
|
+
const missingMandatory = missingMandatoryCorePacks(registry);
|
|
192
189
|
spinner.succeed("Project detected");
|
|
193
190
|
console.log(formatAnalysis(analysis));
|
|
194
|
-
if (
|
|
195
|
-
console.log(pc3.
|
|
196
|
-
for (const pack of recommended) {
|
|
197
|
-
console.log(`${pc3.green("OK")} ${pack.name}`);
|
|
198
|
-
}
|
|
199
|
-
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
|
-
);
|
|
191
|
+
if (missingMandatory.length > 0) {
|
|
192
|
+
console.log(pc3.yellow(`Mandatory packs missing from registry: ${missingMandatory.join(", ")}`));
|
|
204
193
|
console.log("");
|
|
205
194
|
}
|
|
206
195
|
const tools = await selectTools();
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
196
|
+
const packNames = [
|
|
197
|
+
.../* @__PURE__ */ new Set([
|
|
198
|
+
...DEFAULT_CORE_PACKS.filter((packName) => registry.packs.some((pack) => pack.name === packName))
|
|
199
|
+
])
|
|
200
|
+
];
|
|
201
|
+
for (const packName of packNames) {
|
|
202
|
+
await installPack(root, registryUrl, packName, { force: true });
|
|
203
|
+
}
|
|
204
|
+
const initialConfig = createConfig(
|
|
205
|
+
analysis,
|
|
206
|
+
packNames.map((name) => ({ name })),
|
|
207
|
+
tools,
|
|
208
|
+
registryUrl
|
|
209
|
+
);
|
|
210
|
+
const result = await syncProject(root, initialConfig);
|
|
212
211
|
console.log(pc3.green("ContextForge initialized."));
|
|
213
212
|
console.log(formatGeneratedFiles(result.generatedFiles));
|
|
213
|
+
printRecommendedAddCommands(recommended);
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
// src/commands/
|
|
217
|
-
import {
|
|
216
|
+
// src/commands/list.ts
|
|
217
|
+
import { fetchRegistry as fetchRegistry3, listRegistryPacks } from "@contextforge/core";
|
|
218
218
|
import pc4 from "picocolors";
|
|
219
|
+
async function listCommand(options = {}) {
|
|
220
|
+
const registryUrl = resolveRegistryUrl(void 0, options);
|
|
221
|
+
const registry = await fetchRegistry3(registryUrl);
|
|
222
|
+
const packs = listRegistryPacks(registry);
|
|
223
|
+
const topics = /* @__PURE__ */ new Map();
|
|
224
|
+
for (const pack of packs) {
|
|
225
|
+
const topicPacks = topics.get(pack.topic) ?? [];
|
|
226
|
+
topicPacks.push(pack);
|
|
227
|
+
topics.set(pack.topic, topicPacks);
|
|
228
|
+
}
|
|
229
|
+
console.log(pc4.bold("ContextForge Registry"));
|
|
230
|
+
console.log(pc4.dim(registryUrl));
|
|
231
|
+
console.log("");
|
|
232
|
+
for (const [topic, topicPacks] of topics) {
|
|
233
|
+
console.log(pc4.bold(topic));
|
|
234
|
+
for (const pack of topicPacks) {
|
|
235
|
+
console.log(` ${pc4.green(pack.name)} - ${pack.title}`);
|
|
236
|
+
if (pack.description) {
|
|
237
|
+
console.log(` ${pc4.dim(pack.description)}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
console.log("");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/commands/search.ts
|
|
245
|
+
import { fetchRegistry as fetchRegistry4, searchRegistryPacks } from "@contextforge/core";
|
|
246
|
+
import pc5 from "picocolors";
|
|
247
|
+
async function searchCommand(query, options = {}) {
|
|
248
|
+
const registryUrl = resolveRegistryUrl(void 0, options);
|
|
249
|
+
const registry = await fetchRegistry4(registryUrl);
|
|
250
|
+
const results = searchRegistryPacks(registry, query);
|
|
251
|
+
if (results.length === 0) {
|
|
252
|
+
console.log(pc5.yellow(`No packs found for "${query}".`));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
console.log(pc5.bold(`ContextForge packs matching "${query}"`));
|
|
256
|
+
console.log("");
|
|
257
|
+
for (const pack of results) {
|
|
258
|
+
console.log(`${pc5.green(pack.name)} ${pc5.dim(`[${pack.topic}]`)}`);
|
|
259
|
+
console.log(` ${pack.title}`);
|
|
260
|
+
if (pack.description) {
|
|
261
|
+
console.log(` ${pc5.dim(pack.description)}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/commands/sync.ts
|
|
267
|
+
import { loadConfig as loadConfig2, syncProject as syncProject2 } from "@contextforge/core";
|
|
268
|
+
import pc6 from "picocolors";
|
|
219
269
|
async function syncCommand(options = {}) {
|
|
220
270
|
const root = process.cwd();
|
|
221
271
|
const config = await loadConfig2(root);
|
|
222
|
-
const
|
|
272
|
+
const registry = resolveRegistryUrl(config, options);
|
|
273
|
+
const result = await syncProject2(root, {
|
|
223
274
|
...config,
|
|
224
|
-
|
|
275
|
+
registry
|
|
225
276
|
});
|
|
226
|
-
console.log(
|
|
277
|
+
console.log(pc6.green("ContextForge synced."));
|
|
227
278
|
console.log(formatGeneratedFiles(result.generatedFiles));
|
|
228
279
|
}
|
|
229
280
|
|
|
230
281
|
// src/index.ts
|
|
231
282
|
var program = new Command();
|
|
232
|
-
program.name("contextforge").description("Make existing codebases AI-agent ready").version("0.1.
|
|
283
|
+
program.name("contextforge").description("Make existing codebases AI-agent ready").version("0.1.6");
|
|
233
284
|
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);
|
|
285
|
+
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
286
|
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);
|
|
287
|
+
program.command("doctor").description("Check whether AI instructions match the repo").option("--registry <url>", "Static remote registry index URL", collectRegistryOption, []).action(doctorCommand);
|
|
288
|
+
program.command("list").description("List available instruction packs").option("--registry <url>", "Static remote registry index URL", collectRegistryOption, []).action(listCommand);
|
|
289
|
+
program.command("search").argument("<query>").description("Search available instruction packs").option("--registry <url>", "Static remote registry index URL", collectRegistryOption, []).action(searchCommand);
|
|
237
290
|
program.parseAsync().catch((error) => {
|
|
238
291
|
const message = error instanceof Error ? error.message : String(error);
|
|
239
|
-
console.error(
|
|
292
|
+
console.error(pc7.red(`Error: ${message}`));
|
|
240
293
|
process.exitCode = 1;
|
|
241
294
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contextforge/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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.6"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"tsx": "^4.20.0"
|