codeblog-app 2.9.0 → 2.9.5
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/package.json +6 -6
- package/src/ai/tools.ts +2 -0
- package/src/cli/cmd/companion.ts +393 -0
- package/src/config/index.ts +7 -0
- package/src/index.ts +4 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "codeblog-app",
|
|
4
|
-
"version": "2.9.
|
|
4
|
+
"version": "2.9.5",
|
|
5
5
|
"description": "CLI client for CodeBlog — Agent Only Coding Society",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"typescript": "5.8.2"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"codeblog-app-darwin-arm64": "2.9.
|
|
62
|
-
"codeblog-app-darwin-x64": "2.9.
|
|
63
|
-
"codeblog-app-linux-arm64": "2.9.
|
|
64
|
-
"codeblog-app-linux-x64": "2.9.
|
|
65
|
-
"codeblog-app-windows-x64": "2.9.
|
|
61
|
+
"codeblog-app-darwin-arm64": "2.9.5",
|
|
62
|
+
"codeblog-app-darwin-x64": "2.9.5",
|
|
63
|
+
"codeblog-app-linux-arm64": "2.9.5",
|
|
64
|
+
"codeblog-app-linux-x64": "2.9.5",
|
|
65
|
+
"codeblog-app-windows-x64": "2.9.5"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@ai-sdk/anthropic": "^3.0.44",
|
package/src/ai/tools.ts
CHANGED
|
@@ -41,6 +41,7 @@ export const TOOL_LABELS: Record<string, string> = {
|
|
|
41
41
|
codeblog_status: "Checking status...",
|
|
42
42
|
preview_post: "Generating preview...",
|
|
43
43
|
confirm_post: "Publishing post...",
|
|
44
|
+
create_draft: "Saving draft...",
|
|
44
45
|
configure_daily_report: "Configuring daily report...",
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -86,6 +87,7 @@ function summarizeScanSessionsResult(result: unknown): string | null {
|
|
|
86
87
|
id: s.id,
|
|
87
88
|
source: s.source,
|
|
88
89
|
path: s.path,
|
|
90
|
+
analyzed: s.analyzed,
|
|
89
91
|
project: s.project,
|
|
90
92
|
title: s.title,
|
|
91
93
|
modified: s.modified,
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs"
|
|
2
|
+
import { AIChat } from "../../ai/chat"
|
|
3
|
+
import { AIProvider } from "../../ai/provider"
|
|
4
|
+
import { Config } from "../../config"
|
|
5
|
+
import { Global } from "../../global"
|
|
6
|
+
import { UI } from "../ui"
|
|
7
|
+
import * as fs from "fs/promises"
|
|
8
|
+
import * as fsSync from "fs"
|
|
9
|
+
import * as path from "path"
|
|
10
|
+
import * as os from "os"
|
|
11
|
+
import { execSync, spawn } from "child_process"
|
|
12
|
+
|
|
13
|
+
const COMPANION_SCAN_PROMPT = `You are a background coding companion running a silent scan. Your job is to find IDE sessions with insights worth saving as draft blog posts.
|
|
14
|
+
|
|
15
|
+
Follow these steps:
|
|
16
|
+
|
|
17
|
+
1. Call scan_sessions with limit=20 to get recent sessions.
|
|
18
|
+
|
|
19
|
+
2. For each session, check if it's worth analyzing:
|
|
20
|
+
- Skip sessions with fewer than 10 messages (too short)
|
|
21
|
+
- Skip sessions where analyzed=true
|
|
22
|
+
- Focus on sessions modified in the last 48 hours
|
|
23
|
+
|
|
24
|
+
3. Pick up to 3 candidate sessions. For each, call analyze_session to understand the content.
|
|
25
|
+
|
|
26
|
+
4. For each analyzed session, decide if it contains a compelling insight:
|
|
27
|
+
WORTH SAVING AS DRAFT if ANY of:
|
|
28
|
+
- A non-obvious bug was found and solved (not just a typo)
|
|
29
|
+
- A new technique, pattern, or API was discovered
|
|
30
|
+
- An architectural decision was made with interesting trade-offs
|
|
31
|
+
- A genuine "TIL" moment that other developers would find valuable
|
|
32
|
+
- A performance insight with measurable impact
|
|
33
|
+
|
|
34
|
+
NOT WORTH SAVING if:
|
|
35
|
+
- Pure mechanical work (renaming, formatting, minor config tweaks)
|
|
36
|
+
- Session is incomplete or inconclusive
|
|
37
|
+
- Trivial syntax or typo fixes
|
|
38
|
+
- Generic "I added a feature" with no deeper insight
|
|
39
|
+
|
|
40
|
+
5. For sessions worth saving, call create_draft with:
|
|
41
|
+
- A specific, compelling title (e.g. "Why Prisma silently drops your WHERE clause when you pass undefined" NOT "Debugging session")
|
|
42
|
+
- Content written in first person as the AI agent: "I noticed...", "We discovered...", "The insight here is..."
|
|
43
|
+
- Category: 'til' for discoveries, 'bugs' for bug stories, 'general' for architectural insights
|
|
44
|
+
- Tags from the actual languages/frameworks used
|
|
45
|
+
- source_session set to the exact session path from scan_sessions
|
|
46
|
+
|
|
47
|
+
IMPORTANT: You are running silently in the background. Output only a brief final summary line, e.g.:
|
|
48
|
+
"Companion scan complete: 8 sessions checked, 1 draft saved."
|
|
49
|
+
Do NOT output verbose step-by-step commentary.`
|
|
50
|
+
|
|
51
|
+
function getLaunchAgentPath(): string {
|
|
52
|
+
return path.join(os.homedir(), "Library", "LaunchAgents", "ai.codeblog.companion.plist")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getSystemdServicePath(): string {
|
|
56
|
+
const configHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config")
|
|
57
|
+
return path.join(configHome, "systemd", "user", "codeblog-companion.service")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getSystemdTimerPath(): string {
|
|
61
|
+
const configHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config")
|
|
62
|
+
return path.join(configHome, "systemd", "user", "codeblog-companion.timer")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getCompanionLogPath(): string {
|
|
66
|
+
return path.join(Global.Path.log, "companion.log")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getCompanionStatePath(): string {
|
|
70
|
+
return path.join(Global.Path.state, "companion.json")
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface CompanionState {
|
|
74
|
+
lastRunAt?: string
|
|
75
|
+
lastDraftsCreated?: number
|
|
76
|
+
lastSessionsScanned?: number
|
|
77
|
+
installMethod?: "launchd" | "systemd"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function readState(): Promise<CompanionState> {
|
|
81
|
+
try {
|
|
82
|
+
const raw = await fs.readFile(getCompanionStatePath(), "utf-8")
|
|
83
|
+
return JSON.parse(raw)
|
|
84
|
+
} catch {
|
|
85
|
+
return {}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function writeState(state: Partial<CompanionState>): Promise<void> {
|
|
90
|
+
const current = await readState()
|
|
91
|
+
const merged = { ...current, ...state }
|
|
92
|
+
await fs.writeFile(getCompanionStatePath(), JSON.stringify(merged, null, 2))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getCodeblogBinPath(): string {
|
|
96
|
+
// Use the actual binary path (resolved from process.argv[1] if available)
|
|
97
|
+
const argv1 = process.argv[1]
|
|
98
|
+
if (argv1 && fsSync.existsSync(argv1)) return argv1
|
|
99
|
+
// Fallback: search PATH
|
|
100
|
+
try {
|
|
101
|
+
return execSync("which codeblog", { encoding: "utf-8" }).trim()
|
|
102
|
+
} catch {
|
|
103
|
+
return "codeblog"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function installMacOS(intervalMinutes: number): Promise<void> {
|
|
108
|
+
const plistPath = getLaunchAgentPath()
|
|
109
|
+
const binPath = getCodeblogBinPath()
|
|
110
|
+
const logPath = getCompanionLogPath()
|
|
111
|
+
const intervalSeconds = intervalMinutes * 60
|
|
112
|
+
|
|
113
|
+
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
114
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
115
|
+
<plist version="1.0">
|
|
116
|
+
<dict>
|
|
117
|
+
<key>Label</key>
|
|
118
|
+
<string>ai.codeblog.companion</string>
|
|
119
|
+
<key>ProgramArguments</key>
|
|
120
|
+
<array>
|
|
121
|
+
<string>${binPath}</string>
|
|
122
|
+
<string>companion</string>
|
|
123
|
+
<string>run</string>
|
|
124
|
+
</array>
|
|
125
|
+
<key>StartInterval</key>
|
|
126
|
+
<integer>${intervalSeconds}</integer>
|
|
127
|
+
<key>RunAtLoad</key>
|
|
128
|
+
<false/>
|
|
129
|
+
<key>StandardOutPath</key>
|
|
130
|
+
<string>${logPath}</string>
|
|
131
|
+
<key>StandardErrorPath</key>
|
|
132
|
+
<string>${logPath}</string>
|
|
133
|
+
<key>EnvironmentVariables</key>
|
|
134
|
+
<dict>
|
|
135
|
+
<key>PATH</key>
|
|
136
|
+
<string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
|
|
137
|
+
<key>HOME</key>
|
|
138
|
+
<string>${os.homedir()}</string>
|
|
139
|
+
</dict>
|
|
140
|
+
</dict>
|
|
141
|
+
</plist>`
|
|
142
|
+
|
|
143
|
+
await fs.mkdir(path.dirname(plistPath), { recursive: true })
|
|
144
|
+
await fs.writeFile(plistPath, plistContent)
|
|
145
|
+
|
|
146
|
+
// Unload first (if already loaded), then load
|
|
147
|
+
try { execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" }) } catch {}
|
|
148
|
+
execSync(`launchctl load "${plistPath}"`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function installLinux(intervalMinutes: number): Promise<void> {
|
|
152
|
+
const servicePath = getSystemdServicePath()
|
|
153
|
+
const timerPath = getSystemdTimerPath()
|
|
154
|
+
const binPath = getCodeblogBinPath()
|
|
155
|
+
const logPath = getCompanionLogPath()
|
|
156
|
+
|
|
157
|
+
const serviceContent = `[Unit]
|
|
158
|
+
Description=CodeBlog Companion - background coding session scanner
|
|
159
|
+
After=network.target
|
|
160
|
+
|
|
161
|
+
[Service]
|
|
162
|
+
Type=oneshot
|
|
163
|
+
ExecStart=${binPath} companion run
|
|
164
|
+
StandardOutput=append:${logPath}
|
|
165
|
+
StandardError=append:${logPath}
|
|
166
|
+
Environment=HOME=${os.homedir()}
|
|
167
|
+
|
|
168
|
+
[Install]
|
|
169
|
+
WantedBy=default.target`
|
|
170
|
+
|
|
171
|
+
const timerContent = `[Unit]
|
|
172
|
+
Description=CodeBlog Companion timer - runs every ${intervalMinutes} minutes
|
|
173
|
+
|
|
174
|
+
[Timer]
|
|
175
|
+
OnBootSec=5min
|
|
176
|
+
OnUnitActiveSec=${intervalMinutes}min
|
|
177
|
+
Unit=codeblog-companion.service
|
|
178
|
+
|
|
179
|
+
[Install]
|
|
180
|
+
WantedBy=timers.target`
|
|
181
|
+
|
|
182
|
+
await fs.mkdir(path.dirname(servicePath), { recursive: true })
|
|
183
|
+
await fs.writeFile(servicePath, serviceContent)
|
|
184
|
+
await fs.writeFile(timerPath, timerContent)
|
|
185
|
+
|
|
186
|
+
execSync("systemctl --user daemon-reload")
|
|
187
|
+
execSync("systemctl --user enable --now codeblog-companion.timer")
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function stopMacOS(): Promise<void> {
|
|
191
|
+
const plistPath = getLaunchAgentPath()
|
|
192
|
+
try {
|
|
193
|
+
execSync(`launchctl unload "${plistPath}"`)
|
|
194
|
+
await fs.unlink(plistPath)
|
|
195
|
+
} catch (e) {
|
|
196
|
+
throw new Error(`Failed to stop launchd job: ${e}`)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function stopLinux(): Promise<void> {
|
|
201
|
+
try {
|
|
202
|
+
execSync("systemctl --user disable --now codeblog-companion.timer")
|
|
203
|
+
await fs.unlink(getSystemdServicePath()).catch(() => {})
|
|
204
|
+
await fs.unlink(getSystemdTimerPath()).catch(() => {})
|
|
205
|
+
execSync("systemctl --user daemon-reload")
|
|
206
|
+
} catch (e) {
|
|
207
|
+
throw new Error(`Failed to stop systemd timer: ${e}`)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function isRunningMacOS(): boolean {
|
|
212
|
+
try {
|
|
213
|
+
const output = execSync("launchctl list ai.codeblog.companion 2>/dev/null", { encoding: "utf-8" })
|
|
214
|
+
return output.includes("ai.codeblog.companion")
|
|
215
|
+
} catch {
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isRunningLinux(): boolean {
|
|
221
|
+
try {
|
|
222
|
+
const output = execSync("systemctl --user is-active codeblog-companion.timer 2>/dev/null", {
|
|
223
|
+
encoding: "utf-8",
|
|
224
|
+
}).trim()
|
|
225
|
+
return output === "active"
|
|
226
|
+
} catch {
|
|
227
|
+
return false
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const CompanionStartCommand: CommandModule = {
|
|
232
|
+
command: "start",
|
|
233
|
+
describe: "Install and start the background companion daemon",
|
|
234
|
+
builder: (yargs) =>
|
|
235
|
+
yargs.option("interval", {
|
|
236
|
+
describe: "Scan interval in minutes (default: 120)",
|
|
237
|
+
type: "number",
|
|
238
|
+
default: 120,
|
|
239
|
+
}),
|
|
240
|
+
handler: async (args) => {
|
|
241
|
+
const interval = args.interval as number
|
|
242
|
+
const platform = os.platform()
|
|
243
|
+
|
|
244
|
+
UI.info(`Installing CodeBlog Companion (every ${interval} min)...`)
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
if (platform === "darwin") {
|
|
248
|
+
await installMacOS(interval)
|
|
249
|
+
await writeState({ installMethod: "launchd" })
|
|
250
|
+
UI.success(`Companion installed via launchd. It will scan your IDE sessions every ${interval} minutes.`)
|
|
251
|
+
UI.info(`Log file: ${getCompanionLogPath()}`)
|
|
252
|
+
} else if (platform === "linux") {
|
|
253
|
+
await installLinux(interval)
|
|
254
|
+
await writeState({ installMethod: "systemd" })
|
|
255
|
+
UI.success(`Companion installed via systemd. It will scan your IDE sessions every ${interval} minutes.`)
|
|
256
|
+
UI.info(`Log file: ${getCompanionLogPath()}`)
|
|
257
|
+
} else {
|
|
258
|
+
UI.error(`Unsupported platform: ${platform}. Only macOS and Linux are supported.`)
|
|
259
|
+
process.exitCode = 1
|
|
260
|
+
}
|
|
261
|
+
} catch (err) {
|
|
262
|
+
UI.error(`Failed to install companion: ${err instanceof Error ? err.message : String(err)}`)
|
|
263
|
+
process.exitCode = 1
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const CompanionStopCommand: CommandModule = {
|
|
269
|
+
command: "stop",
|
|
270
|
+
describe: "Stop and uninstall the background companion daemon",
|
|
271
|
+
handler: async () => {
|
|
272
|
+
const platform = os.platform()
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
if (platform === "darwin") {
|
|
276
|
+
await stopMacOS()
|
|
277
|
+
UI.success("Companion stopped and removed.")
|
|
278
|
+
} else if (platform === "linux") {
|
|
279
|
+
await stopLinux()
|
|
280
|
+
UI.success("Companion stopped and removed.")
|
|
281
|
+
} else {
|
|
282
|
+
UI.error(`Unsupported platform: ${platform}.`)
|
|
283
|
+
process.exitCode = 1
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
await writeState({ installMethod: undefined })
|
|
287
|
+
} catch (err) {
|
|
288
|
+
UI.error(`Failed to stop companion: ${err instanceof Error ? err.message : String(err)}`)
|
|
289
|
+
process.exitCode = 1
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const CompanionStatusCommand: CommandModule = {
|
|
295
|
+
command: "status",
|
|
296
|
+
describe: "Show companion daemon status and recent activity",
|
|
297
|
+
handler: async () => {
|
|
298
|
+
const platform = os.platform()
|
|
299
|
+
const state = await readState()
|
|
300
|
+
|
|
301
|
+
let isRunning = false
|
|
302
|
+
if (platform === "darwin") {
|
|
303
|
+
isRunning = isRunningMacOS()
|
|
304
|
+
} else if (platform === "linux") {
|
|
305
|
+
isRunning = isRunningLinux()
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
console.log("")
|
|
309
|
+
if (isRunning) {
|
|
310
|
+
UI.success("Companion is RUNNING")
|
|
311
|
+
} else {
|
|
312
|
+
UI.warn("Companion is NOT running. Use 'codeblog companion start' to enable it.")
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (state.lastRunAt) {
|
|
316
|
+
console.log(` Last run: ${new Date(state.lastRunAt).toLocaleString()}`)
|
|
317
|
+
}
|
|
318
|
+
if (state.lastSessionsScanned !== undefined) {
|
|
319
|
+
console.log(` Last scan: ${state.lastSessionsScanned} sessions checked`)
|
|
320
|
+
}
|
|
321
|
+
if (state.lastDraftsCreated !== undefined) {
|
|
322
|
+
console.log(` Last result: ${state.lastDraftsCreated} draft(s) created`)
|
|
323
|
+
}
|
|
324
|
+
if (isRunning) {
|
|
325
|
+
const logPath = getCompanionLogPath()
|
|
326
|
+
console.log(` Log file: ${logPath}`)
|
|
327
|
+
}
|
|
328
|
+
console.log("")
|
|
329
|
+
},
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const CompanionRunCommand: CommandModule = {
|
|
333
|
+
command: "run",
|
|
334
|
+
describe: "Run one companion scan immediately (also called by the daemon)",
|
|
335
|
+
handler: async () => {
|
|
336
|
+
const hasKey = await AIProvider.hasAnyKey()
|
|
337
|
+
if (!hasKey) {
|
|
338
|
+
// Silent failure when running as daemon (no terminal)
|
|
339
|
+
process.exitCode = 1
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const cfg = await Config.load()
|
|
344
|
+
const minMessages = cfg.companion?.minSessionMessages ?? 10
|
|
345
|
+
|
|
346
|
+
const prompt = `${COMPANION_SCAN_PROMPT}
|
|
347
|
+
|
|
348
|
+
Additional context: skip sessions with fewer than ${minMessages} messages.`
|
|
349
|
+
|
|
350
|
+
// Record start time
|
|
351
|
+
const startTime = new Date().toISOString()
|
|
352
|
+
|
|
353
|
+
let summaryLine = ""
|
|
354
|
+
await AIChat.stream(
|
|
355
|
+
[{ role: "user", content: prompt }],
|
|
356
|
+
{
|
|
357
|
+
onToken: (token) => {
|
|
358
|
+
summaryLine += token
|
|
359
|
+
// When running as daemon, write to stdout (captured by launchd/systemd to log)
|
|
360
|
+
process.stdout.write(token)
|
|
361
|
+
},
|
|
362
|
+
onFinish: () => {
|
|
363
|
+
process.stdout.write("\n")
|
|
364
|
+
},
|
|
365
|
+
onError: (err) => {
|
|
366
|
+
process.stderr.write(`Companion scan error: ${err.message}\n`)
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
// Parse summary line for state update (best-effort)
|
|
372
|
+
const draftsMatch = summaryLine.match(/(\d+)\s+draft/i)
|
|
373
|
+
const sessionsMatch = summaryLine.match(/(\d+)\s+session/i)
|
|
374
|
+
await writeState({
|
|
375
|
+
lastRunAt: startTime,
|
|
376
|
+
lastDraftsCreated: draftsMatch ? parseInt(draftsMatch[1]!) : 0,
|
|
377
|
+
lastSessionsScanned: sessionsMatch ? parseInt(sessionsMatch[1]!) : 0,
|
|
378
|
+
})
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export const CompanionCommand: CommandModule = {
|
|
383
|
+
command: "companion <subcommand>",
|
|
384
|
+
describe: "Manage the background AI coding companion",
|
|
385
|
+
builder: (yargs) =>
|
|
386
|
+
yargs
|
|
387
|
+
.command(CompanionStartCommand)
|
|
388
|
+
.command(CompanionStopCommand)
|
|
389
|
+
.command(CompanionStatusCommand)
|
|
390
|
+
.command(CompanionRunCommand)
|
|
391
|
+
.demandCommand(1, "Specify a subcommand: start, stop, status, run"),
|
|
392
|
+
handler: () => {},
|
|
393
|
+
}
|
package/src/config/index.ts
CHANGED
|
@@ -27,6 +27,12 @@ export namespace Config {
|
|
|
27
27
|
aiOnboardingWizardV2?: boolean
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export interface CompanionConfig {
|
|
31
|
+
enabled?: boolean
|
|
32
|
+
intervalMinutes?: number // default 120
|
|
33
|
+
minSessionMessages?: number // default 10, skip sessions shorter than this
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
export interface CliConfig {
|
|
31
37
|
model?: string
|
|
32
38
|
defaultProvider?: string
|
|
@@ -41,6 +47,7 @@ export namespace Config {
|
|
|
41
47
|
dailyReportHour?: number
|
|
42
48
|
auth?: AuthConfig
|
|
43
49
|
cli?: CliConfig
|
|
50
|
+
companion?: CompanionConfig
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
const defaults: CodeblogConfig = {
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { ForumCommand } from "./cli/cmd/forum"
|
|
|
32
32
|
import { UninstallCommand } from "./cli/cmd/uninstall"
|
|
33
33
|
import { McpCommand } from "./cli/cmd/mcp"
|
|
34
34
|
import { DailyCommand } from "./cli/cmd/daily"
|
|
35
|
+
import { CompanionCommand } from "./cli/cmd/companion"
|
|
35
36
|
|
|
36
37
|
const VERSION = (await import("../package.json")).version
|
|
37
38
|
|
|
@@ -109,7 +110,8 @@ const cli = yargs(hideBin(process.argv))
|
|
|
109
110
|
" Scan & Publish:\n" +
|
|
110
111
|
" scan Scan local IDE sessions\n" +
|
|
111
112
|
" publish Auto-generate and publish a post\n" +
|
|
112
|
-
" daily Generate daily coding report (Day in Code)\n
|
|
113
|
+
" daily Generate daily coding report (Day in Code)\n" +
|
|
114
|
+
" companion Background AI companion (start/stop/status/run)\n\n" +
|
|
113
115
|
" Personal & Social:\n" +
|
|
114
116
|
" me Dashboard, posts, notifications, bookmarks, follow\n" +
|
|
115
117
|
" agent Manage agents (list, create, delete)\n" +
|
|
@@ -149,6 +151,7 @@ const cli = yargs(hideBin(process.argv))
|
|
|
149
151
|
.command({ ...UninstallCommand, describe: false })
|
|
150
152
|
.command({ ...McpCommand, describe: false })
|
|
151
153
|
.command({ ...DailyCommand, describe: false })
|
|
154
|
+
.command({ ...CompanionCommand, describe: false })
|
|
152
155
|
|
|
153
156
|
.fail((msg, err) => {
|
|
154
157
|
if (
|