@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.
Files changed (2) hide show
  1. package/dist/index.js +150 -97
  2. 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 pc5 from "picocolors";
5
+ import pc7 from "picocolors";
6
6
 
7
7
  // src/commands/add.ts
8
8
  import {
9
- addPackToConfig,
10
- cacheRemotePacks,
11
- findPack,
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 { DEFAULT_REGISTRY_SOURCES } from "@contextforge/core";
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?.registries?.length ? config.registries : DEFAULT_REGISTRY_SOURCES;
80
- return [.../* @__PURE__ */ new Set([...configured, ...envRegistries(), ...options?.registry ?? []])];
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 loadConfig(root);
87
- const registrySources = resolveRegistrySources(config, options);
88
- const registry = await loadRegistry({ root, sources: registrySources });
89
- const pack = findPack(registry, packName);
90
- if (!pack) {
91
- throw new Error(`Unknown pack "${packName}".`);
92
- }
93
- const packageJson = await readPackageJson(root);
94
- const compatible = await packMatchesProject(pack, root, packageJson);
95
- const alreadyInstalled = config.packs.includes(packName);
96
- const nextConfig = {
97
- ...addPackToConfig(config, packName),
98
- registries: registrySources
99
- };
100
- if (!compatible) {
101
- console.log(
102
- pc2.yellow(
103
- `${packName} does not match the current project detection hints. Adding it anyway; run doctor if this was intentional.`
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.yellow(`${packName} was already installed. Synced generated files.`) : pc2.green(`Added ${packName}.`)
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 report = await doctorProject(process.cwd());
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
- loadRegistry as loadRegistry2,
137
+ fetchRegistry as fetchRegistry2,
138
+ installPack,
139
+ missingMandatoryCorePacks,
134
140
  recommendPacks,
135
- saveConfig as saveConfig2,
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
- return checkbox({
158
+ const selected = await checkbox({
154
159
  message: "Which AI tools should ContextForge configure?",
155
160
  required: true,
156
- choices: DEFAULT_TOOLS.map((tool) => ({
157
- name: TOOL_LABELS[tool],
158
- value: tool,
159
- checked: true
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
- async function selectPacks(registry, recommended) {
164
- if (registry.length === 0) {
165
- return [];
172
+ function printRecommendedAddCommands(recommended) {
173
+ if (recommended.length === 0) {
174
+ return;
166
175
  }
167
- if (!canPrompt()) {
168
- return recommended;
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 registrySources = resolveRegistrySources(void 0, options);
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 (recommended.length > 0) {
195
- console.log(pc3.bold("Recommended packs:"));
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 packs = await selectPacks(registry, recommended);
208
- await cacheRemotePacks2(root, packs);
209
- const initialConfig = createConfig(analysis, packs, tools, registrySources);
210
- await saveConfig2(root, initialConfig);
211
- const result = await syncProject2(root, initialConfig);
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/sync.ts
217
- import { loadConfig as loadConfig2, syncProject as syncProject3 } from "@contextforge/core";
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 result = await syncProject3(root, {
272
+ const registry = resolveRegistryUrl(config, options);
273
+ const result = await syncProject2(root, {
223
274
  ...config,
224
- registries: resolveRegistrySources(config, options)
275
+ registry
225
276
  });
226
- console.log(pc4.green("ContextForge synced."));
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.2");
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(pc5.red(`Error: ${message}`));
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.2",
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.2"
16
+ "@contextforge/core": "0.1.6"
17
17
  },
18
18
  "devDependencies": {
19
19
  "tsx": "^4.20.0"