vibeflow-cli 0.1.1
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 +38 -0
- package/index.js +479 -0
- package/package.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# VibeFlow CLI
|
|
2
|
+
|
|
3
|
+
Terminal-first intent + context tracking.
|
|
4
|
+
|
|
5
|
+
## Install (from this repo)
|
|
6
|
+
```bash
|
|
7
|
+
cd cli
|
|
8
|
+
npm install -g .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Versioning
|
|
12
|
+
Before publishing, bump the version:
|
|
13
|
+
```bash
|
|
14
|
+
npm version patch
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Publish
|
|
18
|
+
```bash
|
|
19
|
+
npm publish --access public
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
```bash
|
|
24
|
+
vf start [path]
|
|
25
|
+
vf intent "text"
|
|
26
|
+
vf park "note"
|
|
27
|
+
vf status
|
|
28
|
+
vf status --watch
|
|
29
|
+
vf timer
|
|
30
|
+
vf resume [path]
|
|
31
|
+
vf history [path]
|
|
32
|
+
vf end
|
|
33
|
+
vf receipt [id]
|
|
34
|
+
vf help
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Data location
|
|
38
|
+
Stored locally in your user config directory. Override with `VF_DATA_DIR`.
|
package/index.js
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const command = args[0];
|
|
9
|
+
|
|
10
|
+
const isTty = process.stdout.isTTY;
|
|
11
|
+
const color = (code, text) => (isTty ? `\u001b[${code}m${text}\u001b[0m` : text);
|
|
12
|
+
const c = {
|
|
13
|
+
dim: (t) => color("2", t),
|
|
14
|
+
cyan: (t) => color("36", t),
|
|
15
|
+
magenta: (t) => color("35", t),
|
|
16
|
+
blue: (t) => color("34", t),
|
|
17
|
+
green: (t) => color("32", t),
|
|
18
|
+
yellow: (t) => color("33", t),
|
|
19
|
+
gray: (t) => color("90", t)
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const nowIso = () => new Date().toISOString();
|
|
23
|
+
|
|
24
|
+
const getDataDir = () => {
|
|
25
|
+
if (process.env.VF_DATA_DIR) {
|
|
26
|
+
return process.env.VF_DATA_DIR;
|
|
27
|
+
}
|
|
28
|
+
if (process.platform === "win32") {
|
|
29
|
+
return path.join(process.env.APPDATA || os.homedir(), "vibeflow-cli");
|
|
30
|
+
}
|
|
31
|
+
if (process.platform === "darwin") {
|
|
32
|
+
return path.join(os.homedir(), "Library", "Application Support", "vibeflow-cli");
|
|
33
|
+
}
|
|
34
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
35
|
+
return path.join(xdg, "vibeflow-cli");
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const DATA_DIR = getDataDir();
|
|
39
|
+
const SESSIONS_FILE = path.join(DATA_DIR, "sessions.json");
|
|
40
|
+
const STATE_FILE = path.join(DATA_DIR, "state.json");
|
|
41
|
+
|
|
42
|
+
const ensureDir = () => {
|
|
43
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const loadJson = (filePath, fallback) => {
|
|
47
|
+
try {
|
|
48
|
+
if (!fs.existsSync(filePath)) {
|
|
49
|
+
return fallback;
|
|
50
|
+
}
|
|
51
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
52
|
+
return JSON.parse(raw);
|
|
53
|
+
} catch {
|
|
54
|
+
return fallback;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const saveJson = (filePath, value) => {
|
|
59
|
+
ensureDir();
|
|
60
|
+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf8");
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const loadSessions = () => {
|
|
64
|
+
const data = loadJson(SESSIONS_FILE, []);
|
|
65
|
+
return Array.isArray(data) ? data : [];
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const saveSessions = (sessions) => {
|
|
69
|
+
saveJson(SESSIONS_FILE, sessions);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const loadState = () => {
|
|
73
|
+
const data = loadJson(STATE_FILE, { activeByRepo: {} });
|
|
74
|
+
if (!data || typeof data !== "object") {
|
|
75
|
+
return { activeByRepo: {} };
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
activeByRepo: data.activeByRepo || {}
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const saveState = (state) => {
|
|
83
|
+
saveJson(STATE_FILE, state);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const findGitRoot = (startDir) => {
|
|
87
|
+
let current = path.resolve(startDir);
|
|
88
|
+
while (true) {
|
|
89
|
+
if (fs.existsSync(path.join(current, ".git"))) {
|
|
90
|
+
return current;
|
|
91
|
+
}
|
|
92
|
+
const parent = path.dirname(current);
|
|
93
|
+
if (parent === current) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
current = parent;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const getRepoKey = (cwd) => {
|
|
101
|
+
const root = findGitRoot(cwd);
|
|
102
|
+
return root || path.resolve(cwd);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const formatDuration = (ms) => {
|
|
106
|
+
const total = Math.max(0, Math.floor(ms / 1000));
|
|
107
|
+
const hours = Math.floor(total / 3600);
|
|
108
|
+
const minutes = Math.floor((total % 3600) / 60);
|
|
109
|
+
const seconds = total % 60;
|
|
110
|
+
if (hours > 0) {
|
|
111
|
+
return `${hours}h ${minutes.toString().padStart(2, "0")}m`;
|
|
112
|
+
}
|
|
113
|
+
if (minutes > 0) {
|
|
114
|
+
return `${minutes}m ${seconds.toString().padStart(2, "0")}s`;
|
|
115
|
+
}
|
|
116
|
+
return `${seconds}s`;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const formatClock = (ms) => {
|
|
120
|
+
const total = Math.max(0, Math.floor(ms / 1000));
|
|
121
|
+
const hours = Math.floor(total / 3600);
|
|
122
|
+
const minutes = Math.floor((total % 3600) / 60);
|
|
123
|
+
const seconds = total % 60;
|
|
124
|
+
if (hours > 0) {
|
|
125
|
+
return `${hours.toString().padStart(2, "0")}:${minutes
|
|
126
|
+
.toString()
|
|
127
|
+
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
128
|
+
}
|
|
129
|
+
return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const BIG_DIGITS = {
|
|
133
|
+
"0": [" ___ ", "| |", "| |", "| |", "|___|"],
|
|
134
|
+
"1": [" | ", " | ", " | ", " | ", " | "],
|
|
135
|
+
"2": [" ___ ", " |", " ___|", "| ", "|___ "],
|
|
136
|
+
"3": [" ___ ", " |", " ___|", " |", " ___|"],
|
|
137
|
+
"4": ["| |", "| |", "|___|", " |", " |"],
|
|
138
|
+
"5": [" ___ ", "| ", "|___ ", " |", " ___|"],
|
|
139
|
+
"6": [" ___ ", "| ", "|___ ", "| |", "|___|"],
|
|
140
|
+
"7": [" ___ ", " |", " |", " |", " |"],
|
|
141
|
+
"8": [" ___ ", "| |", "|___|", "| |", "|___|"],
|
|
142
|
+
"9": [" ___ ", "| |", "|___|", " |", " ___|"],
|
|
143
|
+
":": [" ", " : ", " ", " : ", " "]
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const renderBigTime = (text) => {
|
|
147
|
+
const lines = ["", "", "", "", ""];
|
|
148
|
+
for (const ch of text) {
|
|
149
|
+
const glyph = BIG_DIGITS[ch] || BIG_DIGITS["0"];
|
|
150
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
151
|
+
lines[i] += `${glyph[i]} `;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return lines.map((line) => line.trimEnd());
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const formatTime = (iso) => {
|
|
158
|
+
try {
|
|
159
|
+
return new Date(iso).toLocaleString();
|
|
160
|
+
} catch {
|
|
161
|
+
return iso;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const findSessionById = (sessions, id) => sessions.find((s) => s.id === id);
|
|
166
|
+
|
|
167
|
+
const getLastSessionForRepo = (sessions, repoKey) => {
|
|
168
|
+
const filtered = sessions.filter((s) => s.repoKey === repoKey);
|
|
169
|
+
if (filtered.length === 0) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
filtered.sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
|
|
173
|
+
return filtered[0];
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const usage = () => {
|
|
177
|
+
console.log(`
|
|
178
|
+
VibeFlow CLI
|
|
179
|
+
|
|
180
|
+
Usage:
|
|
181
|
+
vf start [path] Start a session
|
|
182
|
+
vf intent "text" Set intent for current repo session
|
|
183
|
+
vf park "note" Park a thought
|
|
184
|
+
vf status Show current session status
|
|
185
|
+
vf status --watch Live session timer
|
|
186
|
+
vf timer Live session timer (alias)
|
|
187
|
+
vf resume [path] Show last session summary
|
|
188
|
+
vf history [path] List recent sessions
|
|
189
|
+
vf end End current session
|
|
190
|
+
vf receipt [id] Print receipt (defaults to last session)
|
|
191
|
+
vf help Show this help
|
|
192
|
+
`);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const printSessionSummary = (session, active = false) => {
|
|
196
|
+
const durationMs = session.endedAt
|
|
197
|
+
? Date.parse(session.endedAt) - Date.parse(session.startedAt)
|
|
198
|
+
: Date.now() - Date.parse(session.startedAt);
|
|
199
|
+
const state = active ? "Active" : session.endedAt ? "Ended" : "In progress";
|
|
200
|
+
console.log(`Repo: ${session.repoName || path.basename(session.repoKey)}`);
|
|
201
|
+
console.log(`Session ID: ${session.id}`);
|
|
202
|
+
console.log(`Status: ${state}`);
|
|
203
|
+
console.log(`Started: ${formatTime(session.startedAt)}`);
|
|
204
|
+
if (session.endedAt) {
|
|
205
|
+
console.log(`Ended: ${formatTime(session.endedAt)}`);
|
|
206
|
+
}
|
|
207
|
+
console.log(`Duration: ${formatDuration(durationMs)}`);
|
|
208
|
+
console.log(`Intent: ${session.intent?.text || "Not set"}`);
|
|
209
|
+
console.log(`Parked: ${session.parkedThoughts.length}`);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const ensureActiveSession = (sessions, state, repoKey) => {
|
|
213
|
+
const activeId = state.activeByRepo[repoKey];
|
|
214
|
+
if (!activeId) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const session = findSessionById(sessions, activeId);
|
|
218
|
+
return session || null;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const startSession = (targetPath) => {
|
|
222
|
+
const cwd = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
223
|
+
const repoKey = getRepoKey(cwd);
|
|
224
|
+
const repoName = path.basename(repoKey);
|
|
225
|
+
const sessions = loadSessions();
|
|
226
|
+
const state = loadState();
|
|
227
|
+
|
|
228
|
+
const existing = ensureActiveSession(sessions, state, repoKey);
|
|
229
|
+
if (existing && !existing.endedAt) {
|
|
230
|
+
console.log(c.yellow("Session already active:"));
|
|
231
|
+
printSessionSummary(existing, true);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const session = {
|
|
236
|
+
id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
237
|
+
repoKey,
|
|
238
|
+
repoName,
|
|
239
|
+
cwd,
|
|
240
|
+
startedAt: nowIso(),
|
|
241
|
+
parkedThoughts: []
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
sessions.push(session);
|
|
245
|
+
state.activeByRepo[repoKey] = session.id;
|
|
246
|
+
saveSessions(sessions);
|
|
247
|
+
saveState(state);
|
|
248
|
+
|
|
249
|
+
const banner = [
|
|
250
|
+
c.cyan("__ __ _ ______ _ "),
|
|
251
|
+
c.cyan("\\ \\ / /(_) | | ____| | "),
|
|
252
|
+
c.blue(" \\ \\ / / _| |__ ___ | |__ | | _____ __"),
|
|
253
|
+
c.blue(" \\ \\/ / | | '_ \\ / _ \\| __| | |/ _ \\ \\ /\\ / /"),
|
|
254
|
+
c.magenta(" \\ / | | |_) | __/| | | | (_) \\ V V / "),
|
|
255
|
+
c.magenta(" \\/ |_|_.__/ \\___||_| |_|\\___/ \\_/\\_/ ")
|
|
256
|
+
];
|
|
257
|
+
console.log(banner.join("\n"));
|
|
258
|
+
console.log(c.dim("Terminal IDE - Intent - Context - Flow\n"));
|
|
259
|
+
console.log(c.green("Session started."));
|
|
260
|
+
printSessionSummary(session, true);
|
|
261
|
+
renderLiveTimer(session);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const setIntent = (text) => {
|
|
265
|
+
const value = text.trim();
|
|
266
|
+
if (!value) {
|
|
267
|
+
console.error("Intent text is required.");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const repoKey = getRepoKey(process.cwd());
|
|
271
|
+
const sessions = loadSessions();
|
|
272
|
+
const state = loadState();
|
|
273
|
+
const session = ensureActiveSession(sessions, state, repoKey);
|
|
274
|
+
if (!session) {
|
|
275
|
+
console.error("No active session. Run `vf start` first.");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
session.intent = { text: value, setAt: nowIso() };
|
|
279
|
+
saveSessions(sessions);
|
|
280
|
+
console.log("Intent saved.");
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const parkThought = (text) => {
|
|
284
|
+
const value = text.trim();
|
|
285
|
+
if (!value) {
|
|
286
|
+
console.error("Parked thought text is required.");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const repoKey = getRepoKey(process.cwd());
|
|
290
|
+
const sessions = loadSessions();
|
|
291
|
+
const state = loadState();
|
|
292
|
+
const session = ensureActiveSession(sessions, state, repoKey);
|
|
293
|
+
if (!session) {
|
|
294
|
+
console.error("No active session. Run `vf start` first.");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
session.parkedThoughts.push({ text: value, createdAt: nowIso() });
|
|
298
|
+
saveSessions(sessions);
|
|
299
|
+
console.log("Thought parked.");
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const renderLiveTimer = (session) => {
|
|
303
|
+
console.log(c.cyan("== VibeFlow Timer =="));
|
|
304
|
+
console.log(c.gray("Ctrl+C to stop\n"));
|
|
305
|
+
const startMs = Date.parse(session.startedAt);
|
|
306
|
+
let painted = false;
|
|
307
|
+
const interval = setInterval(() => {
|
|
308
|
+
const elapsed = formatClock(Date.now() - startMs);
|
|
309
|
+
const lines = renderBigTime(elapsed);
|
|
310
|
+
if (painted) {
|
|
311
|
+
process.stdout.write(`\u001b[${lines.length}A`);
|
|
312
|
+
}
|
|
313
|
+
const tinted = lines.map((line, idx) => {
|
|
314
|
+
if (idx < 2) return c.cyan(line);
|
|
315
|
+
if (idx < 4) return c.blue(line);
|
|
316
|
+
return c.magenta(line);
|
|
317
|
+
});
|
|
318
|
+
process.stdout.write(tinted.map((line) => `\u001b[2K${line}`).join("\n") + "\n");
|
|
319
|
+
painted = true;
|
|
320
|
+
}, 1000);
|
|
321
|
+
const cleanup = () => {
|
|
322
|
+
clearInterval(interval);
|
|
323
|
+
process.stdout.write("\n");
|
|
324
|
+
process.exit(0);
|
|
325
|
+
};
|
|
326
|
+
process.on("SIGINT", cleanup);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const status = (watch = false) => {
|
|
330
|
+
const repoKey = getRepoKey(process.cwd());
|
|
331
|
+
const sessions = loadSessions();
|
|
332
|
+
const state = loadState();
|
|
333
|
+
const session = ensureActiveSession(sessions, state, repoKey);
|
|
334
|
+
if (session) {
|
|
335
|
+
if (watch) {
|
|
336
|
+
renderLiveTimer(session);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
printSessionSummary(session, true);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const last = getLastSessionForRepo(sessions, repoKey);
|
|
343
|
+
if (!last) {
|
|
344
|
+
console.log("No sessions found for this repo.");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
console.log("No active session. Last session:");
|
|
348
|
+
printSessionSummary(last, false);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const resume = (targetPath) => {
|
|
352
|
+
const cwd = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
353
|
+
const repoKey = getRepoKey(cwd);
|
|
354
|
+
const sessions = loadSessions();
|
|
355
|
+
const state = loadState();
|
|
356
|
+
const active = ensureActiveSession(sessions, state, repoKey);
|
|
357
|
+
if (active) {
|
|
358
|
+
console.log("Active session:");
|
|
359
|
+
printSessionSummary(active, true);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const last = getLastSessionForRepo(sessions, repoKey);
|
|
363
|
+
if (!last) {
|
|
364
|
+
console.log("No sessions found for this repo.");
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
console.log("Last session:");
|
|
368
|
+
printSessionSummary(last, false);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const history = (targetPath) => {
|
|
372
|
+
const cwd = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
373
|
+
const repoKey = getRepoKey(cwd);
|
|
374
|
+
const sessions = loadSessions().filter((s) => s.repoKey === repoKey);
|
|
375
|
+
if (sessions.length === 0) {
|
|
376
|
+
console.log("No sessions found for this repo.");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
sessions.sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
|
|
380
|
+
console.log(`Recent sessions (${Math.min(5, sessions.length)}):`);
|
|
381
|
+
for (const session of sessions.slice(0, 5)) {
|
|
382
|
+
const durationMs = session.endedAt
|
|
383
|
+
? Date.parse(session.endedAt) - Date.parse(session.startedAt)
|
|
384
|
+
: Date.now() - Date.parse(session.startedAt);
|
|
385
|
+
console.log(
|
|
386
|
+
`- ${session.id} - ${formatTime(session.startedAt)} - ${formatDuration(durationMs)} - ${
|
|
387
|
+
session.intent?.text || "No intent"
|
|
388
|
+
}`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const timer = () => status(true);
|
|
394
|
+
|
|
395
|
+
const end = () => {
|
|
396
|
+
const repoKey = getRepoKey(process.cwd());
|
|
397
|
+
const sessions = loadSessions();
|
|
398
|
+
const state = loadState();
|
|
399
|
+
const session = ensureActiveSession(sessions, state, repoKey);
|
|
400
|
+
if (!session) {
|
|
401
|
+
console.error("No active session to end.");
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
session.endedAt = nowIso();
|
|
405
|
+
saveSessions(sessions);
|
|
406
|
+
delete state.activeByRepo[repoKey];
|
|
407
|
+
saveState(state);
|
|
408
|
+
console.log("Session ended.");
|
|
409
|
+
printSessionSummary(session, false);
|
|
410
|
+
process.exit(0);
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const receipt = (id) => {
|
|
414
|
+
const sessions = loadSessions();
|
|
415
|
+
const repoKey = getRepoKey(process.cwd());
|
|
416
|
+
const targetId = id || getLastSessionForRepo(sessions, repoKey)?.id;
|
|
417
|
+
if (!targetId) {
|
|
418
|
+
console.error("No session found to print a receipt.");
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const session = findSessionById(sessions, targetId);
|
|
422
|
+
if (!session) {
|
|
423
|
+
console.error("Session not found.");
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
console.log("VibeFlow Session Receipt");
|
|
427
|
+
console.log(`Session ID: ${session.id}`);
|
|
428
|
+
console.log(`Repo: ${session.repoName || path.basename(session.repoKey)}`);
|
|
429
|
+
console.log(`Started: ${formatTime(session.startedAt)}`);
|
|
430
|
+
if (session.endedAt) {
|
|
431
|
+
console.log(`Ended: ${formatTime(session.endedAt)}`);
|
|
432
|
+
}
|
|
433
|
+
const durationMs = session.endedAt
|
|
434
|
+
? Date.parse(session.endedAt) - Date.parse(session.startedAt)
|
|
435
|
+
: Date.now() - Date.parse(session.startedAt);
|
|
436
|
+
console.log(`Duration: ${formatDuration(durationMs)}`);
|
|
437
|
+
console.log(`Intent: ${session.intent?.text || "Not set"}`);
|
|
438
|
+
console.log(`Parked: ${session.parkedThoughts.length}`);
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
switch (command) {
|
|
442
|
+
case "start":
|
|
443
|
+
startSession(args[1]);
|
|
444
|
+
break;
|
|
445
|
+
case "intent":
|
|
446
|
+
setIntent(args.slice(1).join(" "));
|
|
447
|
+
break;
|
|
448
|
+
case "park":
|
|
449
|
+
parkThought(args.slice(1).join(" "));
|
|
450
|
+
break;
|
|
451
|
+
case "status":
|
|
452
|
+
status(args.includes("--watch"));
|
|
453
|
+
break;
|
|
454
|
+
case "timer":
|
|
455
|
+
timer();
|
|
456
|
+
break;
|
|
457
|
+
case "resume":
|
|
458
|
+
resume(args[1]);
|
|
459
|
+
break;
|
|
460
|
+
case "history":
|
|
461
|
+
history(args[1]);
|
|
462
|
+
break;
|
|
463
|
+
case "end":
|
|
464
|
+
end();
|
|
465
|
+
break;
|
|
466
|
+
case "receipt":
|
|
467
|
+
receipt(args[1]);
|
|
468
|
+
break;
|
|
469
|
+
case "help":
|
|
470
|
+
case "-h":
|
|
471
|
+
case "--help":
|
|
472
|
+
case undefined:
|
|
473
|
+
usage();
|
|
474
|
+
break;
|
|
475
|
+
default:
|
|
476
|
+
console.error(`Unknown command: ${command}`);
|
|
477
|
+
usage();
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vibeflow-cli",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "VibeFlow CLI — intent, context, and session memory for any repo.",
|
|
5
|
+
"author": "Dev Kuns (https://github.com/atobouh)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"bin": {
|
|
9
|
+
"vf": "index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"index.js",
|
|
13
|
+
"README.md",
|
|
14
|
+
"package.json"
|
|
15
|
+
]
|
|
16
|
+
}
|