coding-friend-cli 1.7.0 → 1.8.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 +2 -0
- package/dist/{chunk-HSQX3PKW.js → chunk-BPLN4LDL.js} +3 -5
- package/dist/{chunk-4PLV2ENL.js → chunk-FYGACWU6.js} +3 -2
- package/dist/chunk-FYHHNX7K.js +35 -0
- package/dist/{chunk-MRTR7TJ4.js → chunk-HFLBFX6J.js} +2 -4
- package/dist/{chunk-DHPWBSF5.js → chunk-JS75SVQA.js} +6 -8
- 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-6DUFTBTO.js → chunk-W5CD7WTX.js} +1 -0
- package/dist/config-JZEFZIPY.js +406 -0
- package/dist/{dev-QW6VPG4G.js → dev-U7LPXAHR.js} +9 -11
- package/dist/{host-EERZVOHY.js → host-LOG5RPZ7.js} +7 -6
- package/dist/index.js +36 -13
- package/dist/init-JJATBCHC.js +512 -0
- package/dist/{install-ORIDNWRW.js → install-7MSZ7B5O.js} +7 -8
- package/dist/{mcp-3MUUQZQD.js → mcp-ORMYETXQ.js} +7 -6
- package/dist/postinstall.js +2 -2
- package/dist/session-3MWYAKKY.js +235 -0
- package/dist/{statusline-BWGI5PQ5.js → statusline-5HWRTSVL.js} +4 -5
- package/dist/{uninstall-KOAJFPD6.js → uninstall-HDLTWPXG.js} +8 -10
- package/dist/update-E4MQDRFC.js +16 -0
- package/package.json +1 -1
- 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,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadConfig
|
|
3
|
+
} from "./chunk-FYHHNX7K.js";
|
|
4
|
+
import "./chunk-PGLUEN7D.js";
|
|
5
|
+
import {
|
|
6
|
+
claudeSessionDir,
|
|
7
|
+
encodeProjectPath,
|
|
8
|
+
globalConfigPath,
|
|
9
|
+
mergeJson,
|
|
10
|
+
readJson,
|
|
11
|
+
writeJson
|
|
12
|
+
} from "./chunk-TPRZHSFS.js";
|
|
13
|
+
import {
|
|
14
|
+
log
|
|
15
|
+
} from "./chunk-W5CD7WTX.js";
|
|
16
|
+
|
|
17
|
+
// src/commands/session.ts
|
|
18
|
+
import { input, select } from "@inquirer/prompts";
|
|
19
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
20
|
+
import { homedir as homedir2 } from "os";
|
|
21
|
+
import { join as join2, basename } from "path";
|
|
22
|
+
|
|
23
|
+
// src/lib/session.ts
|
|
24
|
+
import {
|
|
25
|
+
readdirSync,
|
|
26
|
+
statSync,
|
|
27
|
+
copyFileSync,
|
|
28
|
+
existsSync,
|
|
29
|
+
readFileSync
|
|
30
|
+
} from "fs";
|
|
31
|
+
import { join } from "path";
|
|
32
|
+
import { hostname as osHostname } from "os";
|
|
33
|
+
function buildPreviewText(jsonlPath, maxChars = 200) {
|
|
34
|
+
try {
|
|
35
|
+
const content = readFileSync(jsonlPath, "utf-8");
|
|
36
|
+
const lines = content.split("\n").filter(Boolean);
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
try {
|
|
39
|
+
const entry = JSON.parse(line);
|
|
40
|
+
if (entry.type === "user") {
|
|
41
|
+
const msg = entry.message;
|
|
42
|
+
const msgContent = msg?.content;
|
|
43
|
+
if (typeof msgContent === "string" && msgContent.trim()) {
|
|
44
|
+
return msgContent.trim().slice(0, maxChars);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return "(preview unavailable)";
|
|
51
|
+
} catch {
|
|
52
|
+
return "(preview unavailable)";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function listSyncedSessions(syncDir) {
|
|
56
|
+
const sessionsDir = join(syncDir, "sessions");
|
|
57
|
+
if (!existsSync(sessionsDir)) return [];
|
|
58
|
+
const entries = readdirSync(sessionsDir).filter((entry) => {
|
|
59
|
+
const entryPath = join(sessionsDir, entry);
|
|
60
|
+
return statSync(entryPath).isDirectory();
|
|
61
|
+
});
|
|
62
|
+
const metas = [];
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const metaPath = join(sessionsDir, entry, "meta.json");
|
|
65
|
+
const meta = readJson(metaPath);
|
|
66
|
+
if (meta) metas.push(meta);
|
|
67
|
+
}
|
|
68
|
+
return metas.sort(
|
|
69
|
+
(a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime()
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
function remapProjectPath(originalPath, currentHome) {
|
|
73
|
+
const homePattern = /^(\/(?:Users|home)\/[^/]+)(\/.*)?$/;
|
|
74
|
+
const match = originalPath.match(homePattern);
|
|
75
|
+
if (!match) return originalPath;
|
|
76
|
+
const origHomeDir = match[1];
|
|
77
|
+
const rest = match[2] ?? "";
|
|
78
|
+
if (origHomeDir === currentHome) return originalPath;
|
|
79
|
+
return currentHome + rest;
|
|
80
|
+
}
|
|
81
|
+
function saveSession(opts) {
|
|
82
|
+
const { jsonlPath, sessionId, label, projectPath, syncDir, previewText } = opts;
|
|
83
|
+
const destDir = join(syncDir, "sessions", sessionId);
|
|
84
|
+
const destJsonl = join(destDir, "session.jsonl");
|
|
85
|
+
const destMeta = join(destDir, "meta.json");
|
|
86
|
+
copyFileSync(jsonlPath, destJsonl);
|
|
87
|
+
const meta = {
|
|
88
|
+
sessionId,
|
|
89
|
+
label,
|
|
90
|
+
projectPath,
|
|
91
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
92
|
+
machine: hostname(),
|
|
93
|
+
previewText
|
|
94
|
+
};
|
|
95
|
+
writeJson(destMeta, meta);
|
|
96
|
+
}
|
|
97
|
+
function loadSession(meta, localProjectPath, syncDir) {
|
|
98
|
+
const encodedPath = encodeProjectPath(localProjectPath);
|
|
99
|
+
const destDir = claudeSessionDir(encodedPath);
|
|
100
|
+
const destPath = join(destDir, `${meta.sessionId}.jsonl`);
|
|
101
|
+
const srcPath = join(syncDir, "sessions", meta.sessionId, "session.jsonl");
|
|
102
|
+
copyFileSync(srcPath, destPath);
|
|
103
|
+
}
|
|
104
|
+
function hostname() {
|
|
105
|
+
try {
|
|
106
|
+
return osHostname();
|
|
107
|
+
} catch {
|
|
108
|
+
return "unknown";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/commands/session.ts
|
|
113
|
+
async function resolveSyncDir() {
|
|
114
|
+
const config = loadConfig();
|
|
115
|
+
if (config.sessionSyncDir) return config.sessionSyncDir;
|
|
116
|
+
log.warn("No session sync folder configured.");
|
|
117
|
+
const syncDir = await input({
|
|
118
|
+
message: "Enter path to your sync folder (e.g. ~/Dropbox/cf-sessions or a git repo path):",
|
|
119
|
+
validate: (v) => v.trim().length > 0 || "Path cannot be empty"
|
|
120
|
+
});
|
|
121
|
+
const resolved = syncDir.startsWith("~/") ? join2(homedir2(), syncDir.slice(2)) : syncDir;
|
|
122
|
+
mergeJson(globalConfigPath(), { sessionSyncDir: resolved });
|
|
123
|
+
log.success(`Sync folder saved to global config: ${resolved}`);
|
|
124
|
+
log.warn(
|
|
125
|
+
"Session files contain your full conversation history. Make sure this folder is private."
|
|
126
|
+
);
|
|
127
|
+
return resolved;
|
|
128
|
+
}
|
|
129
|
+
function formatSessionChoice(meta) {
|
|
130
|
+
const date = new Date(meta.savedAt).toLocaleString();
|
|
131
|
+
const preview = meta.previewText.slice(0, 60).replace(/\n/g, " ");
|
|
132
|
+
return `[${meta.label}] ${date} @${meta.machine} \u2014 ${preview}`;
|
|
133
|
+
}
|
|
134
|
+
async function sessionSaveCommand(opts = {}) {
|
|
135
|
+
const syncDir = await resolveSyncDir();
|
|
136
|
+
const cwd = process.cwd();
|
|
137
|
+
let jsonlPath = null;
|
|
138
|
+
if (opts.sessionId) {
|
|
139
|
+
const candidate = join2(
|
|
140
|
+
claudeSessionDir(encodeProjectPath(cwd)),
|
|
141
|
+
`${opts.sessionId}.jsonl`
|
|
142
|
+
);
|
|
143
|
+
if (!existsSync2(candidate)) {
|
|
144
|
+
log.error(`Session file not found: ${candidate}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
jsonlPath = candidate;
|
|
148
|
+
} else {
|
|
149
|
+
const sessionDir = claudeSessionDir(encodeProjectPath(cwd));
|
|
150
|
+
if (!existsSync2(sessionDir)) {
|
|
151
|
+
log.error(
|
|
152
|
+
`No sessions found for current directory. Run this inside a project that has Claude Code sessions.`
|
|
153
|
+
);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
const files = readdirSync2(sessionDir).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).map((f) => ({ name: f, mtime: statSync2(join2(sessionDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
157
|
+
if (files.length === 0) {
|
|
158
|
+
log.error("No session files found in current project directory.");
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
const recentThreshold = 6e4;
|
|
162
|
+
const recentFiles = files.filter(
|
|
163
|
+
(f) => files[0].mtime - f.mtime < recentThreshold
|
|
164
|
+
);
|
|
165
|
+
if (recentFiles.length > 1) {
|
|
166
|
+
const chosen = await select({
|
|
167
|
+
message: "Multiple recent sessions found. Which one to save?",
|
|
168
|
+
choices: recentFiles.map((f) => ({
|
|
169
|
+
name: `${f.name} (modified ${new Date(f.mtime).toLocaleString()})`,
|
|
170
|
+
value: join2(sessionDir, f.name)
|
|
171
|
+
}))
|
|
172
|
+
});
|
|
173
|
+
jsonlPath = chosen;
|
|
174
|
+
} else {
|
|
175
|
+
jsonlPath = join2(sessionDir, files[0].name);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const sessionId = basename(jsonlPath, ".jsonl");
|
|
179
|
+
const label = opts.label ?? await input({
|
|
180
|
+
message: "Give this session a label:",
|
|
181
|
+
default: `session-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`
|
|
182
|
+
});
|
|
183
|
+
const previewText = buildPreviewText(jsonlPath);
|
|
184
|
+
saveSession({
|
|
185
|
+
jsonlPath,
|
|
186
|
+
sessionId,
|
|
187
|
+
label,
|
|
188
|
+
projectPath: cwd,
|
|
189
|
+
syncDir,
|
|
190
|
+
previewText
|
|
191
|
+
});
|
|
192
|
+
log.success(`Session saved: "${label}"`);
|
|
193
|
+
log.dim(` \u2192 ${join2(syncDir, "sessions", sessionId)}`);
|
|
194
|
+
}
|
|
195
|
+
async function sessionLoadCommand() {
|
|
196
|
+
const syncDir = await resolveSyncDir();
|
|
197
|
+
const sessions = listSyncedSessions(syncDir);
|
|
198
|
+
if (sessions.length === 0) {
|
|
199
|
+
log.warn("No saved sessions found in sync folder.");
|
|
200
|
+
log.dim(` Sync folder: ${syncDir}`);
|
|
201
|
+
log.dim(" Run /cf-session inside a Claude Code conversation to save one.");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const chosen = await select({
|
|
205
|
+
message: "Choose a session to load:",
|
|
206
|
+
choices: sessions.map((s) => ({
|
|
207
|
+
name: formatSessionChoice(s),
|
|
208
|
+
value: s
|
|
209
|
+
}))
|
|
210
|
+
});
|
|
211
|
+
const currentHome = homedir2();
|
|
212
|
+
const remapped = remapProjectPath(chosen.projectPath, currentHome);
|
|
213
|
+
let localProjectPath = remapped;
|
|
214
|
+
if (remapped !== chosen.projectPath) {
|
|
215
|
+
log.step(
|
|
216
|
+
`Original path: ${chosen.projectPath}
|
|
217
|
+
Remapped to: ${remapped}`
|
|
218
|
+
);
|
|
219
|
+
const confirmed = await input({
|
|
220
|
+
message: "Local project path (press Enter to accept or edit):",
|
|
221
|
+
default: remapped
|
|
222
|
+
});
|
|
223
|
+
localProjectPath = confirmed.trim() || remapped;
|
|
224
|
+
}
|
|
225
|
+
loadSession(chosen, localProjectPath, syncDir);
|
|
226
|
+
log.success(`Session "${chosen.label}" loaded.`);
|
|
227
|
+
log.info(`To resume, run:`);
|
|
228
|
+
console.log(`
|
|
229
|
+
claude --resume ${chosen.sessionId}
|
|
230
|
+
`);
|
|
231
|
+
}
|
|
232
|
+
export {
|
|
233
|
+
sessionLoadCommand,
|
|
234
|
+
sessionSaveCommand
|
|
235
|
+
};
|
|
@@ -4,15 +4,14 @@ import {
|
|
|
4
4
|
saveStatuslineConfig,
|
|
5
5
|
selectStatuslineComponents,
|
|
6
6
|
writeStatuslineSettings
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BPLN4LDL.js";
|
|
8
8
|
import {
|
|
9
9
|
ALL_COMPONENT_IDS
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-PGLUEN7D.js";
|
|
11
|
+
import "./chunk-TPRZHSFS.js";
|
|
12
12
|
import {
|
|
13
13
|
log
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-IUTXHCP7.js";
|
|
14
|
+
} from "./chunk-W5CD7WTX.js";
|
|
16
15
|
|
|
17
16
|
// src/commands/statusline.ts
|
|
18
17
|
import { confirm } from "@inquirer/prompts";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isMarketplaceRegistered,
|
|
3
3
|
isPluginInstalled
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HFLBFX6J.js";
|
|
5
5
|
import {
|
|
6
6
|
hasShellCompletion,
|
|
7
7
|
removeShellCompletion
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-FYGACWU6.js";
|
|
9
9
|
import {
|
|
10
10
|
commandExists,
|
|
11
11
|
run
|
|
@@ -15,15 +15,13 @@ import {
|
|
|
15
15
|
devStatePath,
|
|
16
16
|
globalConfigDir,
|
|
17
17
|
marketplaceCachePath,
|
|
18
|
-
marketplaceClonePath
|
|
19
|
-
} from "./chunk-WHCJT7E2.js";
|
|
20
|
-
import {
|
|
21
|
-
log
|
|
22
|
-
} from "./chunk-6DUFTBTO.js";
|
|
23
|
-
import {
|
|
18
|
+
marketplaceClonePath,
|
|
24
19
|
readJson,
|
|
25
20
|
writeJson
|
|
26
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-TPRZHSFS.js";
|
|
22
|
+
import {
|
|
23
|
+
log
|
|
24
|
+
} from "./chunk-W5CD7WTX.js";
|
|
27
25
|
|
|
28
26
|
// src/commands/uninstall.ts
|
|
29
27
|
import { existsSync, rmSync } from "fs";
|
|
@@ -80,7 +78,7 @@ function nothingToRemove(d) {
|
|
|
80
78
|
}
|
|
81
79
|
async function uninstallCommand() {
|
|
82
80
|
console.log(`
|
|
83
|
-
=== ${chalk.red("Coding Friend Uninstall")} ===`);
|
|
81
|
+
=== \u{1F44B} ${chalk.red("Coding Friend Uninstall")} \u{1F44B} ===`);
|
|
84
82
|
if (!commandExists("claude")) {
|
|
85
83
|
log.error("Claude CLI not found. Cannot uninstall plugin without it.");
|
|
86
84
|
log.dim(
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLatestVersion,
|
|
3
|
+
semverCompare,
|
|
4
|
+
updateCommand
|
|
5
|
+
} from "./chunk-JS75SVQA.js";
|
|
6
|
+
import "./chunk-BPLN4LDL.js";
|
|
7
|
+
import "./chunk-FYGACWU6.js";
|
|
8
|
+
import "./chunk-PGLUEN7D.js";
|
|
9
|
+
import "./chunk-UFGNO6CW.js";
|
|
10
|
+
import "./chunk-TPRZHSFS.js";
|
|
11
|
+
import "./chunk-W5CD7WTX.js";
|
|
12
|
+
export {
|
|
13
|
+
getLatestVersion,
|
|
14
|
+
semverCompare,
|
|
15
|
+
updateCommand
|
|
16
|
+
};
|
package/package.json
CHANGED
package/dist/chunk-IUTXHCP7.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// src/lib/json.ts
|
|
2
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
-
import { dirname } from "path";
|
|
4
|
-
function readJson(filePath) {
|
|
5
|
-
try {
|
|
6
|
-
const content = readFileSync(filePath, "utf-8");
|
|
7
|
-
return JSON.parse(content);
|
|
8
|
-
} catch {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
function writeJson(filePath, data) {
|
|
13
|
-
const dir = dirname(filePath);
|
|
14
|
-
if (!existsSync(dir)) {
|
|
15
|
-
mkdirSync(dir, { recursive: true });
|
|
16
|
-
}
|
|
17
|
-
writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
18
|
-
}
|
|
19
|
-
function mergeJson(filePath, data) {
|
|
20
|
-
const existing = readJson(filePath) ?? {};
|
|
21
|
-
writeJson(filePath, { ...existing, ...data });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export {
|
|
25
|
-
readJson,
|
|
26
|
-
writeJson,
|
|
27
|
-
mergeJson
|
|
28
|
-
};
|
package/dist/chunk-WK5YYHXM.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
globalConfigPath,
|
|
3
|
-
localConfigPath,
|
|
4
|
-
resolvePath
|
|
5
|
-
} from "./chunk-WHCJT7E2.js";
|
|
6
|
-
import {
|
|
7
|
-
readJson
|
|
8
|
-
} from "./chunk-IUTXHCP7.js";
|
|
9
|
-
|
|
10
|
-
// src/lib/config.ts
|
|
11
|
-
function resolveDocsDir(explicitPath) {
|
|
12
|
-
if (explicitPath) {
|
|
13
|
-
return resolvePath(explicitPath);
|
|
14
|
-
}
|
|
15
|
-
const local = readJson(localConfigPath());
|
|
16
|
-
if (local?.learn?.outputDir) {
|
|
17
|
-
return resolvePath(local.learn.outputDir);
|
|
18
|
-
}
|
|
19
|
-
const global = readJson(globalConfigPath());
|
|
20
|
-
if (global?.learn?.outputDir) {
|
|
21
|
-
return resolvePath(global.learn.outputDir);
|
|
22
|
-
}
|
|
23
|
-
return resolvePath("docs/learn");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// src/lib/lib-path.ts
|
|
27
|
-
import { existsSync } from "fs";
|
|
28
|
-
import { dirname, join } from "path";
|
|
29
|
-
import { fileURLToPath } from "url";
|
|
30
|
-
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
31
|
-
function getLibPath(name) {
|
|
32
|
-
const bundled = join(__dirname, "..", "lib", name);
|
|
33
|
-
if (existsSync(bundled)) return bundled;
|
|
34
|
-
const dev = join(__dirname, "..", "..", "lib", name);
|
|
35
|
-
if (existsSync(dev)) return dev;
|
|
36
|
-
throw new Error(
|
|
37
|
-
`Could not find lib/${name}. Ensure it exists in the CLI package.`
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export {
|
|
42
|
-
resolveDocsDir,
|
|
43
|
-
getLibPath
|
|
44
|
-
};
|