@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 +5 -0
- package/package.json +1 -1
- package/src/constants.mjs +1 -1
- package/src/credential-store.mjs +12 -12
- package/src/workbuddy-install.mjs +4 -0
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
package/src/constants.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const CLI_VERSION = "0.4.
|
|
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";
|
package/src/credential-store.mjs
CHANGED
|
@@ -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);
|