coding-friend-cli 1.7.0 → 1.9.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 +4 -0
- package/dist/chunk-7N64TDZ6.js +277 -0
- package/dist/{chunk-HSQX3PKW.js → chunk-BPLN4LDL.js} +3 -5
- package/dist/chunk-FYHHNX7K.js +35 -0
- package/dist/{chunk-MRTR7TJ4.js → chunk-HFLBFX6J.js} +2 -4
- package/dist/{chunk-WXBI2HUL.js → chunk-PGLUEN7D.js} +0 -1
- package/dist/chunk-QQ5SVZET.js +135 -0
- package/dist/chunk-RZRT7NGT.js +18 -0
- package/dist/{chunk-WHCJT7E2.js → chunk-TPRZHSFS.js} +38 -1
- package/dist/{chunk-DHPWBSF5.js → chunk-VYMXERKM.js} +18 -14
- package/dist/{chunk-6DUFTBTO.js → chunk-W5CD7WTX.js} +1 -0
- package/dist/config-VAML7F7K.js +567 -0
- package/dist/{dev-QW6VPG4G.js → dev-2GBY3GKC.js} +9 -11
- package/dist/{host-EERZVOHY.js → host-LOG5RPZ7.js} +7 -6
- package/dist/index.js +36 -13
- package/dist/init-CIEDOFNC.js +512 -0
- package/dist/{install-ORIDNWRW.js → install-D4NW3OAA.js} +7 -8
- package/dist/{mcp-3MUUQZQD.js → mcp-ORMYETXQ.js} +7 -6
- package/dist/postinstall.js +2 -2
- package/dist/session-NCQQJRSH.js +222 -0
- package/dist/{statusline-BWGI5PQ5.js → statusline-5HWRTSVL.js} +4 -5
- package/dist/{uninstall-KOAJFPD6.js → uninstall-SOHU5WGK.js} +8 -10
- package/dist/update-LA4B3LN4.js +16 -0
- package/package.json +1 -1
- package/dist/chunk-4PLV2ENL.js +0 -144
- package/dist/chunk-IUTXHCP7.js +0 -28
- package/dist/chunk-WK5YYHXM.js +0 -44
- package/dist/init-5BJVESH7.js +0 -529
- package/dist/json-2XS56OJY.js +0 -10
- package/dist/update-GW37S23M.js +0 -17
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BACK,
|
|
3
|
+
applyDocsDirChange,
|
|
4
|
+
askScope,
|
|
5
|
+
formatScopeLabel,
|
|
6
|
+
getMergedValue,
|
|
7
|
+
getScopeLabel,
|
|
8
|
+
injectBackChoice,
|
|
9
|
+
showConfigHint
|
|
10
|
+
} from "./chunk-QQ5SVZET.js";
|
|
11
|
+
import {
|
|
12
|
+
findStatuslineHookPath,
|
|
13
|
+
isStatuslineConfigured,
|
|
14
|
+
saveStatuslineConfig,
|
|
15
|
+
selectStatuslineComponents,
|
|
16
|
+
writeStatuslineSettings
|
|
17
|
+
} from "./chunk-BPLN4LDL.js";
|
|
18
|
+
import {
|
|
19
|
+
ensureShellCompletion,
|
|
20
|
+
hasShellCompletion,
|
|
21
|
+
removeShellCompletion
|
|
22
|
+
} from "./chunk-7N64TDZ6.js";
|
|
23
|
+
import {
|
|
24
|
+
ALL_COMPONENT_IDS,
|
|
25
|
+
DEFAULT_CONFIG
|
|
26
|
+
} from "./chunk-PGLUEN7D.js";
|
|
27
|
+
import {
|
|
28
|
+
run
|
|
29
|
+
} from "./chunk-UFGNO6CW.js";
|
|
30
|
+
import {
|
|
31
|
+
globalConfigPath,
|
|
32
|
+
localConfigPath,
|
|
33
|
+
mergeJson,
|
|
34
|
+
readJson,
|
|
35
|
+
resolvePath
|
|
36
|
+
} from "./chunk-TPRZHSFS.js";
|
|
37
|
+
import {
|
|
38
|
+
log
|
|
39
|
+
} from "./chunk-W5CD7WTX.js";
|
|
40
|
+
|
|
41
|
+
// src/commands/config.ts
|
|
42
|
+
import { checkbox, confirm, input, select } from "@inquirer/prompts";
|
|
43
|
+
import chalk from "chalk";
|
|
44
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
45
|
+
function getLearnFieldScope(field, globalCfg, localCfg) {
|
|
46
|
+
const inGlobal = globalCfg?.learn ? globalCfg.learn[field] !== void 0 : false;
|
|
47
|
+
const inLocal = localCfg?.learn ? localCfg.learn[field] !== void 0 : false;
|
|
48
|
+
if (inGlobal && inLocal) return "both";
|
|
49
|
+
if (inGlobal) return "global";
|
|
50
|
+
if (inLocal) return "local";
|
|
51
|
+
return "-";
|
|
52
|
+
}
|
|
53
|
+
function getMergedLearnValue(field, globalCfg, localCfg) {
|
|
54
|
+
const localVal = localCfg?.learn ? localCfg.learn[field] : void 0;
|
|
55
|
+
if (localVal !== void 0) return localVal;
|
|
56
|
+
const globalVal = globalCfg?.learn ? globalCfg.learn[field] : void 0;
|
|
57
|
+
return globalVal;
|
|
58
|
+
}
|
|
59
|
+
function writeToScope(scope, data) {
|
|
60
|
+
const targetPath = scope === "global" ? globalConfigPath() : localConfigPath();
|
|
61
|
+
mergeJson(targetPath, data);
|
|
62
|
+
log.success(`Saved to ${targetPath}`);
|
|
63
|
+
}
|
|
64
|
+
function writeLearnField(scope, field, value) {
|
|
65
|
+
const targetPath = scope === "global" ? globalConfigPath() : localConfigPath();
|
|
66
|
+
const existingConfig = readJson(targetPath);
|
|
67
|
+
const existingLearn = existingConfig?.learn ?? {};
|
|
68
|
+
const updated = { ...existingLearn, [field]: value };
|
|
69
|
+
mergeJson(targetPath, { learn: updated });
|
|
70
|
+
log.success(`Saved to ${targetPath}`);
|
|
71
|
+
}
|
|
72
|
+
async function editDocsDir(globalCfg, localCfg) {
|
|
73
|
+
const currentValue = getMergedValue("docsDir", globalCfg, localCfg);
|
|
74
|
+
if (currentValue) {
|
|
75
|
+
log.dim(`Current: ${currentValue}`);
|
|
76
|
+
}
|
|
77
|
+
const value = await input({
|
|
78
|
+
message: "Docs folder name:",
|
|
79
|
+
default: currentValue ?? DEFAULT_CONFIG.docsDir,
|
|
80
|
+
validate: (val) => {
|
|
81
|
+
if (!val) return "Folder name cannot be empty";
|
|
82
|
+
if (val.includes("/") || val.includes("\\"))
|
|
83
|
+
return "Must be a folder name, not a path (no slashes)";
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const scope = await askScope();
|
|
88
|
+
if (scope === "back") return;
|
|
89
|
+
applyDocsDirChange(value, currentValue, scope);
|
|
90
|
+
writeToScope(scope, { docsDir: value });
|
|
91
|
+
}
|
|
92
|
+
async function editLanguage(globalCfg, localCfg) {
|
|
93
|
+
const currentValue = getMergedValue("language", globalCfg, localCfg);
|
|
94
|
+
if (currentValue) {
|
|
95
|
+
log.dim(`Current: ${currentValue}`);
|
|
96
|
+
}
|
|
97
|
+
const choice = await select({
|
|
98
|
+
message: "What language should generated docs be written in?",
|
|
99
|
+
choices: injectBackChoice(
|
|
100
|
+
[
|
|
101
|
+
{ name: "English", value: "en" },
|
|
102
|
+
{ name: "Vietnamese", value: "vi" },
|
|
103
|
+
{ name: "Other", value: "_other" }
|
|
104
|
+
],
|
|
105
|
+
"Back"
|
|
106
|
+
)
|
|
107
|
+
});
|
|
108
|
+
if (choice === BACK) return;
|
|
109
|
+
let lang = choice;
|
|
110
|
+
if (choice === "_other") {
|
|
111
|
+
lang = await input({ message: "Enter language name:" });
|
|
112
|
+
if (!lang) lang = "en";
|
|
113
|
+
}
|
|
114
|
+
const scope = await askScope();
|
|
115
|
+
if (scope === "back") return;
|
|
116
|
+
writeToScope(scope, { language: lang });
|
|
117
|
+
}
|
|
118
|
+
async function editLearnOutputDir(globalCfg, localCfg) {
|
|
119
|
+
const currentValue = getMergedLearnValue("outputDir", globalCfg, localCfg);
|
|
120
|
+
if (currentValue) {
|
|
121
|
+
log.dim(`Current: ${currentValue}`);
|
|
122
|
+
}
|
|
123
|
+
const locationChoice = await select({
|
|
124
|
+
message: "Where to store learning docs?",
|
|
125
|
+
choices: [
|
|
126
|
+
{ name: "In this project (docs/learn/)", value: "local" },
|
|
127
|
+
{ name: "A separate folder", value: "external" }
|
|
128
|
+
]
|
|
129
|
+
});
|
|
130
|
+
let outputDir = "docs/learn";
|
|
131
|
+
if (locationChoice === "external") {
|
|
132
|
+
outputDir = await input({
|
|
133
|
+
message: "Enter path (absolute or ~/...):",
|
|
134
|
+
default: currentValue ?? void 0,
|
|
135
|
+
validate: (val) => val.length > 0 ? true : "Path cannot be empty"
|
|
136
|
+
});
|
|
137
|
+
const resolved = resolvePath(outputDir);
|
|
138
|
+
if (!existsSync(resolved)) {
|
|
139
|
+
const create = await confirm({
|
|
140
|
+
message: `Folder ${resolved} doesn't exist. Create it?`,
|
|
141
|
+
default: true
|
|
142
|
+
});
|
|
143
|
+
if (create) {
|
|
144
|
+
run("mkdir", ["-p", resolved]);
|
|
145
|
+
log.success(`Created ${resolved}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const scope = await askScope();
|
|
150
|
+
if (scope === "back") return;
|
|
151
|
+
writeLearnField(scope, "outputDir", outputDir);
|
|
152
|
+
}
|
|
153
|
+
async function editLearnLanguage(globalCfg, localCfg) {
|
|
154
|
+
const currentValue = getMergedLearnValue("language", globalCfg, localCfg);
|
|
155
|
+
if (currentValue) {
|
|
156
|
+
log.dim(`Current: ${currentValue}`);
|
|
157
|
+
}
|
|
158
|
+
const choice = await select({
|
|
159
|
+
message: "What language should /cf-learn notes be written in?",
|
|
160
|
+
choices: injectBackChoice(
|
|
161
|
+
[
|
|
162
|
+
{ name: "English", value: "en" },
|
|
163
|
+
{ name: "Vietnamese", value: "vi" },
|
|
164
|
+
{ name: "Other", value: "_other" }
|
|
165
|
+
],
|
|
166
|
+
"Back"
|
|
167
|
+
)
|
|
168
|
+
});
|
|
169
|
+
if (choice === BACK) return;
|
|
170
|
+
let lang = choice;
|
|
171
|
+
if (choice === "_other") {
|
|
172
|
+
lang = await input({ message: "Enter language name:" });
|
|
173
|
+
if (!lang) lang = "en";
|
|
174
|
+
}
|
|
175
|
+
const scope = await askScope();
|
|
176
|
+
if (scope === "back") return;
|
|
177
|
+
writeLearnField(scope, "language", lang);
|
|
178
|
+
}
|
|
179
|
+
async function editLearnCategories(globalCfg, localCfg) {
|
|
180
|
+
const existingCats = getMergedLearnValue(
|
|
181
|
+
"categories",
|
|
182
|
+
globalCfg,
|
|
183
|
+
localCfg
|
|
184
|
+
);
|
|
185
|
+
const defaultNames = DEFAULT_CONFIG.learn.categories.map((c) => c.name).join(", ");
|
|
186
|
+
const catChoices = [
|
|
187
|
+
{ name: `Use defaults (${defaultNames})`, value: "defaults" }
|
|
188
|
+
];
|
|
189
|
+
if (existingCats && existingCats.length > 0) {
|
|
190
|
+
const existingNames = existingCats.map((c) => c.name).join(", ");
|
|
191
|
+
catChoices.push({
|
|
192
|
+
name: `Keep current (${existingNames})`,
|
|
193
|
+
value: "existing"
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catChoices.push({ name: "Customize", value: "custom" });
|
|
197
|
+
const catChoice = await select({
|
|
198
|
+
message: "Categories for organizing learning docs?",
|
|
199
|
+
choices: catChoices
|
|
200
|
+
});
|
|
201
|
+
let categories = DEFAULT_CONFIG.learn.categories;
|
|
202
|
+
if (catChoice === "existing" && existingCats) {
|
|
203
|
+
categories = existingCats;
|
|
204
|
+
} else if (catChoice === "custom") {
|
|
205
|
+
console.log();
|
|
206
|
+
if (existingCats && existingCats.length > 0) {
|
|
207
|
+
console.log("Current categories:");
|
|
208
|
+
for (const c of existingCats) {
|
|
209
|
+
log.dim(` ${c.name}: ${c.description}`);
|
|
210
|
+
}
|
|
211
|
+
console.log();
|
|
212
|
+
}
|
|
213
|
+
console.log(
|
|
214
|
+
'Enter categories (format: "name: description"). Empty line to finish.'
|
|
215
|
+
);
|
|
216
|
+
console.log();
|
|
217
|
+
const customCats = [];
|
|
218
|
+
let keepGoing = true;
|
|
219
|
+
while (keepGoing) {
|
|
220
|
+
const line = await input({
|
|
221
|
+
message: `Category ${customCats.length + 1}:`
|
|
222
|
+
});
|
|
223
|
+
if (!line) {
|
|
224
|
+
keepGoing = false;
|
|
225
|
+
} else {
|
|
226
|
+
const [name, ...descParts] = line.split(":");
|
|
227
|
+
customCats.push({
|
|
228
|
+
name: name.trim(),
|
|
229
|
+
description: descParts.join(":").trim() || name.trim()
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (customCats.length > 0) categories = customCats;
|
|
234
|
+
}
|
|
235
|
+
const scope = await askScope();
|
|
236
|
+
if (scope === "back") return;
|
|
237
|
+
writeLearnField(scope, "categories", categories);
|
|
238
|
+
}
|
|
239
|
+
async function editLearnAutoCommit(globalCfg, localCfg) {
|
|
240
|
+
const currentValue = getMergedLearnValue(
|
|
241
|
+
"autoCommit",
|
|
242
|
+
globalCfg,
|
|
243
|
+
localCfg
|
|
244
|
+
);
|
|
245
|
+
if (currentValue !== void 0) {
|
|
246
|
+
log.dim(`Current: ${currentValue}`);
|
|
247
|
+
}
|
|
248
|
+
const value = await confirm({
|
|
249
|
+
message: "Auto-commit learning docs to git after each /cf-learn?",
|
|
250
|
+
default: currentValue ?? false
|
|
251
|
+
});
|
|
252
|
+
const scope = await askScope();
|
|
253
|
+
if (scope === "back") return;
|
|
254
|
+
writeLearnField(scope, "autoCommit", value);
|
|
255
|
+
}
|
|
256
|
+
async function editLearnReadmeIndex(globalCfg, localCfg) {
|
|
257
|
+
const currentValue = getMergedLearnValue(
|
|
258
|
+
"readmeIndex",
|
|
259
|
+
globalCfg,
|
|
260
|
+
localCfg
|
|
261
|
+
);
|
|
262
|
+
if (currentValue !== void 0) {
|
|
263
|
+
log.dim(`Current: ${currentValue}`);
|
|
264
|
+
}
|
|
265
|
+
const indexChoice = await select({
|
|
266
|
+
message: "How should learning docs be indexed?",
|
|
267
|
+
choices: [
|
|
268
|
+
{ name: "No index", value: "none" },
|
|
269
|
+
{ name: "Single README at root", value: "single" },
|
|
270
|
+
{ name: "Per-category READMEs", value: "per-category" }
|
|
271
|
+
]
|
|
272
|
+
});
|
|
273
|
+
let readmeIndex = false;
|
|
274
|
+
if (indexChoice === "single") readmeIndex = true;
|
|
275
|
+
else if (indexChoice === "per-category") readmeIndex = "per-category";
|
|
276
|
+
const scope = await askScope();
|
|
277
|
+
if (scope === "back") return;
|
|
278
|
+
writeLearnField(scope, "readmeIndex", readmeIndex);
|
|
279
|
+
}
|
|
280
|
+
async function learnSubMenu() {
|
|
281
|
+
while (true) {
|
|
282
|
+
const globalCfg = readJson(globalConfigPath());
|
|
283
|
+
const localCfg = readJson(localConfigPath());
|
|
284
|
+
const outputDirScope = getLearnFieldScope("outputDir", globalCfg, localCfg);
|
|
285
|
+
const outputDirVal = getMergedLearnValue(
|
|
286
|
+
"outputDir",
|
|
287
|
+
globalCfg,
|
|
288
|
+
localCfg
|
|
289
|
+
);
|
|
290
|
+
const langScope = getLearnFieldScope("language", globalCfg, localCfg);
|
|
291
|
+
const langVal = getMergedLearnValue("language", globalCfg, localCfg);
|
|
292
|
+
const catScope = getLearnFieldScope("categories", globalCfg, localCfg);
|
|
293
|
+
const autoCommitScope = getLearnFieldScope(
|
|
294
|
+
"autoCommit",
|
|
295
|
+
globalCfg,
|
|
296
|
+
localCfg
|
|
297
|
+
);
|
|
298
|
+
const autoCommitVal = getMergedLearnValue(
|
|
299
|
+
"autoCommit",
|
|
300
|
+
globalCfg,
|
|
301
|
+
localCfg
|
|
302
|
+
);
|
|
303
|
+
const readmeScope = getLearnFieldScope("readmeIndex", globalCfg, localCfg);
|
|
304
|
+
const readmeVal = getMergedLearnValue(
|
|
305
|
+
"readmeIndex",
|
|
306
|
+
globalCfg,
|
|
307
|
+
localCfg
|
|
308
|
+
);
|
|
309
|
+
const choice = await select({
|
|
310
|
+
message: "Learn settings:",
|
|
311
|
+
choices: injectBackChoice(
|
|
312
|
+
[
|
|
313
|
+
{
|
|
314
|
+
name: `Output dir ${formatScopeLabel(outputDirScope)}${outputDirVal ? ` (${outputDirVal})` : ""}`,
|
|
315
|
+
value: "outputDir"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: `Language ${formatScopeLabel(langScope)}${langVal ? ` (${langVal})` : ""}`,
|
|
319
|
+
value: "language"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: `Categories ${formatScopeLabel(catScope)}`,
|
|
323
|
+
value: "categories"
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: `Auto-commit ${formatScopeLabel(autoCommitScope)}${autoCommitVal !== void 0 ? ` (${autoCommitVal})` : ""}`,
|
|
327
|
+
value: "autoCommit"
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: `README index ${formatScopeLabel(readmeScope)}${readmeVal !== void 0 ? ` (${readmeVal})` : ""}`,
|
|
331
|
+
value: "readmeIndex"
|
|
332
|
+
}
|
|
333
|
+
],
|
|
334
|
+
"Back"
|
|
335
|
+
)
|
|
336
|
+
});
|
|
337
|
+
if (choice === BACK) return;
|
|
338
|
+
switch (choice) {
|
|
339
|
+
case "outputDir":
|
|
340
|
+
await editLearnOutputDir(globalCfg, localCfg);
|
|
341
|
+
break;
|
|
342
|
+
case "language":
|
|
343
|
+
await editLearnLanguage(globalCfg, localCfg);
|
|
344
|
+
break;
|
|
345
|
+
case "categories":
|
|
346
|
+
await editLearnCategories(globalCfg, localCfg);
|
|
347
|
+
break;
|
|
348
|
+
case "autoCommit":
|
|
349
|
+
await editLearnAutoCommit(globalCfg, localCfg);
|
|
350
|
+
break;
|
|
351
|
+
case "readmeIndex":
|
|
352
|
+
await editLearnReadmeIndex(globalCfg, localCfg);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async function editStatusline() {
|
|
358
|
+
const hookResult = findStatuslineHookPath();
|
|
359
|
+
if (!hookResult) {
|
|
360
|
+
log.error(
|
|
361
|
+
"coding-friend plugin not found in cache. Install it first via Claude Code."
|
|
362
|
+
);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
log.info(`Found plugin ${chalk.green(`v${hookResult.version}`)}`);
|
|
366
|
+
if (isStatuslineConfigured()) {
|
|
367
|
+
log.dim("Statusline already configured.");
|
|
368
|
+
const overwrite = await confirm({
|
|
369
|
+
message: "Reconfigure statusline?",
|
|
370
|
+
default: true
|
|
371
|
+
});
|
|
372
|
+
if (!overwrite) return;
|
|
373
|
+
}
|
|
374
|
+
const components = await selectStatuslineComponents();
|
|
375
|
+
saveStatuslineConfig(components);
|
|
376
|
+
writeStatuslineSettings(hookResult.hookPath);
|
|
377
|
+
log.success("Statusline configured!");
|
|
378
|
+
if (components.length < ALL_COMPONENT_IDS.length) {
|
|
379
|
+
log.dim(`Showing: ${components.join(", ")}`);
|
|
380
|
+
} else {
|
|
381
|
+
log.dim("Showing all components.");
|
|
382
|
+
}
|
|
383
|
+
log.dim("Restart Claude Code (or start a new session) to see it.");
|
|
384
|
+
}
|
|
385
|
+
var GITIGNORE_START = "# >>> coding-friend managed";
|
|
386
|
+
var GITIGNORE_END = "# <<< coding-friend managed";
|
|
387
|
+
function escapeRegExp(str) {
|
|
388
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
389
|
+
}
|
|
390
|
+
async function editGitignore(globalCfg, localCfg) {
|
|
391
|
+
const docsDir = localCfg?.docsDir ?? globalCfg?.docsDir ?? DEFAULT_CONFIG.docsDir;
|
|
392
|
+
const allEntries = [
|
|
393
|
+
`${docsDir}/plans/`,
|
|
394
|
+
`${docsDir}/memory/`,
|
|
395
|
+
`${docsDir}/research/`,
|
|
396
|
+
`${docsDir}/learn/`,
|
|
397
|
+
`${docsDir}/sessions/`,
|
|
398
|
+
".coding-friend/"
|
|
399
|
+
];
|
|
400
|
+
const existing = existsSync(".gitignore") ? readFileSync(".gitignore", "utf-8") : "";
|
|
401
|
+
const hasBlock = existing.includes(GITIGNORE_START) || existing.includes("# coding-friend");
|
|
402
|
+
if (hasBlock) {
|
|
403
|
+
log.dim(".gitignore already has a coding-friend block.");
|
|
404
|
+
}
|
|
405
|
+
const choice = await select({
|
|
406
|
+
message: "Add coding-friend artifacts to .gitignore?",
|
|
407
|
+
choices: injectBackChoice(
|
|
408
|
+
[
|
|
409
|
+
{ name: "Yes, ignore all", value: "all" },
|
|
410
|
+
{ name: "Partial \u2014 pick which to ignore", value: "partial" },
|
|
411
|
+
{ name: "No \u2014 keep everything tracked", value: "none" }
|
|
412
|
+
],
|
|
413
|
+
"Back"
|
|
414
|
+
)
|
|
415
|
+
});
|
|
416
|
+
if (choice === BACK) return;
|
|
417
|
+
if (choice === "none") {
|
|
418
|
+
log.dim("Skipped .gitignore config.");
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
let entries = allEntries;
|
|
422
|
+
if (choice === "partial") {
|
|
423
|
+
entries = await checkbox({
|
|
424
|
+
message: "Which folders to ignore?",
|
|
425
|
+
choices: allEntries.map((e) => ({ name: e, value: e }))
|
|
426
|
+
});
|
|
427
|
+
if (entries.length === 0) {
|
|
428
|
+
log.dim("Nothing selected.");
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const block = `${GITIGNORE_START}
|
|
433
|
+
${entries.join("\n")}
|
|
434
|
+
${GITIGNORE_END}`;
|
|
435
|
+
const managedBlockRe = new RegExp(
|
|
436
|
+
`${escapeRegExp(GITIGNORE_START)}[\\s\\S]*?${escapeRegExp(GITIGNORE_END)}`
|
|
437
|
+
);
|
|
438
|
+
const legacyBlockRe = /# coding-friend\n([\w/.]+\n)*/;
|
|
439
|
+
let updated;
|
|
440
|
+
if (managedBlockRe.test(existing)) {
|
|
441
|
+
updated = existing.replace(managedBlockRe, block);
|
|
442
|
+
log.success(`Updated .gitignore: ${entries.join(", ")}`);
|
|
443
|
+
} else if (legacyBlockRe.test(existing)) {
|
|
444
|
+
updated = existing.replace(legacyBlockRe, block);
|
|
445
|
+
log.success(`Migrated .gitignore block: ${entries.join(", ")}`);
|
|
446
|
+
} else {
|
|
447
|
+
updated = existing.trimEnd() + "\n\n" + block + "\n";
|
|
448
|
+
log.success(`Added to .gitignore: ${entries.join(", ")}`);
|
|
449
|
+
}
|
|
450
|
+
writeFileSync(".gitignore", updated);
|
|
451
|
+
}
|
|
452
|
+
async function editShellCompletion() {
|
|
453
|
+
const installed = hasShellCompletion();
|
|
454
|
+
if (installed) {
|
|
455
|
+
const choice = await select({
|
|
456
|
+
message: "Shell tab completion is already installed.",
|
|
457
|
+
choices: injectBackChoice(
|
|
458
|
+
[
|
|
459
|
+
{ name: "Update to latest", value: "update" },
|
|
460
|
+
{ name: "Remove", value: "remove" }
|
|
461
|
+
],
|
|
462
|
+
"Back"
|
|
463
|
+
)
|
|
464
|
+
});
|
|
465
|
+
if (choice === BACK) return;
|
|
466
|
+
if (choice === "remove") {
|
|
467
|
+
if (removeShellCompletion()) {
|
|
468
|
+
log.success("Shell completion removed.");
|
|
469
|
+
} else {
|
|
470
|
+
log.warn("Could not remove shell completion.");
|
|
471
|
+
}
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
ensureShellCompletion({ silent: false });
|
|
475
|
+
} else {
|
|
476
|
+
ensureShellCompletion({ silent: false });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
var em = chalk.hex("#10b981");
|
|
480
|
+
async function configCommand() {
|
|
481
|
+
console.log();
|
|
482
|
+
console.log(em(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
483
|
+
console.log(
|
|
484
|
+
em(" \u2502 ") + "\u2726" + em(" ") + chalk.bold.white("Coding Friend") + em(" \u2726 \u2502")
|
|
485
|
+
);
|
|
486
|
+
console.log(em(" \u2502 ") + chalk.dim("Config") + em(" \u2502"));
|
|
487
|
+
console.log(em(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
488
|
+
console.log(em(" \u2570\u2500\u25B8"));
|
|
489
|
+
console.log();
|
|
490
|
+
showConfigHint();
|
|
491
|
+
while (true) {
|
|
492
|
+
const globalCfg = readJson(globalConfigPath());
|
|
493
|
+
const localCfg = readJson(localConfigPath());
|
|
494
|
+
const docsDirScope = getScopeLabel("docsDir", globalCfg, localCfg);
|
|
495
|
+
const docsDirVal = getMergedValue("docsDir", globalCfg, localCfg);
|
|
496
|
+
const langScope = getScopeLabel("language", globalCfg, localCfg);
|
|
497
|
+
const langVal = getMergedValue("language", globalCfg, localCfg);
|
|
498
|
+
const learnScope = getScopeLabel("learn", globalCfg, localCfg);
|
|
499
|
+
const statuslineStatus = isStatuslineConfigured() ? chalk.green("configured") : chalk.yellow("not configured");
|
|
500
|
+
const completionStatus = hasShellCompletion() ? chalk.green("installed") : chalk.yellow("not installed");
|
|
501
|
+
const choice = await select({
|
|
502
|
+
message: "What to configure?",
|
|
503
|
+
choices: injectBackChoice(
|
|
504
|
+
[
|
|
505
|
+
{
|
|
506
|
+
name: `docsDir ${formatScopeLabel(docsDirScope)}${docsDirVal ? ` (${docsDirVal})` : ""}`,
|
|
507
|
+
value: "docsDir",
|
|
508
|
+
description: " Top-level folder name for plans, memory, research, and sessions"
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: `Docs language ${formatScopeLabel(langScope)}${langVal ? ` (${langVal})` : ""}`,
|
|
512
|
+
value: "language",
|
|
513
|
+
description: " Language for /cf-plan, /cf-ask, /cf-remember generated docs"
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: `Learn settings ${formatScopeLabel(learnScope)}`,
|
|
517
|
+
value: "learn",
|
|
518
|
+
description: " Output dir, language, categories, auto-commit, README index"
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: `Statusline (${statuslineStatus})`,
|
|
522
|
+
value: "statusline",
|
|
523
|
+
description: " Choose which components to show in the Claude Code statusline"
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
name: `.gitignore`,
|
|
527
|
+
value: "gitignore",
|
|
528
|
+
description: " Add or update coding-friend artifacts in .gitignore"
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
name: `Shell completion (${completionStatus})`,
|
|
532
|
+
value: "completion",
|
|
533
|
+
description: " Install, update, or remove tab completion for the cf command"
|
|
534
|
+
}
|
|
535
|
+
],
|
|
536
|
+
"Exit"
|
|
537
|
+
)
|
|
538
|
+
});
|
|
539
|
+
if (choice === BACK) {
|
|
540
|
+
process.exit(0);
|
|
541
|
+
}
|
|
542
|
+
switch (choice) {
|
|
543
|
+
case "docsDir":
|
|
544
|
+
await editDocsDir(globalCfg, localCfg);
|
|
545
|
+
break;
|
|
546
|
+
case "language":
|
|
547
|
+
await editLanguage(globalCfg, localCfg);
|
|
548
|
+
break;
|
|
549
|
+
case "learn":
|
|
550
|
+
await learnSubMenu();
|
|
551
|
+
break;
|
|
552
|
+
case "statusline":
|
|
553
|
+
await editStatusline();
|
|
554
|
+
break;
|
|
555
|
+
case "gitignore":
|
|
556
|
+
await editGitignore(globalCfg, localCfg);
|
|
557
|
+
break;
|
|
558
|
+
case "completion":
|
|
559
|
+
await editShellCompletion();
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
console.log();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
export {
|
|
566
|
+
configCommand
|
|
567
|
+
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isMarketplaceRegistered,
|
|
3
3
|
isPluginInstalled
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HFLBFX6J.js";
|
|
5
5
|
import {
|
|
6
6
|
ensureStatusline
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BPLN4LDL.js";
|
|
8
8
|
import {
|
|
9
9
|
ensureShellCompletion
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-7N64TDZ6.js";
|
|
11
|
+
import "./chunk-PGLUEN7D.js";
|
|
12
12
|
import {
|
|
13
13
|
commandExists,
|
|
14
14
|
run
|
|
@@ -16,15 +16,13 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
devStatePath,
|
|
18
18
|
knownMarketplacesPath,
|
|
19
|
-
pluginCachePath
|
|
20
|
-
} from "./chunk-WHCJT7E2.js";
|
|
21
|
-
import {
|
|
22
|
-
log
|
|
23
|
-
} from "./chunk-6DUFTBTO.js";
|
|
24
|
-
import {
|
|
19
|
+
pluginCachePath,
|
|
25
20
|
readJson,
|
|
26
21
|
writeJson
|
|
27
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-TPRZHSFS.js";
|
|
23
|
+
import {
|
|
24
|
+
log
|
|
25
|
+
} from "./chunk-W5CD7WTX.js";
|
|
28
26
|
|
|
29
27
|
// src/commands/dev.ts
|
|
30
28
|
import {
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
getLibPath
|
|
2
|
+
getLibPath
|
|
3
|
+
} from "./chunk-RZRT7NGT.js";
|
|
4
|
+
import {
|
|
3
5
|
resolveDocsDir
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-FYHHNX7K.js";
|
|
7
|
+
import "./chunk-PGLUEN7D.js";
|
|
6
8
|
import {
|
|
7
9
|
run,
|
|
8
10
|
streamExec
|
|
9
11
|
} from "./chunk-UFGNO6CW.js";
|
|
10
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-TPRZHSFS.js";
|
|
11
13
|
import {
|
|
12
14
|
log
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-IUTXHCP7.js";
|
|
15
|
+
} from "./chunk-W5CD7WTX.js";
|
|
15
16
|
|
|
16
17
|
// src/commands/host.ts
|
|
17
18
|
import { existsSync, readdirSync } from "fs";
|