@flowmind/cli 0.3.1 → 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abdullah Faruk Gonullu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -13,6 +13,9 @@ 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
+ When the Flowmind Claude Code plugin (`flowmind@flowmind` from `afgonullu/flowmind-skill`) is
17
+ installed and enabled, the plugin provides the skill in Claude Code: `hooks install` writes no
18
+ copy to `~/.claude/skills`, removes an unmodified copy it wrote earlier, and still adds the hooks.
16
19
  The hooks run `agent hook` through the absolute paths of the installing Node binary and CLI file,
17
20
  because hooks start without your shell profile and a version manager may not put `node` on PATH.
18
21
  Codex on Windows gets a PowerShell command (`& '<node>' '<cli>' agent hook --agent codex`).
package/dist/index.js CHANGED
@@ -3775,16 +3775,16 @@ async function inspect(agent) {
3775
3775
  const file = await statIfPresent(path);
3776
3776
  const marker = await statIfPresent(markerPath);
3777
3777
  if (file && !file.isFile() || marker && !marker.isFile()) {
3778
- return { state: "unsupported", path, markerPath };
3778
+ return { agent, state: "unsupported", path, markerPath };
3779
3779
  }
3780
- if (!file) return { state: "absent", path, markerPath };
3780
+ if (!file) return { agent, state: "absent", path, markerPath };
3781
3781
  const contentHash = hash(await readFile3(path, "utf8"));
3782
- if (!marker) return { state: "unmanaged", path, markerPath, contentHash };
3782
+ if (!marker) return { agent, state: "unmanaged", path, markerPath, contentHash };
3783
3783
  const savedHash = (await readFile3(markerPath, "utf8")).trim();
3784
3784
  if (!/^[a-f0-9]{64}$/.test(savedHash)) {
3785
- return { state: "unsupported", path, markerPath, contentHash };
3785
+ return { agent, state: "unsupported", path, markerPath, contentHash };
3786
3786
  }
3787
- return { state: savedHash === contentHash ? "installed" : "modified", path, markerPath, contentHash };
3787
+ return { agent, state: savedHash === contentHash ? "installed" : "modified", path, markerPath, contentHash };
3788
3788
  }
