@flowmind/cli 0.3.10 → 0.3.11
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/dist/index.js +69 -43
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -294,7 +294,7 @@ var isHttpEvidenceUrl = (value) => {
|
|
|
294
294
|
|
|
295
295
|
// src/agent-session.ts
|
|
296
296
|
import { createHash as createHash4 } from "node:crypto";
|
|
297
|
-
import { appendFile, mkdir as mkdir5, readdir as readdir7, readFile as readFile6, rename as
|
|
297
|
+
import { appendFile, mkdir as mkdir5, readdir as readdir7, readFile as readFile6, rename as rename5, rm as rm4, rmdir, stat as stat3, utimes, writeFile as writeFile5 } from "node:fs/promises";
|
|
298
298
|
import { join as join8 } from "node:path";
|
|
299
299
|
|
|
300
300
|
// src/awake-clock.ts
|
|
@@ -566,7 +566,8 @@ function accountDirectory() {
|
|
|
566
566
|
}
|
|
567
567
|
|
|
568
568
|
// src/project-link.ts
|
|
569
|
-
import {
|
|
569
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
570
|
+
import { chmod, readFile as readFile2, rename, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
570
571
|
import { dirname as dirname2, join as join4, resolve } from "node:path";
|
|
571
572
|
var LINK_FILE = ".flowmind.json";
|
|
572
573
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -625,22 +626,40 @@ async function readLink(start) {
|
|
|
625
626
|
async function writeLink(start, project) {
|
|
626
627
|
const root = await findRepoRoot(start);
|
|
627
628
|
const path = join4(root, LINK_FILE);
|
|
628
|
-
await
|
|
629
|
-
`, "utf8");
|
|
629
|
+
await replaceLinkFile(path, { projectId: project.id, name: project.name });
|
|
630
630
|
return { kind: "linked", root, path, projectId: project.id, name: project.name };
|
|
631
631
|
}
|
|
632
632
|
async function writeIgnore(start, reason) {
|
|
633
633
|
const root = await findRepoRoot(start);
|
|
634
634
|
const path = join4(root, LINK_FILE);
|
|
635
635
|
const trimmed = reason?.trim() || null;
|
|
636
|
-
await
|
|
637
|
-
`, "utf8");
|
|
636
|
+
await replaceLinkFile(path, { ignore: true, ...trimmed ? { reason: trimmed } : {} });
|
|
638
637
|
return { kind: "ignored", root, path, reason: trimmed };
|
|
639
638
|
}
|
|
639
|
+
async function replaceLinkFile(path, content) {
|
|
640
|
+
const temporary = `${path}.${process.pid}.${randomUUID2()}.tmp`;
|
|
641
|
+
const mode = await permissionBits(path);
|
|
642
|
+
try {
|
|
643
|
+
await writeFile2(temporary, `${JSON.stringify(content, null, 2)}
|
|
644
|
+
`, { encoding: "utf8", flag: "wx" });
|
|
645
|
+
if (mode !== null) await chmod(temporary, mode);
|
|
646
|
+
await rename(temporary, path);
|
|
647
|
+
} finally {
|
|
648
|
+
await rm(temporary, { force: true });
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
async function permissionBits(path) {
|
|
652
|
+
try {
|
|
653
|
+
return (await stat(path)).mode & 4095;
|
|
654
|
+
} catch (error) {
|
|
655
|
+
if (error.code === "ENOENT") return null;
|
|
656
|
+
throw error;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
640
659
|
|
|
641
660
|
// src/manual-save.ts
|
|
642
|
-
import { createHash as createHash3, randomUUID as
|
|
643
|
-
import { mkdir as mkdir4, readdir as readdir6, readFile as readFile5, rename as
|
|
661
|
+
import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
|
|
662
|
+
import { mkdir as mkdir4, readdir as readdir6, readFile as readFile5, rename as rename4, unlink as unlink2, writeFile as writeFile4 } from "node:fs/promises";
|
|
644
663
|
import { join as join7 } from "node:path";
|
|
645
664
|
|
|
646
665
|
// src/account.ts
|
|
@@ -975,13 +994,13 @@ async function apiRequest(endpoint, args, options = {}) {
|
|
|
975
994
|
}
|
|
976
995
|
|
|
977
996
|
// src/legacy-proofs.ts
|
|
978
|
-
import { mkdir as mkdir3, readdir as readdir4, readFile as readFile4, rename as
|
|
997
|
+
import { mkdir as mkdir3, readdir as readdir4, readFile as readFile4, rename as rename3, rm as rm3, stat as stat2 } from "node:fs/promises";
|
|
979
998
|
import { join as join6 } from "node:path";
|
|
980
999
|
|
|
981
1000
|
// src/pending-stages.ts
|
|
982
1001
|
import { spawn } from "node:child_process";
|
|
983
|
-
import { createHash as createHash2, randomUUID as
|
|
984
|
-
import { mkdir as mkdir2, readdir as readdir3, readFile as readFile3, rename, rm, writeFile as writeFile3 } from "node:fs/promises";
|
|
1002
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
|
|
1003
|
+
import { mkdir as mkdir2, readdir as readdir3, readFile as readFile3, rename as rename2, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
|
|
985
1004
|
import { basename, join as join5 } from "node:path";
|
|
986
1005
|
|
|
987
1006
|
// src/stored-stage-input.ts
|
|
@@ -1065,9 +1084,9 @@ function readStage(value, fileName, legacy = false) {
|
|
|
1065
1084
|
}
|
|
1066
1085
|
var writePendingStage = async (stage) => {
|
|
1067
1086
|
await mkdir2(outboxDir(), { recursive: true });
|
|
1068
|
-
const temporary = fileOf(stage.id) + "." + process.pid + "." +
|
|
1087
|
+
const temporary = fileOf(stage.id) + "." + process.pid + "." + randomUUID3() + ".tmp";
|
|
1069
1088
|
await writeFile3(temporary, JSON.stringify(stage, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
1070
|
-
await
|
|
1089
|
+
await rename2(temporary, fileOf(stage.id));
|
|
1071
1090
|
};
|
|
1072
1091
|
async function saveStageBeforeSending(input2) {
|
|
1073
1092
|
const at2 = input2.now.toISOString();
|
|
@@ -1111,7 +1130,7 @@ async function recordStageFailure(stage, failure, now) {
|
|
|
1111
1130
|
return next;
|
|
1112
1131
|
}
|
|
1113
1132
|
async function forgetStage(id) {
|
|
1114
|
-
await
|
|
1133
|
+
await rm2(fileOf(id), { force: true });
|
|
1115
1134
|
}
|
|
1116
1135
|
async function listPendingStages() {
|
|
1117
1136
|
let names;
|
|
@@ -1309,7 +1328,7 @@ async function moveIntoAccount(stage) {
|
|
|
1309
1328
|
}
|
|
1310
1329
|
);
|
|
1311
1330
|
if (present) return false;
|
|
1312
|
-
await
|
|
1331
|
+
await rename3(join6(legacyOutboxDir(), stage.id + ".json"), target);
|
|
1313
1332
|
await writePendingStage({ ...stage, credentialFingerprint: accountBinding().credential });
|
|
1314
1333
|
return true;
|
|
1315
1334
|
}
|
|
@@ -1342,7 +1361,7 @@ async function adoptUnownedProof(id) {
|
|
|
1342
1361
|
}
|
|
1343
1362
|
async function discardUnownedProof(id) {
|
|
1344
1363
|
await findUnowned(id);
|
|
1345
|
-
await
|
|
1364
|
+
await rm3(join6(legacyOutboxDir(), id + ".json"));
|
|
1346
1365
|
}
|
|
1347
1366
|
|
|
1348
1367
|
// src/account.ts
|
|
@@ -1450,10 +1469,10 @@ async function manualSave(identity, prepare, send) {
|
|
|
1450
1469
|
if (error.code !== "ENOENT") throw error;
|
|
1451
1470
|
const body = await prepare();
|
|
1452
1471
|
if (body === null) return null;
|
|
1453
|
-
saved = { id, body, key:
|
|
1454
|
-
const temp = fileOf2(id) + "." +
|
|
1472
|
+
saved = { id, body, key: randomUUID4(), savedAt: Date.now() };
|
|
1473
|
+
const temp = fileOf2(id) + "." + randomUUID4() + ".tmp";
|
|
1455
1474
|
await writeFile4(temp, JSON.stringify(saved), { mode: 384, flag: "wx" });
|
|
1456
|
-
await
|
|
1475
|
+
await rename4(temp, fileOf2(id));
|
|
1457
1476
|
}
|
|
1458
1477
|
if (Date.now() - saved.savedAt >= RECEIPT_MS) {
|
|
1459
1478
|
throw new Error(
|
|
@@ -4232,7 +4251,7 @@ async function adoptLegacyJournals(fromDir, extendStart, now = Date.now()) {
|
|
|
4232
4251
|
try {
|
|
4233
4252
|
events = text2.split("\n").filter((line) => line.trim() !== "").map((line) => currentFormOf(line, journal.from));
|
|
4234
4253
|
} catch {
|
|
4235
|
-
await
|
|
4254
|
+
await rename5(journal.from, journal.from + ".unreadable");
|
|
4236
4255
|
continue;
|
|
4237
4256
|
}
|
|
4238
4257
|
for (const event of events) earliest = Math.min(earliest, Date.parse(event.at));
|
|
@@ -4244,17 +4263,17 @@ async function adoptLegacyJournals(fromDir, extendStart, now = Date.now()) {
|
|
|
4244
4263
|
const target = join8(sessionsDir(), journal.name + ADOPTED);
|
|
4245
4264
|
await writeFile5(target + ".partial", copy, { mode: 384 });
|
|
4246
4265
|
await utimes(target + ".partial", journal.modified, journal.modified);
|
|
4247
|
-
await
|
|
4266
|
+
await rename5(target + ".partial", target);
|
|
4248
4267
|
sources[journal.name] = { size: journal.size, mtimeMs: journal.mtimeMs };
|
|
4249
4268
|
}
|
|
4250
4269
|
for (const journal of await legacyJournals(fromDir, now)) {
|
|
4251
4270
|
if (!journal.expired || !unchanged(journal, sources)) continue;
|
|
4252
|
-
await
|
|
4271
|
+
await rm4(journal.from, { force: true });
|
|
4253
4272
|
delete sources[journal.name];
|
|
4254
4273
|
}
|
|
4255
4274
|
const manifest = join8(sessionsDir(), SOURCES);
|
|
4256
4275
|
await writeFile5(manifest + ".partial", JSON.stringify(sources), { mode: 384 });
|
|
4257
|
-
await
|
|
4276
|
+
await rename5(manifest + ".partial", manifest);
|
|
4258
4277
|
try {
|
|
4259
4278
|
await rmdir(fromDir);
|
|
4260
4279
|
} catch (error) {
|
|
@@ -4368,9 +4387,9 @@ async function pruneOldSessions(now) {
|
|
|
4368
4387
|
if (!retiring && now.getTime() - await lastWritten(path) <= RETENTION_MS) continue;
|
|
4369
4388
|
if (!retiring && await lastWritten(join8(legacySessionsDir(), name)) !== -Infinity) continue;
|
|
4370
4389
|
if (!retiring) await writeFile5(path + RETIRED, "", { mode: 384 });
|
|
4371
|
-
await
|
|
4372
|
-
await
|
|
4373
|
-
await
|
|
4390
|
+
await rm4(path, { force: true });
|
|
4391
|
+
await rm4(path + ADOPTED, { force: true });
|
|
4392
|
+
await rm4(path + RETIRED, { force: true });
|
|
4374
4393
|
}
|
|
4375
4394
|
}
|
|
4376
4395
|
async function handleHook(agent, raw, prune, now = currentMoment()) {
|
|
@@ -4525,7 +4544,7 @@ function ownedParts(start, end, cut, periods) {
|
|
|
4525
4544
|
return part.ms > 0 ? [part] : [];
|
|
4526
4545
|
});
|
|
4527
4546
|
}
|
|
4528
|
-
function measureRecordedTime(events, now, periods) {
|
|
4547
|
+
function measureRecordedTime(events, now, periods, observedTurnId) {
|
|
4529
4548
|
const first = events.find((event) => event.event === "session" || event.event === "start");
|
|
4530
4549
|
if (!first || first.event !== "session" && first.event !== "start") return null;
|
|
4531
4550
|
const nowPoint = { wall: now.at.getTime(), up: now.up };
|
|
@@ -4572,7 +4591,7 @@ function measureRecordedTime(events, now, periods) {
|
|
|
4572
4591
|
break;
|
|
4573
4592
|
}
|
|
4574
4593
|
}
|
|
4575
|
-
if (open2) turns.push({ start: open2.start, end: open2.last });
|
|
4594
|
+
if (open2) turns.push({ start: open2.start, end: observedTurnId !== void 0 && observedTurnId === open2.turnId ? nowPoint : open2.last });
|
|
4576
4595
|
const stagedCut = lastStage ? cutOf(lastStage) : null;
|
|
4577
4596
|
const counted = turns.flatMap(
|
|
4578
4597
|
({ start, end }) => ownedParts(start, end.wall > nowPoint.wall ? nowPoint : end, stagedCut, periods)
|
|
@@ -4653,17 +4672,23 @@ function missedTurnEvents(events, turns, owner, now, lastWake) {
|
|
|
4653
4672
|
}
|
|
4654
4673
|
return added;
|
|
4655
4674
|
}
|
|
4656
|
-
async function stageTime(sessionId, now) {
|
|
4675
|
+
async function stageTime(sessionId, now, observation) {
|
|
4657
4676
|
const events = await readEventsAt(sessionFile(sessionId));
|
|
4658
4677
|
if (!events) return null;
|
|
4659
4678
|
const owner = events.find(
|
|
4660
4679
|
(event) => event.event === "session" || event.event === "start"
|
|
4661
4680
|
);
|
|
4662
|
-
if (owner?.agent !== "codex")
|
|
4681
|
+
if (owner?.agent !== "codex") {
|
|
4682
|
+
const latestStart = events.filter((event) => event.event === "start").at(-1);
|
|
4683
|
+
const observedTurn2 = observation === "active-turn" && latestStart?.event === "start" ? latestStart.turnId : void 0;
|
|
4684
|
+
return measureRecordedTime(events, now, signedInPeriods(), observedTurn2);
|
|
4685
|
+
}
|
|
4663
4686
|
const file = await codexTurnsOf(sessionId, transcriptPathOf(events));
|
|
4664
4687
|
if (file === null) return measureRecordedTime(events, now, signedInPeriods());
|
|
4665
4688
|
const added = missedTurnEvents(events, file.turns, owner, now, lastWakeMs());
|
|
4666
|
-
|
|
4689
|
+
const latestTurn = file.turns.at(-1);
|
|
4690
|
+
const observedTurn = observation === "codex-env" && latestTurn?.endedAt === null ? latestTurn.turnId : void 0;
|
|
4691
|
+
return measureRecordedTime(byTime([...events, ...added]), now, signedInPeriods(), observedTurn);
|
|
4667
4692
|
}
|
|
4668
4693
|
async function promptReminder(sessionId, cwd, now) {
|
|
4669
4694
|
const link2 = await readLink(cwd);
|
|
@@ -4969,7 +4994,8 @@ async function resolveStageOptions(options, cwd, now) {
|
|
|
4969
4994
|
let { minutes, startedAt } = options;
|
|
4970
4995
|
let recorded = null;
|
|
4971
4996
|
if (minutes === void 0 && startedAt === void 0) {
|
|
4972
|
-
|
|
4997
|
+
const observation = session.source === "codex-env" || session.source === "flag" && session.sessionId === process.env.CODEX_THREAD_ID ? "codex-env" : session.source === "active-turn" ? "active-turn" : void 0;
|
|
4998
|
+
recorded = session.sessionId ? await stageTime(session.sessionId, now, observation) : null;
|
|
4973
4999
|
if (!recorded) {
|
|
4974
5000
|
throw new Error(
|
|
4975
5001
|
"No recorded agent time for this session. If the session hooks are not installed, run `flowmind hooks install`; for this session, pass --minutes and --started-at."
|
|
@@ -5683,9 +5709,9 @@ var logCommand = new Command3("log").description("Log time with natural language
|
|
|
5683
5709
|
});
|
|
5684
5710
|
|
|
5685
5711
|
// src/commands/hooks.ts
|
|
5686
|
-
import { createHash as createHash6, randomUUID as
|
|
5712
|
+
import { createHash as createHash6, randomUUID as randomUUID5 } from "node:crypto";
|
|
5687
5713
|
import { realpathSync } from "node:fs";
|
|
5688
|
-
import { readFile as readFile7, link, lstat, mkdir as mkdir6, rename as
|
|
5714
|
+
import { readFile as readFile7, link, lstat, mkdir as mkdir6, rename as rename6, rm as rm5, writeFile as writeFile6 } from "node:fs/promises";
|
|
5689
5715
|
import { homedir as homedir2 } from "node:os";
|
|
5690
5716
|
import { dirname as dirname3, join as join9 } from "node:path";
|
|
5691
5717
|
import { fileURLToPath } from "node:url";
|
|
@@ -5887,7 +5913,7 @@ async function updateHookFile(agent, action, base) {
|
|
|
5887
5913
|
if (info && !info.isFile()) throw new Error("Unsupported hook settings file: " + path);
|
|
5888
5914
|
const changed = () => new Error(path + " changed while Flowmind was updating it. Nothing was written; run the command again.");
|
|
5889
5915
|
const removeFile = agent === "codex" && action === "uninstall" && Object.keys(next).length === 0;
|
|
5890
|
-
const temporary = path + "." +
|
|
5916
|
+
const temporary = path + "." + randomUUID5() + ".tmp";
|
|
5891
5917
|
if (!removeFile) await writeFile6(temporary, serialize(next), { mode: info ? info.mode & 511 : 384, flag: "wx" });
|
|
5892
5918
|
try {
|
|
5893
5919
|
let onDisk;
|
|
@@ -5898,7 +5924,7 @@ async function updateHookFile(agent, action, base) {
|
|
|
5898
5924
|
onDisk = null;
|
|
5899
5925
|
}
|
|
5900
5926
|
if (onDisk !== text2) throw changed();
|
|
5901
|
-
if (removeFile) await
|
|
5927
|
+
if (removeFile) await rm5(path);
|
|
5902
5928
|
else if (text2 === null) {
|
|
5903
5929
|
try {
|
|
5904
5930
|
await link(temporary, path);
|
|
@@ -5906,9 +5932,9 @@ async function updateHookFile(agent, action, base) {
|
|
|
5906
5932
|
if (error.code === "EEXIST") throw changed();
|
|
5907
5933
|
throw error;
|
|
5908
5934
|
}
|
|
5909
|
-
} else await
|
|
5935
|
+
} else await rename6(temporary, path);
|
|
5910
5936
|
} finally {
|
|
5911
|
-
await
|
|
5937
|
+
await rm5(temporary, { force: true });
|
|
5912
5938
|
}
|
|
5913
5939
|
}
|
|
5914
5940
|
async function bundledSkill() {
|
|
@@ -5962,8 +5988,8 @@ hooksCommand.command("install").description(
|
|
|
5962
5988
|
if (options.dryRun) {
|
|
5963
5989
|
console.log(terminalLine`Would remove ${copy.path} and ${copy.markerPath}: ${reason}`);
|
|
5964
5990
|
} else {
|
|
5965
|
-
await
|
|
5966
|
-
await
|
|
5991
|
+
await rm5(copy.path);
|
|
5992
|
+
await rm5(copy.markerPath);
|
|
5967
5993
|
console.log(terminalLine`Removed ${copy.path} and ${copy.markerPath}: ${reason}`);
|
|
5968
5994
|
}
|
|
5969
5995
|
} else if (copy.state === "absent") {
|
|
@@ -6034,9 +6060,9 @@ hooksCommand.command("uninstall").description("Remove unmodified Flowmind-manage
|
|
|
6034
6060
|
console.log(terminalLine`Left ${target.state} ${target.path}`);
|
|
6035
6061
|
continue;
|
|
6036
6062
|
}
|
|
6037
|
-
await
|
|
6063
|
+
await rm5(target.path);
|
|
6038
6064
|
console.log(terminalLine`Removed ${target.path}`);
|
|
6039
|
-
await
|
|
6065
|
+
await rm5(target.markerPath);
|
|
6040
6066
|
console.log(terminalLine`Removed ${target.markerPath}`);
|
|
6041
6067
|
}
|
|
6042
6068
|
for (const agent of agents) {
|
|
@@ -8017,7 +8043,7 @@ for (const [name, description, done] of [
|
|
|
8017
8043
|
|
|
8018
8044
|
// src/index.ts
|
|
8019
8045
|
var program = new Command12();
|
|
8020
|
-
program.name("flowmind").description("Flowmind CLI (also installed as fm)").version("0.3.
|
|
8046
|
+
program.name("flowmind").description("Flowmind CLI (also installed as fm)").version("0.3.11").enablePositionalOptions();
|
|
8021
8047
|
program.addCommand(agentCommand);
|
|
8022
8048
|
program.addCommand(authCommand);
|
|
8023
8049
|
program.addCommand(logCommand);
|
package/package.json
CHANGED