coding-friend-cli 1.15.0 → 1.17.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 +12 -0
- package/dist/{chunk-PYRGNY5P.js → chunk-C5LYVVEI.js} +9 -1
- package/dist/{chunk-X5WEODUD.js → chunk-CYQU33FY.js} +1 -0
- package/dist/{chunk-ITL5TY3B.js → chunk-G6CEEMAR.js} +3 -3
- package/dist/{chunk-4DB4XTSL.js → chunk-KTX4MGMR.js} +15 -1
- package/dist/{chunk-KJUGTLPQ.js → chunk-YO6JKGR3.js} +38 -2
- package/dist/{config-UQ742WPQ.js → config-LZFXXOI4.js} +276 -14
- package/dist/{dev-WJ5QQ35B.js → dev-R3IYWZ3M.js} +2 -2
- package/dist/{disable-AOZ7FLZD.js → disable-R6K5YJN4.js} +2 -2
- package/dist/{enable-MJVTT3RU.js → enable-HF4PYVJN.js} +2 -2
- package/dist/{host-NA7LZ4HX.js → host-SYZH3FVC.js} +4 -4
- package/dist/index.js +78 -18
- package/dist/{init-AHIEQ27W.js → init-YK6YRTOT.js} +271 -23
- package/dist/{install-EIN7Z5V3.js → install-Q4PWEU43.js} +4 -4
- package/dist/{mcp-DLS3J6QJ.js → mcp-TBEDYELW.js} +4 -4
- package/dist/memory-7RM67ZLS.js +668 -0
- package/dist/postinstall.js +1 -1
- package/dist/{session-E3CZJJZQ.js → session-H4XW2WXH.js} +1 -1
- package/dist/{statusline-6HQCDWBD.js → statusline-6Y2EBAFQ.js} +1 -1
- package/dist/{uninstall-2IOZZERP.js → uninstall-3PSUDGI4.js} +3 -3
- package/dist/{update-IZ5UEKZN.js → update-WL6SFGGO.js} +4 -4
- package/lib/cf-memory/CHANGELOG.md +15 -0
- package/lib/cf-memory/README.md +284 -0
- package/lib/cf-memory/package-lock.json +2790 -0
- package/lib/cf-memory/package.json +31 -0
- package/lib/cf-memory/scripts/migrate-frontmatter.ts +134 -0
- package/lib/cf-memory/src/__tests__/daemon-e2e.test.ts +223 -0
- package/lib/cf-memory/src/__tests__/daemon.test.ts +407 -0
- package/lib/cf-memory/src/__tests__/dedup.test.ts +103 -0
- package/lib/cf-memory/src/__tests__/embeddings.test.ts +292 -0
- package/lib/cf-memory/src/__tests__/lazy-install.test.ts +210 -0
- package/lib/cf-memory/src/__tests__/markdown-backend.test.ts +410 -0
- package/lib/cf-memory/src/__tests__/migration.test.ts +255 -0
- package/lib/cf-memory/src/__tests__/migrations.test.ts +288 -0
- package/lib/cf-memory/src/__tests__/minisearch-backend.test.ts +262 -0
- package/lib/cf-memory/src/__tests__/ollama.test.ts +48 -0
- package/lib/cf-memory/src/__tests__/schema.test.ts +128 -0
- package/lib/cf-memory/src/__tests__/search.test.ts +115 -0
- package/lib/cf-memory/src/__tests__/temporal-decay.test.ts +54 -0
- package/lib/cf-memory/src/__tests__/tier.test.ts +293 -0
- package/lib/cf-memory/src/__tests__/tools.test.ts +83 -0
- package/lib/cf-memory/src/backends/markdown.ts +318 -0
- package/lib/cf-memory/src/backends/minisearch.ts +203 -0
- package/lib/cf-memory/src/backends/sqlite/embeddings.ts +286 -0
- package/lib/cf-memory/src/backends/sqlite/index.ts +549 -0
- package/lib/cf-memory/src/backends/sqlite/migrations.ts +188 -0
- package/lib/cf-memory/src/backends/sqlite/schema.ts +120 -0
- package/lib/cf-memory/src/backends/sqlite/search.ts +296 -0
- package/lib/cf-memory/src/bin/cf-memory.ts +2 -0
- package/lib/cf-memory/src/daemon/entry.ts +99 -0
- package/lib/cf-memory/src/daemon/process.ts +220 -0
- package/lib/cf-memory/src/daemon/server.ts +166 -0
- package/lib/cf-memory/src/daemon/watcher.ts +90 -0
- package/lib/cf-memory/src/index.ts +45 -0
- package/lib/cf-memory/src/lib/backend.ts +23 -0
- package/lib/cf-memory/src/lib/daemon-client.ts +163 -0
- package/lib/cf-memory/src/lib/dedup.ts +80 -0
- package/lib/cf-memory/src/lib/lazy-install.ts +274 -0
- package/lib/cf-memory/src/lib/ollama.ts +76 -0
- package/lib/cf-memory/src/lib/temporal-decay.ts +19 -0
- package/lib/cf-memory/src/lib/tier.ts +107 -0
- package/lib/cf-memory/src/lib/types.ts +109 -0
- package/lib/cf-memory/src/resources/index.ts +62 -0
- package/lib/cf-memory/src/server.ts +20 -0
- package/lib/cf-memory/src/tools/delete.ts +38 -0
- package/lib/cf-memory/src/tools/list.ts +38 -0
- package/lib/cf-memory/src/tools/retrieve.ts +52 -0
- package/lib/cf-memory/src/tools/search.ts +47 -0
- package/lib/cf-memory/src/tools/store.ts +70 -0
- package/lib/cf-memory/src/tools/update.ts +62 -0
- package/lib/cf-memory/tsconfig.json +15 -0
- package/lib/cf-memory/vitest.config.ts +7 -0
- package/lib/learn-host/CHANGELOG.md +4 -1
- package/lib/learn-host/package.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,35 +14,35 @@ program.name("cf").description(
|
|
|
14
14
|
"coding-friend CLI \u2014 host learning docs, setup MCP, init projects"
|
|
15
15
|
).version(pkg.version, "-v, --version");
|
|
16
16
|
program.command("install").description("Install the Coding Friend plugin into Claude Code").option("--user", "Install at user scope (all projects)").option("--global", "Install at user scope (all projects)").option("--project", "Install at project scope (shared via git)").option("--local", "Install at local scope (this machine only)").action(async (opts) => {
|
|
17
|
-
const { installCommand } = await import("./install-
|
|
17
|
+
const { installCommand } = await import("./install-Q4PWEU43.js");
|
|
18
18
|
await installCommand(opts);
|
|
19
19
|
});
|
|
20
20
|
program.command("uninstall").description("Uninstall the Coding Friend plugin from Claude Code").option("--user", "Uninstall from user scope (all projects)").option("--global", "Uninstall from user scope (all projects)").option("--project", "Uninstall from project scope").option("--local", "Uninstall from local scope").action(async (opts) => {
|
|
21
|
-
const { uninstallCommand } = await import("./uninstall-
|
|
21
|
+
const { uninstallCommand } = await import("./uninstall-3PSUDGI4.js");
|
|
22
22
|
await uninstallCommand(opts);
|
|
23
23
|
});
|
|
24
24
|
program.command("disable").description("Disable the Coding Friend plugin without uninstalling").option("--user", "Disable at user scope (all projects)").option("--global", "Disable at user scope (all projects)").option("--project", "Disable at project scope").option("--local", "Disable at local scope").action(async (opts) => {
|
|
25
|
-
const { disableCommand } = await import("./disable-
|
|
25
|
+
const { disableCommand } = await import("./disable-R6K5YJN4.js");
|
|
26
26
|
await disableCommand(opts);
|
|
27
27
|
});
|
|
28
28
|
program.command("enable").description("Re-enable the Coding Friend plugin").option("--user", "Enable at user scope (all projects)").option("--global", "Enable at user scope (all projects)").option("--project", "Enable at project scope").option("--local", "Enable at local scope").action(async (opts) => {
|
|
29
|
-
const { enableCommand } = await import("./enable-
|
|
29
|
+
const { enableCommand } = await import("./enable-HF4PYVJN.js");
|
|
30
30
|
await enableCommand(opts);
|
|
31
31
|
});
|
|
32
32
|
program.command("init").description("Initialize coding-friend in current project").action(async () => {
|
|
33
|
-
const { initCommand } = await import("./init-
|
|
33
|
+
const { initCommand } = await import("./init-YK6YRTOT.js");
|
|
34
34
|
await initCommand();
|
|
35
35
|
});
|
|
36
36
|
program.command("config").description("Manage Coding Friend configuration").action(async () => {
|
|
37
|
-
const { configCommand } = await import("./config-
|
|
37
|
+
const { configCommand } = await import("./config-LZFXXOI4.js");
|
|
38
38
|
await configCommand();
|
|
39
39
|
});
|
|
40
40
|
program.command("host").description("Build and serve learning docs as a static website").argument("[path]", "path to docs folder").option("-p, --port <port>", "port number", "3333").action(async (path, opts) => {
|
|
41
|
-
const { hostCommand } = await import("./host-
|
|
41
|
+
const { hostCommand } = await import("./host-SYZH3FVC.js");
|
|
42
42
|
await hostCommand(path, opts);
|
|
43
43
|
});
|
|
44
44
|
program.command("mcp").description("Setup MCP server for learning docs").argument("[path]", "path to docs folder").action(async (path) => {
|
|
45
|
-
const { mcpCommand } = await import("./mcp-
|
|
45
|
+
const { mcpCommand } = await import("./mcp-TBEDYELW.js");
|
|
46
46
|
await mcpCommand(path);
|
|
47
47
|
});
|
|
48
48
|
program.command("permission").description("Manage Claude Code permission rules for Coding Friend").option("--all", "Apply all recommended permissions without prompts").action(async (opts) => {
|
|
@@ -50,11 +50,11 @@ program.command("permission").description("Manage Claude Code permission rules f
|
|
|
50
50
|
await permissionCommand(opts);
|
|
51
51
|
});
|
|
52
52
|
program.command("statusline").description("Setup coding-friend statusline in Claude Code").action(async () => {
|
|
53
|
-
const { statuslineCommand } = await import("./statusline-
|
|
53
|
+
const { statuslineCommand } = await import("./statusline-6Y2EBAFQ.js");
|
|
54
54
|
await statuslineCommand();
|
|
55
55
|
});
|
|
56
56
|
program.command("update").description("Update coding-friend plugin, CLI, and statusline").option("--cli", "Update only the CLI (npm package)").option("--plugin", "Update only the Claude Code plugin").option("--statusline", "Update only the statusline").option("--user", "Update plugin at user scope (all projects)").option("--global", "Update plugin at user scope (all projects)").option("--project", "Update plugin at project scope").option("--local", "Update plugin at local scope").action(async (opts) => {
|
|
57
|
-
const { updateCommand } = await import("./update-
|
|
57
|
+
const { updateCommand } = await import("./update-WL6SFGGO.js");
|
|
58
58
|
await updateCommand(opts);
|
|
59
59
|
});
|
|
60
60
|
var session = program.command("session").description("Save and load Claude Code sessions across machines");
|
|
@@ -69,13 +69,73 @@ session.command("save").description("Save current Claude Code session to sync fo
|
|
|
69
69
|
"-s, --session-id <id>",
|
|
70
70
|
"session UUID to save (default: auto-detect newest)"
|
|
71
71
|
).option("-l, --label <label>", "label for this session").action(async (opts) => {
|
|
72
|
-
const { sessionSaveCommand } = await import("./session-
|
|
72
|
+
const { sessionSaveCommand } = await import("./session-H4XW2WXH.js");
|
|
73
73
|
await sessionSaveCommand(opts);
|
|
74
74
|
});
|
|
75
75
|
session.command("load").description("Load a saved session from sync folder").action(async () => {
|
|
76
|
-
const { sessionLoadCommand } = await import("./session-
|
|
76
|
+
const { sessionLoadCommand } = await import("./session-H4XW2WXH.js");
|
|
77
77
|
await sessionLoadCommand();
|
|
78
78
|
});
|
|
79
|
+
var memory = program.command("memory").description("AI memory system \u2014 store and search project knowledge");
|
|
80
|
+
program.addHelpText(
|
|
81
|
+
"after",
|
|
82
|
+
`
|
|
83
|
+
Memory subcommands:
|
|
84
|
+
memory status Show memory system status (tier, doc count, daemon)
|
|
85
|
+
memory search Search memories by query
|
|
86
|
+
memory list List memories in current project (--projects for all DBs)
|
|
87
|
+
memory rm Remove a project database (--project-id <id>, --all, or --prune)
|
|
88
|
+
memory init Initialize Tier 1 (install SQLite deps, import existing memories)
|
|
89
|
+
memory start Start the memory daemon (Tier 2)
|
|
90
|
+
memory stop Stop the memory daemon
|
|
91
|
+
memory rebuild Rebuild the daemon search index
|
|
92
|
+
memory mcp Show MCP server setup instructions`
|
|
93
|
+
);
|
|
94
|
+
memory.command("status").description("Show memory system status").action(async () => {
|
|
95
|
+
const { memoryStatusCommand } = await import("./memory-7RM67ZLS.js");
|
|
96
|
+
await memoryStatusCommand();
|
|
97
|
+
});
|
|
98
|
+
memory.command("search").description("Search memories by query").argument("<query>", "search query").action(async (query) => {
|
|
99
|
+
const { memorySearchCommand } = await import("./memory-7RM67ZLS.js");
|
|
100
|
+
await memorySearchCommand(query);
|
|
101
|
+
});
|
|
102
|
+
memory.command("list").description(
|
|
103
|
+
"List memories in current project, or all projects with --projects"
|
|
104
|
+
).option("--projects", "List all project databases with size and metadata").action(async (opts) => {
|
|
105
|
+
const { memoryListCommand } = await import("./memory-7RM67ZLS.js");
|
|
106
|
+
await memoryListCommand(opts);
|
|
107
|
+
});
|
|
108
|
+
memory.command("init").description(
|
|
109
|
+
"Initialize Tier 1 \u2014 install SQLite deps and import existing memories"
|
|
110
|
+
).action(async () => {
|
|
111
|
+
const { memoryInitCommand } = await import("./memory-7RM67ZLS.js");
|
|
112
|
+
await memoryInitCommand();
|
|
113
|
+
});
|
|
114
|
+
memory.command("start").description("Start the memory daemon (Tier 2 \u2014 MiniSearch)").action(async () => {
|
|
115
|
+
const { memoryStartCommand } = await import("./memory-7RM67ZLS.js");
|
|
116
|
+
await memoryStartCommand();
|
|
117
|
+
});
|
|
118
|
+
memory.command("stop").description("Stop the memory daemon").action(async () => {
|
|
119
|
+
const { memoryStopCommand } = await import("./memory-7RM67ZLS.js");
|
|
120
|
+
await memoryStopCommand();
|
|
121
|
+
});
|
|
122
|
+
memory.command("rebuild").description("Rebuild the daemon search index").action(async () => {
|
|
123
|
+
const { memoryRebuildCommand } = await import("./memory-7RM67ZLS.js");
|
|
124
|
+
await memoryRebuildCommand();
|
|
125
|
+
});
|
|
126
|
+
memory.command("mcp").description("Show MCP server setup instructions").action(async () => {
|
|
127
|
+
const { memoryMcpCommand } = await import("./memory-7RM67ZLS.js");
|
|
128
|
+
await memoryMcpCommand();
|
|
129
|
+
});
|
|
130
|
+
memory.command("rm").description("Remove a project database").option("--project-id <id>", "Project ID to remove").option("--all", "Remove all project databases").option(
|
|
131
|
+
"--prune",
|
|
132
|
+
"Remove orphaned projects (source dir missing or 0 memories)"
|
|
133
|
+
).action(
|
|
134
|
+
async (opts) => {
|
|
135
|
+
const { memoryRmCommand } = await import("./memory-7RM67ZLS.js");
|
|
136
|
+
await memoryRmCommand(opts);
|
|
137
|
+
}
|
|
138
|
+
);
|
|
79
139
|
var dev = program.command("dev").description("Development mode commands");
|
|
80
140
|
program.addHelpText(
|
|
81
141
|
"after",
|
|
@@ -89,35 +149,35 @@ Dev subcommands:
|
|
|
89
149
|
dev update [path] Update local dev plugin to latest version`
|
|
90
150
|
);
|
|
91
151
|
dev.command("on").description("Switch to local plugin source").argument("[path]", "path to local coding-friend repo (default: cwd)").action(async (path) => {
|
|
92
|
-
const { devOnCommand } = await import("./dev-
|
|
152
|
+
const { devOnCommand } = await import("./dev-R3IYWZ3M.js");
|
|
93
153
|
await devOnCommand(path);
|
|
94
154
|
});
|
|
95
155
|
dev.command("off").description("Switch back to remote marketplace").action(async () => {
|
|
96
|
-
const { devOffCommand } = await import("./dev-
|
|
156
|
+
const { devOffCommand } = await import("./dev-R3IYWZ3M.js");
|
|
97
157
|
await devOffCommand();
|
|
98
158
|
});
|
|
99
159
|
dev.command("status").description("Show current dev mode").action(async () => {
|
|
100
|
-
const { devStatusCommand } = await import("./dev-
|
|
160
|
+
const { devStatusCommand } = await import("./dev-R3IYWZ3M.js");
|
|
101
161
|
await devStatusCommand();
|
|
102
162
|
});
|
|
103
163
|
dev.command("sync").description(
|
|
104
164
|
"Copy local source files to plugin cache (no version bump needed)"
|
|
105
165
|
).action(async () => {
|
|
106
|
-
const { devSyncCommand } = await import("./dev-
|
|
166
|
+
const { devSyncCommand } = await import("./dev-R3IYWZ3M.js");
|
|
107
167
|
await devSyncCommand();
|
|
108
168
|
});
|
|
109
169
|
dev.command("restart").description("Reinstall local dev plugin (off + on)").argument(
|
|
110
170
|
"[path]",
|
|
111
171
|
"path to local coding-friend repo (default: saved path or cwd)"
|
|
112
172
|
).action(async (path) => {
|
|
113
|
-
const { devRestartCommand } = await import("./dev-
|
|
173
|
+
const { devRestartCommand } = await import("./dev-R3IYWZ3M.js");
|
|
114
174
|
await devRestartCommand(path);
|
|
115
175
|
});
|
|
116
176
|
dev.command("update").description("Update local dev plugin to latest version (off + on)").argument(
|
|
117
177
|
"[path]",
|
|
118
178
|
"path to local coding-friend repo (default: saved path or cwd)"
|
|
119
179
|
).action(async (path) => {
|
|
120
|
-
const { devUpdateCommand } = await import("./dev-
|
|
180
|
+
const { devUpdateCommand } = await import("./dev-R3IYWZ3M.js");
|
|
121
181
|
await devUpdateCommand(path);
|
|
122
182
|
});
|
|
123
183
|
program.parse();
|
|
@@ -4,6 +4,9 @@ import {
|
|
|
4
4
|
getExistingRules,
|
|
5
5
|
getMissingRules
|
|
6
6
|
} from "./chunk-56U7US6J.js";
|
|
7
|
+
import {
|
|
8
|
+
getLibPath
|
|
9
|
+
} from "./chunk-RZRT7NGT.js";
|
|
7
10
|
import {
|
|
8
11
|
findStatuslineHookPath,
|
|
9
12
|
isStatuslineConfigured,
|
|
@@ -17,28 +20,30 @@ import {
|
|
|
17
20
|
import {
|
|
18
21
|
ensureShellCompletion,
|
|
19
22
|
hasShellCompletion
|
|
20
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-YO6JKGR3.js";
|
|
21
24
|
import {
|
|
22
25
|
BACK,
|
|
23
26
|
applyDocsDirChange,
|
|
24
27
|
askScope,
|
|
28
|
+
ensureDocsFolders,
|
|
25
29
|
formatScopeLabel,
|
|
26
30
|
getMergedValue,
|
|
27
31
|
getScopeLabel,
|
|
28
32
|
injectBackChoice,
|
|
29
33
|
showConfigHint
|
|
30
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-C5LYVVEI.js";
|
|
31
35
|
import {
|
|
32
36
|
commandExists,
|
|
33
37
|
run
|
|
34
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-CYQU33FY.js";
|
|
35
39
|
import {
|
|
36
40
|
claudeSettingsPath,
|
|
37
41
|
globalConfigPath,
|
|
38
42
|
localConfigPath,
|
|
39
43
|
mergeJson,
|
|
40
44
|
readJson,
|
|
41
|
-
resolvePath
|
|
45
|
+
resolvePath,
|
|
46
|
+
writeJson
|
|
42
47
|
} from "./chunk-RWUTFVRB.js";
|
|
43
48
|
import {
|
|
44
49
|
log
|
|
@@ -48,6 +53,7 @@ import {
|
|
|
48
53
|
import { checkbox, confirm, input, select } from "@inquirer/prompts";
|
|
49
54
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
50
55
|
import { homedir } from "os";
|
|
56
|
+
import { join } from "path";
|
|
51
57
|
import chalk from "chalk";
|
|
52
58
|
var GITIGNORE_START = "# >>> coding-friend managed";
|
|
53
59
|
var GITIGNORE_END = "# <<< coding-friend managed";
|
|
@@ -84,6 +90,76 @@ function isGitRepo() {
|
|
|
84
90
|
function escapeRegExp(str) {
|
|
85
91
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
86
92
|
}
|
|
93
|
+
function hasGitignoreBlock() {
|
|
94
|
+
if (!existsSync(".gitignore")) return false;
|
|
95
|
+
const content = readFileSync(".gitignore", "utf-8");
|
|
96
|
+
return content.includes(GITIGNORE_START) || content.includes("# coding-friend");
|
|
97
|
+
}
|
|
98
|
+
function paddedScopeLabel(scope) {
|
|
99
|
+
const label = formatScopeLabel(scope);
|
|
100
|
+
const visibleLen = scope.length + 2;
|
|
101
|
+
return label + " ".repeat(Math.max(1, 9 - visibleLen));
|
|
102
|
+
}
|
|
103
|
+
function printSetupStatus(globalCfg, localCfg, gitAvailable) {
|
|
104
|
+
const docsDir = getDocsDir(globalCfg, localCfg);
|
|
105
|
+
const subfolders = ["plans", "memory", "research", "learn", "sessions"];
|
|
106
|
+
const folderStatus = subfolders.map((sub) => ({
|
|
107
|
+
name: `${docsDir}/${sub}`,
|
|
108
|
+
exists: existsSync(`${docsDir}/${sub}`)
|
|
109
|
+
}));
|
|
110
|
+
const foldersReady = folderStatus.filter((f) => f.exists).length;
|
|
111
|
+
const missingFolders = subfolders.length - foldersReady;
|
|
112
|
+
const allFoldersDone = missingFolders === 0;
|
|
113
|
+
const countColor = allFoldersDone ? chalk.green : chalk.yellow;
|
|
114
|
+
console.log(
|
|
115
|
+
` ${chalk.bold("Folders")} ${countColor(`(${foldersReady}/${subfolders.length})`)}:`
|
|
116
|
+
);
|
|
117
|
+
for (const f of folderStatus) {
|
|
118
|
+
const icon = f.exists ? chalk.green("\u2713") : chalk.red("\u2717");
|
|
119
|
+
const name = f.exists ? chalk.dim(f.name) : f.name;
|
|
120
|
+
console.log(` ${icon} ${name}`);
|
|
121
|
+
}
|
|
122
|
+
console.log();
|
|
123
|
+
const configKeys = [
|
|
124
|
+
{ key: "docsDir", label: "Docs folder" },
|
|
125
|
+
{ key: "language", label: "Docs language" },
|
|
126
|
+
{ key: "learn", label: "/cf-learn config" }
|
|
127
|
+
];
|
|
128
|
+
let notLocalCount = 0;
|
|
129
|
+
let notConfiguredCount = 0;
|
|
130
|
+
console.log(` ${chalk.bold("Settings")}:`);
|
|
131
|
+
for (const s of configKeys) {
|
|
132
|
+
const scope = getScopeLabel(s.key, globalCfg, localCfg);
|
|
133
|
+
const value = getMergedValue(s.key, globalCfg, localCfg);
|
|
134
|
+
const valueStr = value && typeof value === "string" ? ` (${chalk.dim(value)})` : "";
|
|
135
|
+
console.log(` ${paddedScopeLabel(scope)}${s.label}${valueStr}`);
|
|
136
|
+
if (scope === "-") notConfiguredCount++;
|
|
137
|
+
if (scope === "-" || scope === "global") notLocalCount++;
|
|
138
|
+
}
|
|
139
|
+
const setupItems = [
|
|
140
|
+
{
|
|
141
|
+
label: ".gitignore",
|
|
142
|
+
done: !gitAvailable || hasGitignoreBlock(),
|
|
143
|
+
skipped: !gitAvailable
|
|
144
|
+
},
|
|
145
|
+
{ label: "Shell completion", done: hasShellCompletion(), skipped: false },
|
|
146
|
+
{ label: "Statusline", done: isStatuslineConfigured(), skipped: false },
|
|
147
|
+
{ label: "CF Memory MCP", done: isMemoryMcpConfigured(), skipped: false }
|
|
148
|
+
];
|
|
149
|
+
for (const item of setupItems) {
|
|
150
|
+
if (item.skipped) {
|
|
151
|
+
console.log(` ${paddedScopeLabel("skip")}${item.label}`);
|
|
152
|
+
} else if (item.done) {
|
|
153
|
+
console.log(` ${paddedScopeLabel("done")}${item.label}`);
|
|
154
|
+
} else {
|
|
155
|
+
console.log(` ${paddedScopeLabel("-")}${item.label}`);
|
|
156
|
+
notConfiguredCount++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
console.log();
|
|
160
|
+
const allDone = allFoldersDone && notConfiguredCount === 0;
|
|
161
|
+
return { allDone, notLocalCount, notConfiguredCount, missingFolders };
|
|
162
|
+
}
|
|
87
163
|
function writeToScope(scope, data) {
|
|
88
164
|
const targetPath = scope === "global" ? globalConfigPath() : localConfigPath();
|
|
89
165
|
mergeJson(targetPath, data);
|
|
@@ -99,6 +175,27 @@ function handleBack(value) {
|
|
|
99
175
|
function getDocsDir(globalCfg, localCfg) {
|
|
100
176
|
return localCfg?.docsDir ?? globalCfg?.docsDir ?? DEFAULT_CONFIG.docsDir;
|
|
101
177
|
}
|
|
178
|
+
async function offerGlobalShortcut(globalDisplay) {
|
|
179
|
+
const choice = await select({
|
|
180
|
+
message: "How to configure?",
|
|
181
|
+
choices: injectBackChoice(
|
|
182
|
+
[
|
|
183
|
+
{
|
|
184
|
+
name: `Use global setting (${globalDisplay})`,
|
|
185
|
+
value: "use_global"
|
|
186
|
+
},
|
|
187
|
+
{ name: "Configure manually", value: "configure" }
|
|
188
|
+
],
|
|
189
|
+
"Cancel init"
|
|
190
|
+
)
|
|
191
|
+
});
|
|
192
|
+
handleBack(choice);
|
|
193
|
+
if (choice === "use_global") {
|
|
194
|
+
log.dim("Using global setting.");
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
102
199
|
async function stepDocsDir(globalCfg, localCfg) {
|
|
103
200
|
const currentValue = getMergedValue("docsDir", globalCfg, localCfg);
|
|
104
201
|
const scopeLabel = getScopeLabel("docsDir", globalCfg, localCfg);
|
|
@@ -106,6 +203,18 @@ async function stepDocsDir(globalCfg, localCfg) {
|
|
|
106
203
|
`Docs folder name ${formatScopeLabel(scopeLabel)}${currentValue ? ` (${chalk.dim(currentValue)})` : ""}`,
|
|
107
204
|
"Where plans, memory, research, and session docs are stored in your project."
|
|
108
205
|
);
|
|
206
|
+
const globalValue = globalCfg?.docsDir;
|
|
207
|
+
if (globalValue && await offerGlobalShortcut(globalValue)) {
|
|
208
|
+
const DOCS_SUBFOLDERS2 = [
|
|
209
|
+
"plans",
|
|
210
|
+
"memory",
|
|
211
|
+
"research",
|
|
212
|
+
"learn",
|
|
213
|
+
"sessions"
|
|
214
|
+
];
|
|
215
|
+
ensureDocsFolders(globalValue, DOCS_SUBFOLDERS2);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
109
218
|
const value = await input({
|
|
110
219
|
message: "Docs folder name:",
|
|
111
220
|
default: currentValue ?? DEFAULT_CONFIG.docsDir,
|
|
@@ -126,11 +235,7 @@ async function stepDocsDir(globalCfg, localCfg) {
|
|
|
126
235
|
writeToScope(scope, { docsDir: value });
|
|
127
236
|
}
|
|
128
237
|
async function stepGitignore(docsDir) {
|
|
129
|
-
const hasBlock = (
|
|
130
|
-
if (!existsSync(".gitignore")) return false;
|
|
131
|
-
const content = readFileSync(".gitignore", "utf-8");
|
|
132
|
-
return content.includes(GITIGNORE_START) || content.includes("# coding-friend");
|
|
133
|
-
})();
|
|
238
|
+
const hasBlock = hasGitignoreBlock();
|
|
134
239
|
if (hasBlock) {
|
|
135
240
|
printStepHeader(
|
|
136
241
|
`Configure .gitignore ${chalk.green("[done]")}`,
|
|
@@ -206,6 +311,8 @@ async function stepDocsLanguage(globalCfg, localCfg) {
|
|
|
206
311
|
`Docs language ${formatScopeLabel(scopeLabel)}${currentValue ? ` (${chalk.dim(currentValue)})` : ""}`,
|
|
207
312
|
"Skills like /cf-plan, /cf-ask, /cf-remember will write docs in this language."
|
|
208
313
|
);
|
|
314
|
+
const globalValue = globalCfg?.language;
|
|
315
|
+
if (globalValue && await offerGlobalShortcut(globalValue)) return;
|
|
209
316
|
const lang = await selectLanguage(
|
|
210
317
|
"What language should generated docs be written in?"
|
|
211
318
|
);
|
|
@@ -243,6 +350,25 @@ async function stepLearnConfig(globalCfg, localCfg, gitAvailable) {
|
|
|
243
350
|
`/cf-learn config ${formatScopeLabel(scopeLabel)}`,
|
|
244
351
|
"Controls where and how /cf-learn saves your learning notes."
|
|
245
352
|
);
|
|
353
|
+
const globalLearn = globalCfg?.learn;
|
|
354
|
+
if (globalLearn) {
|
|
355
|
+
const parts = [
|
|
356
|
+
globalLearn.language || "en",
|
|
357
|
+
globalLearn.outputDir || `${docsDir}/learn`
|
|
358
|
+
];
|
|
359
|
+
if (globalLearn.categories) {
|
|
360
|
+
parts.push(`${globalLearn.categories.length} categories`);
|
|
361
|
+
}
|
|
362
|
+
if (await offerGlobalShortcut(parts.join(", "))) {
|
|
363
|
+
const gOutputDir = globalLearn.outputDir || `${docsDir}/learn`;
|
|
364
|
+
const gIsExternal = !gOutputDir.startsWith(`${docsDir}/`);
|
|
365
|
+
return {
|
|
366
|
+
outputDir: gOutputDir,
|
|
367
|
+
autoCommit: globalLearn.autoCommit || false,
|
|
368
|
+
isExternal: gIsExternal
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
246
372
|
const language = await selectLanguage(
|
|
247
373
|
"What language should /cf-learn notes be written in?"
|
|
248
374
|
);
|
|
@@ -424,6 +550,95 @@ async function stepStatusline() {
|
|
|
424
550
|
writeStatuslineSettings(hookResult.hookPath);
|
|
425
551
|
log.success("Statusline configured!");
|
|
426
552
|
}
|
|
553
|
+
function isMemoryMcpConfigured() {
|
|
554
|
+
const mcpPath = join(process.cwd(), ".mcp.json");
|
|
555
|
+
const config = readJson(mcpPath);
|
|
556
|
+
if (!config) return false;
|
|
557
|
+
const servers = config.mcpServers;
|
|
558
|
+
return servers != null && "coding-friend-memory" in servers;
|
|
559
|
+
}
|
|
560
|
+
async function stepMemory(docsDir) {
|
|
561
|
+
if (isMemoryMcpConfigured()) {
|
|
562
|
+
printStepHeader(
|
|
563
|
+
`CF Memory MCP ${chalk.green("[done]")}`,
|
|
564
|
+
"Connects the memory system to Claude Code via MCP."
|
|
565
|
+
);
|
|
566
|
+
log.dim("Memory MCP already configured in .mcp.json.");
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
printStepHeader(
|
|
570
|
+
"CF Memory MCP",
|
|
571
|
+
"Connects the memory system to Claude Code via MCP so skills can store and search memories."
|
|
572
|
+
);
|
|
573
|
+
const choice = await select({
|
|
574
|
+
message: "Configure CF Memory MCP server?",
|
|
575
|
+
choices: injectBackChoice(
|
|
576
|
+
[
|
|
577
|
+
{ name: "Yes, add to .mcp.json", value: "yes" },
|
|
578
|
+
{
|
|
579
|
+
name: "No, I'll configure it later (cf memory mcp)",
|
|
580
|
+
value: "no"
|
|
581
|
+
}
|
|
582
|
+
],
|
|
583
|
+
"Cancel init"
|
|
584
|
+
)
|
|
585
|
+
});
|
|
586
|
+
handleBack(choice);
|
|
587
|
+
if (choice === "no") {
|
|
588
|
+
log.dim("Skipped. Run `cf memory mcp` anytime to get the config.");
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
let serverPath;
|
|
592
|
+
try {
|
|
593
|
+
const mcpDir = getLibPath("cf-memory");
|
|
594
|
+
serverPath = join(mcpDir, "dist", "index.js");
|
|
595
|
+
if (!existsSync(serverPath)) {
|
|
596
|
+
log.warn(
|
|
597
|
+
"cf-memory not built yet. Run `cd cli/lib/cf-memory && npm install && npm run build` first."
|
|
598
|
+
);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
} catch {
|
|
602
|
+
log.warn(
|
|
603
|
+
"cf-memory package not found. Install the CLI first: npm i -g coding-friend-cli"
|
|
604
|
+
);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const memoryDir = join(process.cwd(), docsDir, "memory");
|
|
608
|
+
const mcpPath = join(process.cwd(), ".mcp.json");
|
|
609
|
+
const existing = readJson(mcpPath) ?? {};
|
|
610
|
+
const servers = existing.mcpServers ?? {};
|
|
611
|
+
writeJson(mcpPath, {
|
|
612
|
+
...existing,
|
|
613
|
+
mcpServers: {
|
|
614
|
+
...servers,
|
|
615
|
+
"coding-friend-memory": {
|
|
616
|
+
command: "node",
|
|
617
|
+
args: [serverPath, memoryDir]
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
log.success(`Added coding-friend-memory to .mcp.json`);
|
|
622
|
+
const autoCapture = await confirm({
|
|
623
|
+
message: "Enable auto-capture? (saves session summaries to memory before context compaction)",
|
|
624
|
+
default: false
|
|
625
|
+
});
|
|
626
|
+
if (autoCapture) {
|
|
627
|
+
const scope = await askScope();
|
|
628
|
+
if (scope !== "back") {
|
|
629
|
+
const targetPath = scope === "global" ? globalConfigPath() : localConfigPath();
|
|
630
|
+
const existingConfig = readJson(targetPath);
|
|
631
|
+
const existingMemory = existingConfig?.memory ?? {};
|
|
632
|
+
mergeJson(targetPath, {
|
|
633
|
+
memory: { ...existingMemory, autoCapture: true }
|
|
634
|
+
});
|
|
635
|
+
log.success(`Saved to ${targetPath}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
log.info(
|
|
639
|
+
`Tip: Run ${chalk.cyan("/cf-onboard")} in Claude Code to populate memory with project knowledge.`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
427
642
|
async function stepClaudePermissions(outputDir, autoCommit) {
|
|
428
643
|
const resolved = resolvePath(outputDir);
|
|
429
644
|
const homePath = resolved.startsWith(homedir()) ? resolved.replace(homedir(), "~") : resolved;
|
|
@@ -472,20 +687,52 @@ async function initCommand() {
|
|
|
472
687
|
}
|
|
473
688
|
const globalCfg = readJson(globalConfigPath());
|
|
474
689
|
const localCfg = readJson(localConfigPath());
|
|
475
|
-
console.log("
|
|
476
|
-
const docsDirScope = getScopeLabel("docsDir", globalCfg, localCfg);
|
|
477
|
-
const docsDirVal = getMergedValue("docsDir", globalCfg, localCfg);
|
|
478
|
-
console.log(
|
|
479
|
-
` ${formatScopeLabel(docsDirScope)} docsDir${docsDirVal ? ` (${chalk.dim(docsDirVal)})` : ""}`
|
|
480
|
-
);
|
|
481
|
-
const langScope = getScopeLabel("language", globalCfg, localCfg);
|
|
482
|
-
const langVal = getMergedValue("language", globalCfg, localCfg);
|
|
483
|
-
console.log(
|
|
484
|
-
` ${formatScopeLabel(langScope)} Docs language${langVal ? ` (${chalk.dim(langVal)})` : ""}`
|
|
485
|
-
);
|
|
486
|
-
const learnScope = getScopeLabel("learn", globalCfg, localCfg);
|
|
487
|
-
console.log(` ${formatScopeLabel(learnScope)} /cf-learn config`);
|
|
690
|
+
console.log("Project status:");
|
|
488
691
|
console.log();
|
|
692
|
+
const { allDone, notLocalCount, notConfiguredCount, missingFolders } = printSetupStatus(globalCfg, localCfg, gitAvailable);
|
|
693
|
+
if (allDone) {
|
|
694
|
+
if (notLocalCount > 0) {
|
|
695
|
+
console.log(
|
|
696
|
+
chalk.dim(
|
|
697
|
+
` ${notLocalCount} setting(s) inherited from global config only.`
|
|
698
|
+
)
|
|
699
|
+
);
|
|
700
|
+
console.log();
|
|
701
|
+
}
|
|
702
|
+
log.success("All settings configured!");
|
|
703
|
+
console.log();
|
|
704
|
+
const proceed = await confirm({
|
|
705
|
+
message: "Modify settings?",
|
|
706
|
+
default: false
|
|
707
|
+
});
|
|
708
|
+
if (!proceed) {
|
|
709
|
+
log.dim("No changes. Run `cf init` anytime to reconfigure.");
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
} else {
|
|
713
|
+
const parts = [];
|
|
714
|
+
if (missingFolders > 0) {
|
|
715
|
+
parts.push(`${missingFolders} folder(s) missing`);
|
|
716
|
+
}
|
|
717
|
+
if (notConfiguredCount > 0) {
|
|
718
|
+
parts.push(`${notConfiguredCount} setting(s) not configured`);
|
|
719
|
+
}
|
|
720
|
+
if (notLocalCount > 0) {
|
|
721
|
+
parts.push(`${notLocalCount} not set locally`);
|
|
722
|
+
}
|
|
723
|
+
if (parts.length > 0) {
|
|
724
|
+
console.log(` ${chalk.yellow("\u26A0")} ${parts.join(" \xB7 ")}`);
|
|
725
|
+
console.log();
|
|
726
|
+
}
|
|
727
|
+
const proceed = await confirm({
|
|
728
|
+
message: "Run setup wizard?",
|
|
729
|
+
default: true
|
|
730
|
+
});
|
|
731
|
+
if (!proceed) {
|
|
732
|
+
log.dim("Init cancelled. Run `cf init` anytime to resume.");
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
489
736
|
await stepDocsDir(globalCfg, localCfg);
|
|
490
737
|
const updatedGlobal = readJson(globalConfigPath());
|
|
491
738
|
const updatedLocal = readJson(localConfigPath());
|
|
@@ -507,6 +754,7 @@ async function initCommand() {
|
|
|
507
754
|
);
|
|
508
755
|
await stepShellCompletion();
|
|
509
756
|
await stepStatusline();
|
|
757
|
+
await stepMemory(docsDir);
|
|
510
758
|
if (isExternal) {
|
|
511
759
|
printStepHeader(
|
|
512
760
|
"Configure Claude permissions",
|
|
@@ -525,7 +773,7 @@ async function initCommand() {
|
|
|
525
773
|
console.log();
|
|
526
774
|
log.congrats("Setup complete!");
|
|
527
775
|
log.dim(
|
|
528
|
-
"Available commands: /cf-ask, /cf-plan, /cf-fix, /cf-commit, /cf-review, /cf-ship, /cf-optimize, /cf-remember, /cf-learn, /cf-research, /cf-session, /cf-help"
|
|
776
|
+
"Available commands: /cf-ask, /cf-plan, /cf-fix, /cf-commit, /cf-review, /cf-ship, /cf-optimize, /cf-onboard, /cf-remember, /cf-learn, /cf-research, /cf-session, /cf-help"
|
|
529
777
|
);
|
|
530
778
|
}
|
|
531
779
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getLatestVersion,
|
|
3
3
|
semverCompare
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-G6CEEMAR.js";
|
|
5
5
|
import {
|
|
6
6
|
enableMarketplaceAutoUpdate,
|
|
7
7
|
isMarketplaceRegistered,
|
|
@@ -13,14 +13,14 @@ import {
|
|
|
13
13
|
import "./chunk-POC2WHU2.js";
|
|
14
14
|
import {
|
|
15
15
|
ensureShellCompletion
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-YO6JKGR3.js";
|
|
17
17
|
import {
|
|
18
18
|
resolveScope
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-C5LYVVEI.js";
|
|
20
20
|
import {
|
|
21
21
|
commandExists,
|
|
22
22
|
run
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-CYQU33FY.js";
|
|
24
24
|
import {
|
|
25
25
|
devStatePath
|
|
26
26
|
} from "./chunk-RWUTFVRB.js";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveDocsDir
|
|
3
|
+
} from "./chunk-KTX4MGMR.js";
|
|
1
4
|
import {
|
|
2
5
|
getLibPath
|
|
3
6
|
} from "./chunk-RZRT7NGT.js";
|
|
4
|
-
import {
|
|
5
|
-
resolveDocsDir
|
|
6
|
-
} from "./chunk-4DB4XTSL.js";
|
|
7
7
|
import "./chunk-POC2WHU2.js";
|
|
8
8
|
import {
|
|
9
9
|
run
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-CYQU33FY.js";
|
|
11
11
|
import "./chunk-RWUTFVRB.js";
|
|
12
12
|
import {
|
|
13
13
|
log
|