vibeflow-cli 0.1.2 → 0.1.3
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/index.js +338 -50
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -73,15 +73,44 @@ const saveSessions = (sessions) => {
|
|
|
73
73
|
const loadState = () => {
|
|
74
74
|
const data = loadJson(STATE_FILE, {
|
|
75
75
|
activeByRepo: {},
|
|
76
|
-
idleTimeoutMinutes: DEFAULT_IDLE_MINUTES
|
|
76
|
+
idleTimeoutMinutes: DEFAULT_IDLE_MINUTES,
|
|
77
|
+
pendingTimeEchoesByRepo: {},
|
|
78
|
+
deliveredTimeEchoesByRepo: {},
|
|
79
|
+
repoParkedThoughtsByRepo: {}
|
|
77
80
|
});
|
|
78
81
|
if (!data || typeof data !== "object") {
|
|
79
|
-
return {
|
|
80
|
-
|
|
82
|
+
return {
|
|
83
|
+
activeByRepo: {},
|
|
84
|
+
idleTimeoutMinutes: DEFAULT_IDLE_MINUTES,
|
|
85
|
+
pendingTimeEchoesByRepo: {},
|
|
86
|
+
deliveredTimeEchoesByRepo: {},
|
|
87
|
+
repoParkedThoughtsByRepo: {}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const pending = data.pendingTimeEchoesByRepo || {};
|
|
91
|
+
const delivered = data.deliveredTimeEchoesByRepo || {};
|
|
92
|
+
const parked = data.repoParkedThoughtsByRepo || {};
|
|
93
|
+
const normalizeEchoMap = (map) =>
|
|
94
|
+
Object.fromEntries(
|
|
95
|
+
Object.entries(map).map(([key, list]) => [
|
|
96
|
+
key,
|
|
97
|
+
Array.isArray(list) ? list.map(normalizeTimeEcho) : []
|
|
98
|
+
])
|
|
99
|
+
);
|
|
100
|
+
const normalizeThoughtMap = (map) =>
|
|
101
|
+
Object.fromEntries(
|
|
102
|
+
Object.entries(map).map(([key, list]) => [
|
|
103
|
+
key,
|
|
104
|
+
Array.isArray(list) ? list.map(normalizeParkedThought) : []
|
|
105
|
+
])
|
|
106
|
+
);
|
|
81
107
|
return {
|
|
82
108
|
activeByRepo: data.activeByRepo || {},
|
|
83
109
|
idleTimeoutMinutes:
|
|
84
|
-
typeof data.idleTimeoutMinutes === "number" ? data.idleTimeoutMinutes : DEFAULT_IDLE_MINUTES
|
|
110
|
+
typeof data.idleTimeoutMinutes === "number" ? data.idleTimeoutMinutes : DEFAULT_IDLE_MINUTES,
|
|
111
|
+
pendingTimeEchoesByRepo: normalizeEchoMap(pending),
|
|
112
|
+
deliveredTimeEchoesByRepo: normalizeEchoMap(delivered),
|
|
113
|
+
repoParkedThoughtsByRepo: normalizeThoughtMap(parked)
|
|
85
114
|
};
|
|
86
115
|
};
|
|
87
116
|
|
|
@@ -160,15 +189,32 @@ const renderBigTime = (text) => {
|
|
|
160
189
|
return lines.map((line) => line.trimEnd());
|
|
161
190
|
};
|
|
162
191
|
|
|
163
|
-
const formatTime = (iso) => {
|
|
164
|
-
try {
|
|
165
|
-
return new Date(iso).toLocaleString();
|
|
166
|
-
} catch {
|
|
167
|
-
return iso;
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
const
|
|
192
|
+
const formatTime = (iso) => {
|
|
193
|
+
try {
|
|
194
|
+
return new Date(iso).toLocaleString();
|
|
195
|
+
} catch {
|
|
196
|
+
return iso;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const createId = () => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
201
|
+
|
|
202
|
+
const normalizeParkedThought = (thought) => ({
|
|
203
|
+
id: thought?.id || createId(),
|
|
204
|
+
text: thought?.text || "",
|
|
205
|
+
createdAt: thought?.createdAt || nowIso()
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const normalizeTimeEcho = (echo) => ({
|
|
209
|
+
id: echo?.id || createId(),
|
|
210
|
+
text: echo?.text || "",
|
|
211
|
+
createdAt: echo?.createdAt || nowIso(),
|
|
212
|
+
deliverAt: echo?.deliverAt || nowIso(),
|
|
213
|
+
deliveredAt: echo?.deliveredAt,
|
|
214
|
+
sourceSessionId: echo?.sourceSessionId
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const findSessionById = (sessions, id) => sessions.find((s) => s.id === id);
|
|
172
218
|
|
|
173
219
|
const getLastSessionForRepo = (sessions, repoKey) => {
|
|
174
220
|
const filtered = sessions.filter((s) => s.repoKey === repoKey);
|
|
@@ -187,34 +233,39 @@ Usage:
|
|
|
187
233
|
vf start [path] Start a session
|
|
188
234
|
vf intent "text" Set intent for current repo session
|
|
189
235
|
vf park "note" Park a thought
|
|
236
|
+
vf echo "message" Queue a time echo for next session
|
|
237
|
+
vf echo list List queued/delivered echoes
|
|
238
|
+
vf echo park <id> Park a delivered echo
|
|
239
|
+
vf echo discard <id> Discard a delivered echo
|
|
190
240
|
vf status Show current session status
|
|
191
241
|
vf status --watch Live session timer
|
|
192
242
|
vf timer Live session timer (alias)
|
|
193
243
|
vf idle [value] Get/set idle timeout (minutes or Nh)
|
|
194
244
|
vf touch Refresh activity timestamp
|
|
195
245
|
vf resume [path] Show last session summary
|
|
196
|
-
vf history [path] List recent sessions
|
|
246
|
+
vf history [path] List recent sessions
|
|
197
247
|
vf end End current session
|
|
198
248
|
vf receipt [id] Print receipt (defaults to last session)
|
|
199
249
|
vf help Show this help
|
|
200
250
|
`);
|
|
201
251
|
};
|
|
202
252
|
|
|
203
|
-
const printSessionSummary = (session, active = false) => {
|
|
204
|
-
const durationMs = session.endedAt
|
|
205
|
-
? Date.parse(session.endedAt) - Date.parse(session.startedAt)
|
|
206
|
-
: Date.now() - Date.parse(session.startedAt);
|
|
207
|
-
const state = active ? "Active" : session.endedAt ? "Ended" : "In progress";
|
|
253
|
+
const printSessionSummary = (session, active = false) => {
|
|
254
|
+
const durationMs = session.endedAt
|
|
255
|
+
? Date.parse(session.endedAt) - Date.parse(session.startedAt)
|
|
256
|
+
: Date.now() - Date.parse(session.startedAt);
|
|
257
|
+
const state = active ? "Active" : session.endedAt ? "Ended" : "In progress";
|
|
208
258
|
console.log(`Repo: ${session.repoName || path.basename(session.repoKey)}`);
|
|
209
259
|
console.log(`Session ID: ${session.id}`);
|
|
210
260
|
console.log(`Status: ${state}`);
|
|
211
|
-
console.log(`Started: ${formatTime(session.startedAt)}`);
|
|
212
|
-
if (session.endedAt) {
|
|
213
|
-
console.log(`Ended: ${formatTime(session.endedAt)}`);
|
|
214
|
-
}
|
|
215
|
-
console.log(`Duration: ${formatDuration(durationMs)}`);
|
|
216
|
-
console.log(`Intent: ${session.intent?.text || "Not set"}`);
|
|
261
|
+
console.log(`Started: ${formatTime(session.startedAt)}`);
|
|
262
|
+
if (session.endedAt) {
|
|
263
|
+
console.log(`Ended: ${formatTime(session.endedAt)}`);
|
|
264
|
+
}
|
|
265
|
+
console.log(`Duration: ${formatDuration(durationMs)}`);
|
|
266
|
+
console.log(`Intent: ${session.intent?.text || "Not set"}`);
|
|
217
267
|
console.log(`Parked: ${(session.parkedThoughts || []).length}`);
|
|
268
|
+
console.log(`Time Echoes: ${(session.timeEchoes || []).length}`);
|
|
218
269
|
};
|
|
219
270
|
|
|
220
271
|
const getIdleTimeoutMs = (state) => {
|
|
@@ -233,9 +284,127 @@ const normalizeSession = (session) => {
|
|
|
233
284
|
if (!session.parkedThoughts) {
|
|
234
285
|
session.parkedThoughts = [];
|
|
235
286
|
}
|
|
287
|
+
session.parkedThoughts = session.parkedThoughts.map(normalizeParkedThought);
|
|
288
|
+
if (!session.timeEchoes) {
|
|
289
|
+
session.timeEchoes = session.timeEcho ? [normalizeTimeEcho(session.timeEcho)] : [];
|
|
290
|
+
} else {
|
|
291
|
+
session.timeEchoes = session.timeEchoes.map(normalizeTimeEcho);
|
|
292
|
+
}
|
|
236
293
|
return session;
|
|
237
294
|
};
|
|
238
295
|
|
|
296
|
+
const ensureGitignore = (repoKey) => {
|
|
297
|
+
const gitDir = path.join(repoKey, ".git");
|
|
298
|
+
if (!fs.existsSync(gitDir)) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const gitignorePath = path.join(repoKey, ".gitignore");
|
|
302
|
+
const entry = ".vibeflow/";
|
|
303
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
304
|
+
fs.writeFileSync(gitignorePath, `${entry}\n`, "utf8");
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const raw = fs.readFileSync(gitignorePath, "utf8");
|
|
308
|
+
const lines = raw.split(/\r?\n/);
|
|
309
|
+
if (lines.some((line) => line.trim() === entry || line.trim() === ".vibeflow")) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
lines.push(entry);
|
|
313
|
+
fs.writeFileSync(gitignorePath, `${lines.join("\n").replace(/\n+$/, "")}\n`, "utf8");
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const buildTracePayload = (session) => {
|
|
317
|
+
const durationMs = session.endedAt
|
|
318
|
+
? Date.parse(session.endedAt) - Date.parse(session.startedAt)
|
|
319
|
+
: Date.now() - Date.parse(session.startedAt);
|
|
320
|
+
return {
|
|
321
|
+
version: 1,
|
|
322
|
+
generatedAt: nowIso(),
|
|
323
|
+
sessionId: session.id,
|
|
324
|
+
repoKey: session.repoKey,
|
|
325
|
+
repoName: session.repoName,
|
|
326
|
+
startedAt: session.startedAt,
|
|
327
|
+
endedAt: session.endedAt || null,
|
|
328
|
+
durationMs,
|
|
329
|
+
intent: session.intent || null,
|
|
330
|
+
parkedThoughts: session.parkedThoughts || [],
|
|
331
|
+
timeEchoes: session.timeEchoes || []
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const writeTrace = (session) => {
|
|
336
|
+
try {
|
|
337
|
+
const dir = path.join(session.repoKey, ".vibeflow");
|
|
338
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
339
|
+
const payload = buildTracePayload(session);
|
|
340
|
+
fs.writeFileSync(path.join(dir, "trace.json"), JSON.stringify(payload, null, 2), "utf8");
|
|
341
|
+
ensureGitignore(session.repoKey);
|
|
342
|
+
} catch {
|
|
343
|
+
// ignore trace failures
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const readTrace = (repoKey) => {
|
|
348
|
+
try {
|
|
349
|
+
const filePath = path.join(repoKey, ".vibeflow", "trace.json");
|
|
350
|
+
if (!fs.existsSync(filePath)) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
354
|
+
return JSON.parse(raw);
|
|
355
|
+
} catch {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const queueTimeEchoes = (session, state) => {
|
|
361
|
+
if (!session.timeEchoes || session.timeEchoes.length === 0) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const existing = state.pendingTimeEchoesByRepo[session.repoKey] || [];
|
|
365
|
+
state.pendingTimeEchoesByRepo[session.repoKey] = existing.concat(session.timeEchoes);
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const deliverPendingEchoes = (repoKey, state) => {
|
|
369
|
+
const pending = state.pendingTimeEchoesByRepo[repoKey] || [];
|
|
370
|
+
if (pending.length === 0) {
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
373
|
+
delete state.pendingTimeEchoesByRepo[repoKey];
|
|
374
|
+
state.deliveredTimeEchoesByRepo[repoKey] = pending;
|
|
375
|
+
return pending;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const clearDeliveredEchoes = (repoKey, state) => {
|
|
379
|
+
if (state.deliveredTimeEchoesByRepo[repoKey]) {
|
|
380
|
+
delete state.deliveredTimeEchoesByRepo[repoKey];
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const showEchoes = (echoes) => {
|
|
385
|
+
if (!echoes || echoes.length === 0) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
console.log(c.magenta("Time Echoes:"));
|
|
389
|
+
echoes.forEach((echo) => {
|
|
390
|
+
console.log(`- ${echo.id}: ${echo.text}`);
|
|
391
|
+
});
|
|
392
|
+
console.log(c.gray("Use `vf echo park <id>` to park or `vf echo discard <id>` to delete."));
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const showTraceSummary = (trace) => {
|
|
396
|
+
if (!trace) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
console.log(c.blue("VibeTrace found"));
|
|
400
|
+
console.log(`Last intent: ${trace.intent?.text || "Not set"}`);
|
|
401
|
+
if (trace.durationMs) {
|
|
402
|
+
console.log(`Last session duration: ${formatDuration(trace.durationMs)}`);
|
|
403
|
+
}
|
|
404
|
+
console.log(`Parked thoughts: ${(trace.parkedThoughts || []).length}`);
|
|
405
|
+
console.log(`Time echoes: ${(trace.timeEchoes || []).length}`);
|
|
406
|
+
};
|
|
407
|
+
|
|
239
408
|
const ensureActiveSession = (sessions, state, repoKey) => {
|
|
240
409
|
const activeId = state.activeByRepo[repoKey];
|
|
241
410
|
if (!activeId) {
|
|
@@ -252,6 +421,8 @@ const ensureActiveSession = (sessions, state, repoKey) => {
|
|
|
252
421
|
if (Date.now() - last > timeoutMs) {
|
|
253
422
|
session.endedAt = nowIso();
|
|
254
423
|
session.endedReason = "idle";
|
|
424
|
+
queueTimeEchoes(session, state);
|
|
425
|
+
writeTrace(session);
|
|
255
426
|
saveSessions(sessions);
|
|
256
427
|
delete state.activeByRepo[repoKey];
|
|
257
428
|
saveState(state);
|
|
@@ -266,7 +437,7 @@ const startSession = (targetPath, watch) => {
|
|
|
266
437
|
const repoKey = getRepoKey(cwd);
|
|
267
438
|
const repoName = path.basename(repoKey);
|
|
268
439
|
const sessions = loadSessions();
|
|
269
|
-
const state = loadState();
|
|
440
|
+
const state = loadState();
|
|
270
441
|
|
|
271
442
|
const existing = ensureActiveSession(sessions, state, repoKey);
|
|
272
443
|
if (existing.session && !existing.session.endedAt) {
|
|
@@ -302,6 +473,14 @@ const startSession = (targetPath, watch) => {
|
|
|
302
473
|
console.log(c.dim("Terminal IDE - Intent - Context - Flow\n"));
|
|
303
474
|
console.log(c.green("Session started."));
|
|
304
475
|
printSessionSummary(session, true);
|
|
476
|
+
const trace = readTrace(repoKey);
|
|
477
|
+
showTraceSummary(trace);
|
|
478
|
+
clearDeliveredEchoes(repoKey, state);
|
|
479
|
+
const echoes = deliverPendingEchoes(repoKey, state);
|
|
480
|
+
if (echoes.length > 0) {
|
|
481
|
+
showEchoes(echoes);
|
|
482
|
+
saveState(state);
|
|
483
|
+
}
|
|
305
484
|
if (watch) {
|
|
306
485
|
renderLiveTimer(session);
|
|
307
486
|
return;
|
|
@@ -334,12 +513,12 @@ const setIntent = (text) => {
|
|
|
334
513
|
console.log("Intent saved.");
|
|
335
514
|
};
|
|
336
515
|
|
|
337
|
-
const parkThought = (text) => {
|
|
338
|
-
const value = text.trim();
|
|
339
|
-
if (!value) {
|
|
340
|
-
console.error("Parked thought text is required.");
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
516
|
+
const parkThought = (text) => {
|
|
517
|
+
const value = text.trim();
|
|
518
|
+
if (!value) {
|
|
519
|
+
console.error("Parked thought text is required.");
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
343
522
|
const repoKey = getRepoKey(process.cwd());
|
|
344
523
|
const sessions = loadSessions();
|
|
345
524
|
const state = loadState();
|
|
@@ -353,9 +532,15 @@ const parkThought = (text) => {
|
|
|
353
532
|
console.error("No active session. Run `vf start` first.");
|
|
354
533
|
return;
|
|
355
534
|
}
|
|
356
|
-
|
|
535
|
+
const thought = normalizeParkedThought({ text: value, createdAt: nowIso() });
|
|
536
|
+
active.session.parkedThoughts.push(thought);
|
|
537
|
+
if (!state.repoParkedThoughtsByRepo[repoKey]) {
|
|
538
|
+
state.repoParkedThoughtsByRepo[repoKey] = [];
|
|
539
|
+
}
|
|
540
|
+
state.repoParkedThoughtsByRepo[repoKey].push(thought);
|
|
357
541
|
active.session.lastActivityAt = nowIso();
|
|
358
542
|
saveSessions(sessions);
|
|
543
|
+
saveState(state);
|
|
359
544
|
console.log("Thought parked.");
|
|
360
545
|
};
|
|
361
546
|
|
|
@@ -434,21 +619,27 @@ const resume = (targetPath) => {
|
|
|
434
619
|
if (active.session) {
|
|
435
620
|
console.log("Active session:");
|
|
436
621
|
printSessionSummary(active.session, true);
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
if (active.autoEnded && active.endedSession) {
|
|
622
|
+
} else if (active.autoEnded && active.endedSession) {
|
|
440
623
|
console.log("Last session (auto-ended due to inactivity):");
|
|
441
624
|
printSessionSummary(active.endedSession, false);
|
|
442
|
-
|
|
625
|
+
} else {
|
|
626
|
+
const last = getLastSessionForRepo(sessions, repoKey);
|
|
627
|
+
if (!last) {
|
|
628
|
+
console.log("No sessions found for this repo.");
|
|
629
|
+
} else {
|
|
630
|
+
console.log("Last session:");
|
|
631
|
+
printSessionSummary(last, false);
|
|
632
|
+
}
|
|
443
633
|
}
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}
|
|
634
|
+
const trace = readTrace(repoKey);
|
|
635
|
+
showTraceSummary(trace);
|
|
636
|
+
clearDeliveredEchoes(repoKey, state);
|
|
637
|
+
const echoes = deliverPendingEchoes(repoKey, state);
|
|
638
|
+
if (echoes.length > 0) {
|
|
639
|
+
showEchoes(echoes);
|
|
640
|
+
saveState(state);
|
|
641
|
+
}
|
|
642
|
+
};
|
|
452
643
|
|
|
453
644
|
const history = (targetPath) => {
|
|
454
645
|
const cwd = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
@@ -473,7 +664,7 @@ const history = (targetPath) => {
|
|
|
473
664
|
};
|
|
474
665
|
|
|
475
666
|
const timer = () => status(true);
|
|
476
|
-
|
|
667
|
+
|
|
477
668
|
const end = () => {
|
|
478
669
|
const repoKey = getRepoKey(process.cwd());
|
|
479
670
|
const sessions = loadSessions();
|
|
@@ -489,6 +680,9 @@ const end = () => {
|
|
|
489
680
|
return;
|
|
490
681
|
}
|
|
491
682
|
active.session.endedAt = nowIso();
|
|
683
|
+
active.session.endedReason = "ended";
|
|
684
|
+
queueTimeEchoes(active.session, state);
|
|
685
|
+
writeTrace(active.session);
|
|
492
686
|
saveSessions(sessions);
|
|
493
687
|
delete state.activeByRepo[repoKey];
|
|
494
688
|
saveState(state);
|
|
@@ -496,7 +690,7 @@ const end = () => {
|
|
|
496
690
|
printSessionSummary(active.session, false);
|
|
497
691
|
process.exit(0);
|
|
498
692
|
};
|
|
499
|
-
|
|
693
|
+
|
|
500
694
|
const receipt = (id) => {
|
|
501
695
|
const sessions = loadSessions();
|
|
502
696
|
const repoKey = getRepoKey(process.cwd());
|
|
@@ -523,6 +717,97 @@ const receipt = (id) => {
|
|
|
523
717
|
console.log(`Duration: ${formatDuration(durationMs)}`);
|
|
524
718
|
console.log(`Intent: ${session.intent?.text || "Not set"}`);
|
|
525
719
|
console.log(`Parked: ${(session.parkedThoughts || []).length}`);
|
|
720
|
+
console.log(`Time Echoes: ${(session.timeEchoes || []).length}`);
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
const echoCommand = (argsList) => {
|
|
724
|
+
const repoKey = getRepoKey(process.cwd());
|
|
725
|
+
const sessions = loadSessions();
|
|
726
|
+
const state = loadState();
|
|
727
|
+
const sub = argsList[0];
|
|
728
|
+
if (!sub || sub === "list") {
|
|
729
|
+
const pending = state.pendingTimeEchoesByRepo[repoKey] || [];
|
|
730
|
+
const delivered = state.deliveredTimeEchoesByRepo[repoKey] || [];
|
|
731
|
+
if (pending.length === 0 && delivered.length === 0) {
|
|
732
|
+
console.log("No time echoes for this repo.");
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (pending.length > 0) {
|
|
736
|
+
console.log(c.blue("Queued (next session):"));
|
|
737
|
+
pending.forEach((echo) => {
|
|
738
|
+
console.log(`- ${echo.id}: ${echo.text}`);
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
if (delivered.length > 0) {
|
|
742
|
+
console.log(c.magenta("Delivered (waiting for action):"));
|
|
743
|
+
delivered.forEach((echo) => {
|
|
744
|
+
console.log(`- ${echo.id}: ${echo.text}`);
|
|
745
|
+
});
|
|
746
|
+
console.log(c.gray("Use `vf echo park <id>` or `vf echo discard <id>`."));
|
|
747
|
+
}
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
if (sub === "park" || sub === "discard") {
|
|
751
|
+
const id = argsList[1];
|
|
752
|
+
if (!id) {
|
|
753
|
+
console.error("Echo id is required.");
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const delivered = state.deliveredTimeEchoesByRepo[repoKey] || [];
|
|
757
|
+
const target = delivered.find((echo) => echo.id === id);
|
|
758
|
+
if (!target) {
|
|
759
|
+
console.error("Echo not found or already handled.");
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
state.deliveredTimeEchoesByRepo[repoKey] = delivered.filter((echo) => echo.id !== id);
|
|
763
|
+
if (sub === "park") {
|
|
764
|
+
if (!state.repoParkedThoughtsByRepo[repoKey]) {
|
|
765
|
+
state.repoParkedThoughtsByRepo[repoKey] = [];
|
|
766
|
+
}
|
|
767
|
+
const thought = normalizeParkedThought({ text: target.text, createdAt: nowIso() });
|
|
768
|
+
state.repoParkedThoughtsByRepo[repoKey].push(thought);
|
|
769
|
+
const active = ensureActiveSession(sessions, state, repoKey);
|
|
770
|
+
if (active.session) {
|
|
771
|
+
active.session.parkedThoughts.push(thought);
|
|
772
|
+
active.session.lastActivityAt = nowIso();
|
|
773
|
+
saveSessions(sessions);
|
|
774
|
+
}
|
|
775
|
+
saveState(state);
|
|
776
|
+
console.log("Echo parked as a thought.");
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
saveState(state);
|
|
780
|
+
console.log("Echo discarded.");
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
const text = argsList.join(" ").trim();
|
|
784
|
+
if (!text) {
|
|
785
|
+
console.error("Echo message is required.");
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
const active = ensureActiveSession(sessions, state, repoKey);
|
|
789
|
+
if (!active.session) {
|
|
790
|
+
if (active.autoEnded) {
|
|
791
|
+
console.error("Session auto-ended due to inactivity. Run `vf start` to begin a new one.");
|
|
792
|
+
printSessionSummary(active.endedSession, false);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
console.error("No active session. Run `vf start` first.");
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const echo = normalizeTimeEcho({
|
|
799
|
+
text,
|
|
800
|
+
createdAt: nowIso(),
|
|
801
|
+
deliverAt: nowIso(),
|
|
802
|
+
sourceSessionId: active.session.id
|
|
803
|
+
});
|
|
804
|
+
if (!active.session.timeEchoes) {
|
|
805
|
+
active.session.timeEchoes = [];
|
|
806
|
+
}
|
|
807
|
+
active.session.timeEchoes.push(echo);
|
|
808
|
+
active.session.lastActivityAt = nowIso();
|
|
809
|
+
saveSessions(sessions);
|
|
810
|
+
console.log("Time Echo queued for next session.");
|
|
526
811
|
};
|
|
527
812
|
|
|
528
813
|
const parseIdleMinutes = (value) => {
|
|
@@ -640,9 +925,12 @@ switch (command) {
|
|
|
640
925
|
case "end":
|
|
641
926
|
end();
|
|
642
927
|
break;
|
|
643
|
-
case "receipt":
|
|
644
|
-
receipt(args[1]);
|
|
645
|
-
break;
|
|
928
|
+
case "receipt":
|
|
929
|
+
receipt(args[1]);
|
|
930
|
+
break;
|
|
931
|
+
case "echo":
|
|
932
|
+
echoCommand(args.slice(1));
|
|
933
|
+
break;
|
|
646
934
|
case "help":
|
|
647
935
|
case "-h":
|
|
648
936
|
case "--help":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
{
|
|
2
2
|
"name": "vibeflow-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "VibeFlow CLI - intent, context, and session memory for any repo.",
|
|
5
5
|
"author": "Dev Kuns (https://github.com/atobouh)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,4 +13,4 @@
|
|
|
13
13
|
"README.md",
|
|
14
14
|
"package.json"
|
|
15
15
|
]
|
|
16
|
-
}
|
|
16
|
+
}
|