@flowmind/cli 0.2.0 → 0.2.2
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 +8 -2
- package/dist/flowmind/SKILL.md +2 -0
- package/dist/index.js +48 -23
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -13,8 +13,14 @@ flowmind hooks install
|
|
|
13
13
|
|
|
14
14
|
`hooks install` copies the Flowmind skill to Codex and Claude Code and adds session hooks to
|
|
15
15
|
`~/.codex/hooks.json` and `~/.claude/settings.json`. Other hooks in those files stay as they are.
|
|
16
|
-
The hooks run `
|
|
17
|
-
|
|
16
|
+
The hooks run `agent hook` through the absolute paths of the installing Node binary and CLI file,
|
|
17
|
+
because hooks start without your shell profile and a version manager may not put `node` on PATH.
|
|
18
|
+
Codex on Windows gets a PowerShell command (`& '<node>' '<cli>' agent hook --agent codex`).
|
|
19
|
+
After you upgrade Node or move the CLI, `hooks status` shows `outdated`; run `hooks install` again
|
|
20
|
+
and trust the changed hooks. The hook records each agent turn under the CLI config folder.
|
|
21
|
+
In a linked repository, the session-start hook tells the agent which project it works on. Once 20 or more minutes of
|
|
22
|
+
finished agent turns are unstaged, each prompt also carries a reminder with the minutes and the
|
|
23
|
+
staging command. A successful `agent stage` resets the count.
|
|
18
24
|
Codex runs new hooks only after you trust them once in `/hooks`. `hooks status` shows both installs,
|
|
19
25
|
and `hooks uninstall` removes only the Flowmind entries.
|
|
20
26
|
|
package/dist/flowmind/SKILL.md
CHANGED
|
@@ -36,6 +36,7 @@ flowmind agent stage --intention "<what you set out to do>" --outcome "<what cha
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
- The CLI reads the project from `.flowmind.json` and the time from the session record. A second stage in the same session covers only the time after the first.
|
|
39
|
+
- After 20 minutes of unstaged agent time, each prompt in a linked repository carries a reminder with the unstaged minutes. Stage when the current unit of work is complete; a stage with explicit times also resets the count.
|
|
39
40
|
- Add `--dry-run` to preview the project and minutes without staging.
|
|
40
41
|
- Pass `--project`, `--minutes`, and `--started-at` only when no session record exists. Use observed times only. Never estimate time from tokens or tool calls.
|
|
41
42
|
- Write the intention and the outcome as one short sentence each. Never paste transcripts, terminal output, commit messages, issue titles, emails, or secrets.
|
|
@@ -63,6 +64,7 @@ Ask only for missing fields. A natural-language entry (`flowmind log "2h on the
|
|
|
63
64
|
|
|
64
65
|
- `flowmind auth status` shows the API URL. A normal install uses `https://api.flowmind.pro`.
|
|
65
66
|
- `flowmind hooks status` shows whether the skill and hooks are installed for Codex and Claude Code.
|
|
67
|
+
- `outdated` hooks: run `flowmind hooks install` again (for example after a Node upgrade) and trust the changed hooks.
|
|
66
68
|
- "No recorded agent time": the hooks are missing or not trusted, or the session started before installation. Stage with observed `--minutes` and `--started-at`, or stage in the next session.
|
|
67
69
|
- "Several agent sessions have a turn in progress": pass `--session-id`.
|
|
68
70
|
- "No project given": run `flowmind link <project>` in the repository, or pass `--project`.
|
package/dist/index.js
CHANGED
|
@@ -2609,6 +2609,7 @@ ${list}` : " No active projects exist.")
|
|
|
2609
2609
|
// src/agent-session.ts
|
|
2610
2610
|
var RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
2611
2611
|
var ACTIVE_WINDOW_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
2612
|
+
var REMINDER_MINUTES = 20;
|
|
2612
2613
|
var OPEN_TURN_LIMIT_MS = 12 * 60 * 60 * 1e3;
|
|
2613
2614
|
var EVENTS = /* @__PURE__ */ new Set(["session", "start", "stop", "staged"]);
|
|
2614
2615
|
var sessionsDir = () => join2(dirname2(config.path), "agent-sessions");
|
|
@@ -2665,6 +2666,14 @@ function sessionContext(projectName) {
|
|
|
2665
2666
|
"Do not use `flowmind log` for agent work."
|
|
2666
2667
|
].join(" ");
|
|
2667
2668
|
}
|
|
2669
|
+
function unstagedReminder(projectName, minutes) {
|
|
2670
|
+
return [
|
|
2671
|
+
`Flowmind: ${minutes} minutes of agent work in this session are not staged yet for "${plainText(projectName)}".`,
|
|
2672
|
+
"When the current unit of work is complete, stage it with",
|
|
2673
|
+
'`flowmind agent stage --intention "<goal>" --outcome "<what changed>" --json`',
|
|
2674
|
+
"and share the review URL."
|
|
2675
|
+
].join(" ");
|
|
2676
|
+
}
|
|
2668
2677
|
async function pruneOldSessions(now) {
|
|
2669
2678
|
let names;
|
|
2670
2679
|
try {
|
|
@@ -2697,7 +2706,7 @@ async function handleHook(agent, raw, now = /* @__PURE__ */ new Date()) {
|
|
|
2697
2706
|
cwd: input2.cwd,
|
|
2698
2707
|
turnId: input2.turnId
|
|
2699
2708
|
});
|
|
2700
|
-
return
|
|
2709
|
+
return promptReminder(input2.sessionId, input2.cwd, now);
|
|
2701
2710
|
case "Stop":
|
|
2702
2711
|
case "Interrupt":
|
|
2703
2712
|
await append(input2.sessionId, { event: "stop", at, turnId: input2.turnId });
|
|
@@ -2736,6 +2745,12 @@ async function recordedTime(sessionId, now = /* @__PURE__ */ new Date()) {
|
|
|
2736
2745
|
const events = await readEventsAt(sessionFile(sessionId));
|
|
2737
2746
|
return events ? measureRecordedTime(events, now) : null;
|
|
2738
2747
|
}
|
|
2748
|
+
async function promptReminder(sessionId, cwd, now) {
|
|
2749
|
+
const link = await readLink(cwd);
|
|
2750
|
+
if (!link) return "";
|
|
2751
|
+
const recorded = await recordedTime(sessionId, now);
|
|
2752
|
+
return recorded && recorded.minutes >= REMINDER_MINUTES ? unstagedReminder(link.name, recorded.minutes) : "";
|
|
2753
|
+
}
|
|
2739
2754
|
async function markStaged(sessionId, cutoff, proposalId) {
|
|
2740
2755
|
await append(sessionId, { event: "staged", at: cutoff, proposalId });
|
|
2741
2756
|
}
|
|
@@ -3069,7 +3084,7 @@ agentCommand.command("stage").description("Stage completed agent work as a proje
|
|
|
3069
3084
|
}
|
|
3070
3085
|
spinner = options.json ? null : ora("Staging agent proof...").start();
|
|
3071
3086
|
const proposal = await createAgentLogProposal(input2, idempotencyKey);
|
|
3072
|
-
if (
|
|
3087
|
+
if (sessionId) await markStaged(sessionId, recorded?.cutoff ?? now.toISOString(), proposal.id);
|
|
3073
3088
|
spinner?.succeed("Agent proof staged");
|
|
3074
3089
|
if (options.json) {
|
|
3075
3090
|
console.log(JSON.stringify({ idempotencyKey, proposal }));
|
|
@@ -3440,9 +3455,11 @@ var logCommand = new Command3("log").description("Log time with natural language
|
|
|
3440
3455
|
|
|
3441
3456
|
// src/commands/hooks.ts
|
|
3442
3457
|
import { createHash as createHash3 } from "node:crypto";
|
|
3458
|
+
import { realpathSync } from "node:fs";
|
|
3443
3459
|
import { readFile as readFile3, lstat, mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
3444
3460
|
import { homedir } from "node:os";
|
|
3445
3461
|
import { dirname as dirname3, join as join3 } from "node:path";
|
|
3462
|
+
import { fileURLToPath } from "node:url";
|
|
3446
3463
|
import { Command as Command4, InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
3447
3464
|
var agents = ["codex", "claude"];
|
|
3448
3465
|
var installedHashFile = ".flowmind-installed.sha256";
|
|
@@ -3477,31 +3494,39 @@ async function inspect(agent) {
|
|
|
3477
3494
|
}
|
|
3478
3495
|
return { state: savedHash === contentHash ? "installed" : "modified", path, markerPath, contentHash };
|
|
3479
3496
|
}
|
|
3480
|
-
var
|
|
3481
|
-
var
|
|
3497
|
+
var FLOWMIND_HOOK = /(?:^flowmind|index\.js') agent hook --agent (?:codex|claude)$/;
|
|
3498
|
+
var portablePath = (path) => realpathSync(path).replaceAll("\\", "/");
|
|
3499
|
+
function hookCommand(agent) {
|
|
3500
|
+
const node = portablePath(process.execPath);
|
|
3501
|
+
const cli = portablePath(fileURLToPath(import.meta.url));
|
|
3502
|
+
const args = `agent hook --agent ${agent}`;
|
|
3503
|
+
if (process.platform === "win32" && agent === "codex") {
|
|
3504
|
+
const quote2 = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
3505
|
+
return `& ${quote2(node)} ${quote2(cli)} ${args}`;
|
|
3506
|
+
}
|
|
3507
|
+
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
3508
|
+
return `${quote(node)} ${quote(cli)} ${args}`;
|
|
3509
|
+
}
|
|
3510
|
+
var handler = (agent, timeout) => ({
|
|
3482
3511
|
type: "command",
|
|
3483
|
-
command:
|
|
3484
|
-
timeout
|
|
3485
|
-
...background ? { async: true } : {}
|
|
3512
|
+
command: hookCommand(agent),
|
|
3513
|
+
timeout
|
|
3486
3514
|
});
|
|
3487
|
-
var hookEvents = {
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
},
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
UserPromptSubmit: { hooks: [handler("claude", 10)] },
|
|
3497
|
-
Stop: { hooks: [handler("claude", 10)] }
|
|
3498
|
-
}
|
|
3515
|
+
var hookEvents = (agent) => agent === "codex" ? {
|
|
3516
|
+
SessionStart: { hooks: [handler("codex", 10)] },
|
|
3517
|
+
UserPromptSubmit: { hooks: [handler("codex", 10)] },
|
|
3518
|
+
Stop: { hooks: [handler("codex", 10)] },
|
|
3519
|
+
Interrupt: { hooks: [handler("codex", 3)] }
|
|
3520
|
+
} : {
|
|
3521
|
+
SessionStart: { hooks: [handler("claude", 10)] },
|
|
3522
|
+
UserPromptSubmit: { hooks: [handler("claude", 10)] },
|
|
3523
|
+
Stop: { hooks: [handler("claude", 10)] }
|
|
3499
3524
|
};
|
|
3500
3525
|
function hookConfigPath(agent) {
|
|
3501
3526
|
return agent === "codex" ? join3(homedir(), ".codex", "hooks.json") : join3(homedir(), ".claude", "settings.json");
|
|
3502
3527
|
}
|
|
3503
3528
|
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3504
|
-
var isFlowmindGroup = (group) => isObject(group) && Array.isArray(group.hooks) && group.hooks.length > 0 && group.hooks.every((hook) => isObject(hook) && typeof hook.command === "string" && hook.command
|
|
3529
|
+
var isFlowmindGroup = (group) => isObject(group) && Array.isArray(group.hooks) && group.hooks.length > 0 && group.hooks.every((hook) => isObject(hook) && typeof hook.command === "string" && FLOWMIND_HOOK.test(hook.command));
|
|
3505
3530
|
async function readHookConfig(agent) {
|
|
3506
3531
|
const path = hookConfigPath(agent);
|
|
3507
3532
|
let text;
|
|
@@ -3532,7 +3557,7 @@ function hookState(config2, agent) {
|
|
|
3532
3557
|
([event, groups]) => groups.filter(isFlowmindGroup).map((group) => `${event}:${JSON.stringify(group)}`)
|
|
3533
3558
|
);
|
|
3534
3559
|
if (ours.length === 0) return "absent";
|
|
3535
|
-
const expected = Object.entries(hookEvents
|
|
3560
|
+
const expected = Object.entries(hookEvents(agent)).map(([event, group]) => `${event}:${JSON.stringify(group)}`);
|
|
3536
3561
|
const exact = ours.length === expected.length && expected.every((entry) => ours.includes(entry));
|
|
3537
3562
|
return exact ? "installed" : "outdated";
|
|
3538
3563
|
}
|
|
@@ -3551,7 +3576,7 @@ function withoutFlowmindHooks(config2) {
|
|
|
3551
3576
|
function withFlowmindHooks(config2, agent) {
|
|
3552
3577
|
const next = withoutFlowmindHooks(config2);
|
|
3553
3578
|
const hooks = isObject(next.hooks) ? { ...next.hooks } : {};
|
|
3554
|
-
for (const [event, group] of Object.entries(hookEvents
|
|
3579
|
+
for (const [event, group] of Object.entries(hookEvents(agent))) {
|
|
3555
3580
|
hooks[event] = [...hooks[event] ?? [], group];
|
|
3556
3581
|
}
|
|
3557
3582
|
return { ...next, hooks };
|
|
@@ -5258,7 +5283,7 @@ var shipLogCommand = new Command10("ship-log").description("Read a project ship
|
|
|
5258
5283
|
|
|
5259
5284
|
// src/index.ts
|
|
5260
5285
|
var program = new Command11();
|
|
5261
|
-
program.name("flowmind").description("Flowmind CLI (also installed as fm)").version("0.2.
|
|
5286
|
+
program.name("flowmind").description("Flowmind CLI (also installed as fm)").version("0.2.2").enablePositionalOptions();
|
|
5262
5287
|
program.addCommand(agentCommand);
|
|
5263
5288
|
program.addCommand(authCommand);
|
|
5264
5289
|
program.addCommand(logCommand);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowmind/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "CLI for Flowmind — proof-of-effort hour ledger",
|
|
5
5
|
"homepage": "https://www.flowmind.pro",
|
|
6
6
|
"repository": {
|
|
@@ -14,10 +14,13 @@
|
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
|
-
"files": [
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/index.js",
|
|
19
|
+
"dist/flowmind/SKILL.md"
|
|
20
|
+
],
|
|
18
21
|
"bin": {
|
|
19
|
-
"fm": "
|
|
20
|
-
"flowmind": "
|
|
22
|
+
"fm": "dist/index.js",
|
|
23
|
+
"flowmind": "dist/index.js"
|
|
21
24
|
},
|
|
22
25
|
"type": "module",
|
|
23
26
|
"scripts": {
|