@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 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 `flowmind agent hook`, which records each agent turn under the CLI config folder.
17
- In a linked repository, the session-start hook tells the agent which project it works on.
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
 
@@ -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 (recorded && sessionId) await markStaged(sessionId, recorded.cutoff, proposal.id);
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 HOOK_COMMAND = "flowmind agent hook";
3481
- var handler = (agent, timeout, background = false) => ({
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: `${HOOK_COMMAND} --agent ${agent}`,
3484
- timeout,
3485
- ...background ? { async: true } : {}
3512
+ command: hookCommand(agent),
3513
+ timeout
3486
3514
  });
3487
- var hookEvents = {
3488
- codex: {
3489
- SessionStart: { hooks: [handler("codex", 10)] },
3490
- UserPromptSubmit: { hooks: [handler("codex", 10, true)] },
3491
- Stop: { hooks: [handler("codex", 10)] },
3492
- Interrupt: { hooks: [handler("codex", 3)] }
3493
- },
3494
- claude: {
3495
- SessionStart: { hooks: [handler("claude", 10)] },
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.startsWith(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[agent]).map(([event, group]) => `${event}:${JSON.stringify(group)}`);
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[agent])) {
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.0").enablePositionalOptions();
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.0",
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": ["dist/index.js", "dist/flowmind/SKILL.md"],
17
+ "files": [
18
+ "dist/index.js",
19
+ "dist/flowmind/SKILL.md"
20
+ ],
18
21
  "bin": {
19
- "fm": "./dist/index.js",
20
- "flowmind": "./dist/index.js"
22
+ "fm": "dist/index.js",
23
+ "flowmind": "dist/index.js"
21
24
  },
22
25
  "type": "module",
23
26
  "scripts": {