@fargowealth/fargo-ai-cli 0.4.1 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.2
4
+
5
+ - Windows no longer calls `chmod` while reading or writing credentials; `%APPDATA%` continues to provide the inherited NTFS user ACL without a sandbox-blocked metadata write.
6
+ - `workbuddy install` preserves the Windows executable search `PATH` in the MCP environment so the host can launch `npx` reliably.
7
+
3
8
  ## 0.4.1
4
9
 
5
10
  - **Windows behavior change:** WorkBuddy credentials now use `%APPDATA%\Fargo AI\credentials.json` instead of DPAPI, because sandboxed WorkBuddy sessions may be unable to read or update DPAPI-protected data.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fargowealth/fargo-ai-cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Fargo AI OAuth and stdio MCP bridge for WorkBuddy",
5
5
  "type": "module",
6
6
  "bin": {
package/src/constants.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const CLI_VERSION = "0.4.1";
1
+ export const CLI_VERSION = "0.4.2";
2
2
  export const DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
3
3
  export const DEFAULT_AUTH_URL = "https://auth.fargowealth.app";
4
4
  export const DEFAULT_MCP_URL = "https://mcp.fargowealth.app/mcp";
@@ -23,14 +23,14 @@ export function createCredentialStore(options = {}) {
23
23
  : join(home, ".fargo");
24
24
  const filePath = env.FARGO_CREDENTIAL_PATH ?? join(credentialDirectory, "credentials.json");
25
25
  const refreshLockPath = env.FARGO_REFRESH_LOCK_PATH ?? join(credentialDirectory, "oauth-refresh.lock");
26
- const withRefreshLock = (callback) => withFileLock(refreshLockPath, callback);
26
+ const withRefreshLock = (callback) => withFileLock(refreshLockPath, callback, platform);
27
27
 
28
28
  return {
29
29
  kind: "private-file",
30
30
  withRefreshLock,
31
31
  async load() {
32
32
  try {
33
- await secureExistingFile(filePath);
33
+ await secureExistingFile(filePath, platform);
34
34
  return parseCredential(await readFile(filePath, "utf8"));
35
35
  } catch (error) {
36
36
  if (error?.code === "ENOENT") return undefined;
@@ -39,7 +39,7 @@ export function createCredentialStore(options = {}) {
39
39
  },
40
40
  async save(value) {
41
41
  const directory = dirname(filePath);
42
- await ensurePrivateDirectory(directory);
42
+ await ensurePrivateDirectory(directory, platform);
43
43
  const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
44
44
  let handle;
45
45
  try {
@@ -48,16 +48,16 @@ export function createCredentialStore(options = {}) {
48
48
  await handle.sync();
49
49
  await handle.close();
50
50
  handle = undefined;
51
- await chmod(temporaryPath, 0o600);
51
+ if (platform !== "win32") await chmod(temporaryPath, 0o600);
52
52
  await rename(temporaryPath, filePath);
53
- await secureExistingFile(filePath);
53
+ await secureExistingFile(filePath, platform);
54
54
  } finally {
55
55
  await handle?.close().catch(() => {});
56
56
  await rm(temporaryPath, { force: true });
57
57
  }
58
58
  },
59
59
  async clear() {
60
- await secureExistingFile(filePath).catch((error) => {
60
+ await secureExistingFile(filePath, platform).catch((error) => {
61
61
  if (error?.code !== "ENOENT") throw error;
62
62
  });
63
63
  await rm(filePath, { force: true });
@@ -65,8 +65,8 @@ export function createCredentialStore(options = {}) {
65
65
  };
66
66
  }
67
67
 
68
- async function withFileLock(lockPath, callback) {
69
- await ensurePrivateDirectory(dirname(lockPath));
68
+ async function withFileLock(lockPath, callback, platform) {
69
+ await ensurePrivateDirectory(dirname(lockPath), platform);
70
70
  const deadline = Date.now() + 15_000;
71
71
  const owner = `${process.pid}:${randomUUID()}`;
72
72
  let handle;
@@ -102,23 +102,23 @@ async function withFileLock(lockPath, callback) {
102
102
  }
103
103
  }
104
104
 
105
- async function ensurePrivateDirectory(directoryPath) {
105
+ async function ensurePrivateDirectory(directoryPath, platform) {
106
106
  await mkdir(directoryPath, { recursive: true, mode: 0o700 });
107
107
  const details = await lstat(directoryPath);
108
108
  if (!details.isDirectory() || details.isSymbolicLink()) {
109
109
  throw new Error(`Fargo credential directory is not a private directory: ${directoryPath}`);
110
110
  }
111
111
  assertCurrentUserOwns(details, "credential directory");
112
- await chmod(directoryPath, 0o700);
112
+ if (platform !== "win32") await chmod(directoryPath, 0o700);
113
113
  }
114
114
 
115
- async function secureExistingFile(filePath) {
115
+ async function secureExistingFile(filePath, platform) {
116
116
  const details = await lstat(filePath);
117
117
  if (!details.isFile() || details.isSymbolicLink()) {
118
118
  throw new Error(`Fargo credential path is not a regular file: ${filePath}`);
119
119
  }
120
120
  assertCurrentUserOwns(details, "credential file");
121
- await chmod(filePath, 0o600);
121
+ if (platform !== "win32") await chmod(filePath, 0o600);
122
122
  }
123
123
 
124
124
  function assertCurrentUserOwns(details, label) {
@@ -34,6 +34,10 @@ export async function installWorkBuddyMcp(options = {}) {
34
34
  FARGO_REFRESH_LOCK_PATH: join(credentialDirectory, "oauth-refresh.lock")
35
35
  };
36
36
  if (platform === "darwin") nextServer.env.PATH = buildMacPath(npxCommand);
37
+ if (platform === "win32") {
38
+ const windowsPath = env.PATH ?? env.Path ?? env.path;
39
+ if (windowsPath) nextServer.env.PATH = windowsPath;
40
+ }
37
41
  }
38
42
  const previousServer = config.mcpServers?.["fargo-ai"];
39
43
  const changed = JSON.stringify(previousServer) !== JSON.stringify(nextServer);