3789
3789
  var FLOWMIND_HOOK = /(?:^flowmind|index\.js') agent hook --agent (?:codex|claude)$/;
3790
3790
  var portablePath = (path) => realpathSync(path).replaceAll("\\", "/");
@@ -3875,6 +3875,50 @@ function withFlowmindHooks(config2, agent) {
3875
3875
  }
3876
3876
  var serialize = (config2) => `${JSON.stringify(config2, null, 2)}
3877
3877
  `;
3878
+ var PLUGIN_NAME = "flowmind";
3879
+ var PLUGIN_REPOSITORY = "afgonullu/flowmind-skill";
3880
+ async function readClaudePluginFile(path) {
3881
+ let text;
3882
+ try {
3883
+ text = await readFile3(path, "utf8");
3884
+ } catch (error) {
3885
+ if (error.code === "ENOENT") return null;
3886
+ throw error;
3887
+ }
3888
+ const parsed = JSON.parse(text);
3889
+ if (!isObject(parsed)) throw new Error(`${path} must contain a JSON object`);
3890
+ return parsed;
3891
+ }
3892
+ function pluginFrom(installed, marketplaces, enabled) {
3893
+ if (!isObject(installed.plugins)) throw new Error('installed_plugins.json has no "plugins" object');
3894
+ for (const [id, installs] of Object.entries(installed.plugins)) {
3895
+ const [name, marketplace] = id.split("@");
3896
+ if (name !== PLUGIN_NAME || !marketplace || enabled[id] !== true) continue;
3897
+ const entry = marketplaces[marketplace];
3898
+ const source = isObject(entry) ? entry.source : void 0;
3899
+ if (!isObject(source) || source.source !== "github" || typeof source.repo !== "string") continue;
3900
+ if (source.repo.toLowerCase() !== PLUGIN_REPOSITORY) continue;
3901
+ const userInstall = Array.isArray(installs) ? installs.find((install) => isObject(install) && install.scope === "user") : void 0;
3902
+ if (!userInstall) continue;
3903
+ return { id, version: typeof userInstall.version === "string" ? userInstall.version : null };
3904
+ }
3905
+ return null;
3906
+ }
3907
+ async function flowmindClaudePlugin(claudeSettings) {
3908
+ const pluginsDir = join3(homedir(), ".claude", "plugins");
3909
+ try {
3910
+ const installed = await readClaudePluginFile(join3(pluginsDir, "installed_plugins.json"));
3911
+ const marketplaces = await readClaudePluginFile(join3(pluginsDir, "known_marketplaces.json"));
3912
+ if (!installed || !marketplaces) return null;
3913
+ const enabled = isObject(claudeSettings?.enabledPlugins) ? claudeSettings.enabledPlugins : {};
3914
+ return pluginFrom(installed, marketplaces, enabled);
3915
+ } catch (error) {
3916
+ const reason = error instanceof Error ? error.message : String(error);
3917
+ console.warn(`Warning: could not read the Claude Code plugin list in ${pluginsDir} (${reason}); treating the Flowmind plugin as absent.`);
3918
+ return null;
3919
+ }
3920
+ }
3921
+ var pluginLabel = (plugin) => plugin.version ? `${plugin.id} ${plugin.version}` : plugin.id;
3878
3922
  function selectedAgents(value) {
3879
3923
  if (value === "all" || value === "codex" || value === "claude") return value;
3880
3924
  throw new InvalidArgumentError2("Agent must be codex, claude, or all.");
@@ -3887,14 +3931,19 @@ async function bundledSkill() {
3887
3931
  return readFile3(new URL("./flowmind/SKILL.md", import.meta.url), "utf8");
3888
3932
  }
3889
3933
  var hooksCommand = new Command4("hooks").description("Manage the Flowmind agent skill and session hooks");
3890
- hooksCommand.command("install").description("Install the Flowmind skill and session hooks for Codex and/or Claude Code").option("--agent <agent>", "codex, claude, or all", selectedAgents, "all").option("--force", "Replace an existing edited or unmanaged skill", false).option("--dry-run", "Show writes without changing files", false).action(async (options) => {
3934
+ hooksCommand.command("install").description(
3935
+ "Install the Flowmind skill and session hooks for Codex and/or Claude Code. An enabled Flowmind Claude Code plugin provides the skill there instead."
3936
+ ).option("--agent <agent>", "codex, claude, or all", selectedAgents, "all").option("--force", "Replace an existing edited or unmanaged skill", false).option("--dry-run", "Show writes without changing files", false).action(async (options) => {
3891
3937
  try {
3892
3938
  const content = await bundledSkill();
3893
3939
  const nextHash = hash(content);
3894
3940
  const targets = options.agent === "all" ? agents : [options.agent];
3895
3941
  const inspections = await Promise.all(targets.map(inspect));
3896
3942
  const hookConfigs = await Promise.all(targets.map(readHookConfig));
3897
- for (const target of inspections) {
3943
+ const claudeIndex = targets.indexOf("claude");
3944
+ const plugin = claudeIndex >= 0 ? await flowmindClaudePlugin(hookConfigs[claudeIndex].current) : null;
3945
+ const copies = inspections.filter((target) => !(plugin && target.agent === "claude"));
3946
+ for (const target of copies) {
3898
3947
  if (target.state === "unsupported") {
3899
3948
  throw new Error(`Unsupported skill or hash file at ${target.path}; no files written.`);
3900
3949
  }
@@ -3902,7 +3951,7 @@ hooksCommand.command("install").description("Install the Flowmind skill and sess
3902
3951
  throw new Error(`Refusing to overwrite ${target.path}; use --force to replace it. No files written.`);
3903
3952
  }
3904
3953
  }
3905
- for (const target of inspections) {
3954
+ for (const target of copies) {
3906
3955
  if (target.contentHash === nextHash && target.state === "installed") {
3907
3956
  console.log(`Unchanged ${target.path}`);
3908
3957
  continue;
@@ -3922,6 +3971,23 @@ hooksCommand.command("install").description("Install the Flowmind skill and sess
3922
3971
  `, { flag: "w", mode: 420 });
3923
3972
  console.log(`Wrote ${target.markerPath}`);
3924
3973
  }
3974
+ if (plugin) {
3975
+ const copy = inspections[claudeIndex];
3976
+ const reason = `the Flowmind plugin ${pluginLabel(plugin)} provides the skill in Claude Code`;
3977
+ if (copy.state === "installed") {
3978
+ if (options.dryRun) {
3979
+ console.log(`Would remove ${copy.path} and ${copy.markerPath}: ${reason}`);
3980
+ } else {
3981
+ await rm2(copy.path);
3982
+ await rm2(copy.markerPath);
3983
+ console.log(`Removed ${copy.path} and ${copy.markerPath}: ${reason}`);
3984
+ }
3985
+ } else if (copy.state === "absent") {
3986
+ console.log(`Skipped ${copy.path}: ${reason}`);
3987
+ } else {
3988
+ console.log(`Left ${copy.state} ${copy.path}: ${reason}. Remove that copy so the skill loads once.`);
3989
+ }
3990
+ }
3925
3991
  let codexHooksChanged = false;
3926
3992
  for (const [index, agent] of targets.entries()) {
3927
3993
  const config2 = hookConfigs[index];
@@ -3948,8 +4014,20 @@ hooksCommand.command("install").description("Install the Flowmind skill and sess
3948
4014
  hooksCommand.command("status").description("Show installed skills, session hooks, and a read-only legacy Codex copy").action(async () => {
3949
4015
  try {
3950
4016
  const currentHash = hash(await bundledSkill());
4017
+ let plugin = null;
4018
+ try {
4019
+ plugin = await flowmindClaudePlugin((await readHookConfig("claude")).current);
4020
+ } catch {
4021
+ plugin = null;
4022
+ }
3951
4023
  for (const agent of agents) {
3952
4024
  const target = await inspect(agent);
4025
+ if (agent === "claude" && plugin) {
4026
+ console.log(`claude: provided by plugin ${pluginLabel(plugin)}`);
4027
+ const duplicate = target.state === "absent" ? "" : " (duplicate of the plugin)";
4028
+ console.log(`claude copy: ${target.state}${duplicate} ${target.path}`);
4029
+ continue;
4030
+ }
3953
4031
  const update = target.state === "installed" && target.contentHash !== currentHash ? " (update available)" : "";
3954
4032
  console.log(`${agent}: ${target.state}${update} ${target.path}`);
3955
4033
  }
@@ -3997,6 +4075,10 @@ hooksCommand.command("uninstall").description("Remove unmodified Flowmind-manage
3997
4075
  console.log(`Updated ${config2.path}`);
3998
4076
  }
3999
4077
  }
4078
+ const plugin = await flowmindClaudePlugin((await readHookConfig("claude")).current);
4079
+ if (plugin) {
4080
+ console.log(`Left plugin ${pluginLabel(plugin)}; remove it with: claude plugin uninstall ${plugin.id}`);
4081
+ }
4000
4082
  } catch (error) {
4001
4083
  reportError(error);
4002
4084
  }
@@ -5853,7 +5935,7 @@ var shipLogCommand = new Command10("ship-log").description("Read a project ship
5853
5935
 
5854
5936
  // src/index.ts
5855
5937
  var program = new Command11();
5856
- program.name("flowmind").description("Flowmind CLI (also installed as fm)").version("0.3.1").enablePositionalOptions();
5938
+ program.name("flowmind").description("Flowmind CLI (also installed as fm)").version("0.3.2").enablePositionalOptions();
5857
5939
  program.addCommand(agentCommand);
5858
5940
  program.addCommand(authCommand);
5859
5941
  program.addCommand(logCommand);
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@flowmind/cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Flowmind CLI: log what you and your coding agents ship, with the outcome and the link that proves it. Works with Codex and Claude Code.",
5
+ "license": "MIT",
5
6
  "keywords": [
6
7
  "flowmind",
7
8
  "cli",