@devflow-one/cli 0.1.2 → 0.1.3
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/package.json +1 -1
- package/src/cli.js +6 -1
- package/src/config.js +16 -56
- package/src/gatewayDataDir.js +86 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -125,12 +125,15 @@ export async function run(argv, io = {}) {
|
|
|
125
125
|
const { key, command, rest } = match;
|
|
126
126
|
|
|
127
127
|
let config;
|
|
128
|
+
let configError = null;
|
|
128
129
|
|
|
129
130
|
try {
|
|
130
131
|
config = resolveConfig();
|
|
131
132
|
} catch (err) {
|
|
132
133
|
if (!(err instanceof ConfigError)) throw err;
|
|
133
134
|
|
|
135
|
+
configError = err;
|
|
136
|
+
|
|
134
137
|
// `where` is a diagnostic: it must still report what the checkout implies
|
|
135
138
|
// when there is no daemon to ask, since that is exactly when it is consulted.
|
|
136
139
|
if (command.method !== "LOCAL") {
|
|
@@ -149,7 +152,9 @@ export async function run(argv, io = {}) {
|
|
|
149
152
|
queryContext: config
|
|
150
153
|
? createContextQuery(config, request)
|
|
151
154
|
: async () => {
|
|
152
|
-
|
|
155
|
+
// Report only what was actually checked — the token file. Whether the
|
|
156
|
+
// app is running is unknown here, since the gateway was never called.
|
|
157
|
+
throw new Error(configError?.message ?? "no gateway token available.");
|
|
153
158
|
},
|
|
154
159
|
});
|
|
155
160
|
|
package/src/config.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
// Resolves how the CLI reaches the local gateway: base URL (port) and the Bearer
|
|
2
|
-
// token.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// token. Path/port resolution lives in the generated gatewayDataDir.js copy
|
|
3
|
+
// (authored at shared/gatewayDataDir.js) so the CLI, the MCP server and the
|
|
4
|
+
// desktop daemon can never disagree about where the token lives. Env vars
|
|
5
|
+
// override for tests / non-standard installs.
|
|
5
6
|
|
|
6
7
|
import fs from "node:fs";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import os from "node:os";
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
const HOST = "127.0.0.1";
|
|
12
|
-
/** Default gateway port (matches desktop server DEFAULT_PORT). */
|
|
13
|
-
const DEFAULT_PORT = 8787;
|
|
14
|
-
/** Token filename under the app's userData dir (matches token.js TOKEN_FILE). */
|
|
15
|
-
const TOKEN_FILE = "gateway-token";
|
|
9
|
+
import { describeMissingToken, resolveGatewayBaseUrl, resolveTokenPath } from "./gatewayDataDir.js";
|
|
16
10
|
|
|
17
11
|
/** A tagged config-resolution failure, so the CLI can print a clear message. */
|
|
18
12
|
export class ConfigError extends Error {
|
|
@@ -22,38 +16,9 @@ export class ConfigError extends Error {
|
|
|
22
16
|
}
|
|
23
17
|
}
|
|
24
18
|
|
|
25
|
-
/**
|
|
26
|
-
* The daemon's userData dir, matching desktop/src/gateway/token.js's fallback
|
|
27
|
-
* (the CLI runs under plain node, never Electron, so it always uses the fallback).
|
|
28
|
-
* @returns {string}
|
|
29
|
-
*/
|
|
30
|
-
function resolveDataDir() {
|
|
31
|
-
const home = os.homedir();
|
|
32
|
-
|
|
33
|
-
if (process.platform === "darwin") {
|
|
34
|
-
return path.join(home, "Library", "Application Support", "devflow");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (process.platform === "win32") {
|
|
38
|
-
const base = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
39
|
-
|
|
40
|
-
return path.join(base, "devflow");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return path.join(home, ".config", "devflow");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Resolve the gateway port from `DEVFLOW_GATEWAY_PORT`, else the default. */
|
|
47
|
-
function resolvePort() {
|
|
48
|
-
const port = Number.parseInt(process.env.DEVFLOW_GATEWAY_PORT, 10);
|
|
49
|
-
|
|
50
|
-
return Number.isInteger(port) && port > 0 && port < 65536 ? port : DEFAULT_PORT;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
19
|
/**
|
|
54
20
|
* Read the gateway token: `DEVFLOW_GATEWAY_TOKEN` wins, else the token file the
|
|
55
|
-
* daemon persisted. Throws {@link ConfigError}
|
|
56
|
-
* neither is available.
|
|
21
|
+
* daemon persisted. Throws {@link ConfigError} naming the exact path probed.
|
|
57
22
|
* @returns {string}
|
|
58
23
|
*/
|
|
59
24
|
function resolveToken() {
|
|
@@ -61,23 +26,20 @@ function resolveToken() {
|
|
|
61
26
|
|
|
62
27
|
if (fromEnv) return fromEnv;
|
|
63
28
|
|
|
64
|
-
const tokenPath =
|
|
29
|
+
const tokenPath = resolveTokenPath();
|
|
30
|
+
let contents;
|
|
65
31
|
|
|
66
32
|
try {
|
|
67
|
-
|
|
33
|
+
contents = fs.readFileSync(tokenPath, "utf8");
|
|
34
|
+
} catch {
|
|
35
|
+
throw new ConfigError(describeMissingToken(tokenPath, "missing"));
|
|
36
|
+
}
|
|
68
37
|
|
|
69
|
-
|
|
38
|
+
const token = contents.trim();
|
|
70
39
|
|
|
71
|
-
|
|
72
|
-
} catch (err) {
|
|
73
|
-
if (err instanceof ConfigError) throw err;
|
|
40
|
+
if (!token) throw new ConfigError(describeMissingToken(tokenPath, "empty"));
|
|
74
41
|
|
|
75
|
-
|
|
76
|
-
`Could not read the gateway token at ${tokenPath}. ` +
|
|
77
|
-
`Is the DevFlow desktop app running? ` +
|
|
78
|
-
`(Or set DEVFLOW_GATEWAY_TOKEN to override.)`,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
42
|
+
return token;
|
|
81
43
|
}
|
|
82
44
|
|
|
83
45
|
/**
|
|
@@ -85,10 +47,8 @@ function resolveToken() {
|
|
|
85
47
|
* @returns {{ baseUrl: string, token: string }}
|
|
86
48
|
*/
|
|
87
49
|
export function resolveConfig() {
|
|
88
|
-
const port = resolvePort();
|
|
89
|
-
|
|
90
50
|
return {
|
|
91
|
-
baseUrl:
|
|
51
|
+
baseUrl: resolveGatewayBaseUrl(),
|
|
92
52
|
token: resolveToken(),
|
|
93
53
|
};
|
|
94
54
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// GENERATED FILE — do not edit.
|
|
2
|
+
// Source of truth: shared/gatewayDataDir.js. Regenerate with:
|
|
3
|
+
// node scripts/sync-context-resolver.mjs
|
|
4
|
+
|
|
5
|
+
// Where the desktop daemon keeps its gateway state (bearer token, routing mirror),
|
|
6
|
+
// and where the CLI and MCP server read it back from. Authored once here because
|
|
7
|
+
// three independent copies of this logic is exactly how the clients ended up
|
|
8
|
+
// reading a directory the app never wrote to.
|
|
9
|
+
//
|
|
10
|
+
// The daemon itself resolves via Electron's `app.getPath("userData")`; this module
|
|
11
|
+
// is that path's plain-node equivalent. Electron derives userData from the app's
|
|
12
|
+
// package `name`, so APP_DIR_NAME must track `desktop/package.json`'s `name`.
|
|
13
|
+
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import os from "node:os";
|
|
16
|
+
|
|
17
|
+
/** Matches `desktop/package.json` `name` — Electron's userData dir is `<appData>/<name>`. */
|
|
18
|
+
export const APP_DIR_NAME = "devflow";
|
|
19
|
+
|
|
20
|
+
/** Filename the daemon writes the gateway token to, under the data dir. */
|
|
21
|
+
export const TOKEN_FILE = "gateway-token";
|
|
22
|
+
|
|
23
|
+
/** Default gateway port — must match desktop/src/gateway/server.js. */
|
|
24
|
+
export const DEFAULT_PORT = 8787;
|
|
25
|
+
|
|
26
|
+
/** Loopback host; the gateway only ever binds 127.0.0.1. */
|
|
27
|
+
export const HOST = "127.0.0.1";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the daemon's data dir for the current platform.
|
|
31
|
+
* `DEVFLOW_GATEWAY_DATA_DIR` overrides, so tests never touch real user data.
|
|
32
|
+
* @returns {string} absolute directory path
|
|
33
|
+
*/
|
|
34
|
+
export function resolveGatewayDataDir() {
|
|
35
|
+
if (process.env.DEVFLOW_GATEWAY_DATA_DIR) return process.env.DEVFLOW_GATEWAY_DATA_DIR;
|
|
36
|
+
|
|
37
|
+
const home = os.homedir();
|
|
38
|
+
|
|
39
|
+
if (process.platform === "darwin") {
|
|
40
|
+
return path.join(home, "Library", "Application Support", APP_DIR_NAME);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (process.platform === "win32") {
|
|
44
|
+
const base = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
45
|
+
|
|
46
|
+
return path.join(base, APP_DIR_NAME);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return path.join(home, ".config", APP_DIR_NAME);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Absolute path of the gateway token file. */
|
|
53
|
+
export function resolveTokenPath() {
|
|
54
|
+
return path.join(resolveGatewayDataDir(), TOKEN_FILE);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Resolve the gateway port from `DEVFLOW_GATEWAY_PORT`, else {@link DEFAULT_PORT}. */
|
|
58
|
+
export function resolveGatewayPort() {
|
|
59
|
+
const parsed = Number.parseInt(process.env.DEVFLOW_GATEWAY_PORT ?? "", 10);
|
|
60
|
+
|
|
61
|
+
return Number.isInteger(parsed) && parsed > 0 && parsed < 65536 ? parsed : DEFAULT_PORT;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Gateway base URL, honouring `DEVFLOW_GATEWAY_PORT`. */
|
|
65
|
+
export function resolveGatewayBaseUrl() {
|
|
66
|
+
return `http://${HOST}:${resolveGatewayPort()}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Message for "the token file isn't readable". States only what was checked —
|
|
71
|
+
* the probed path — and never asserts why, since nothing here touches the network.
|
|
72
|
+
* @param {string} tokenPath
|
|
73
|
+
* @param {"missing" | "empty"} [reason]
|
|
74
|
+
*/
|
|
75
|
+
export function describeMissingToken(tokenPath, reason = "missing") {
|
|
76
|
+
const what =
|
|
77
|
+
reason === "empty"
|
|
78
|
+
? `The gateway token file at ${tokenPath} is empty.`
|
|
79
|
+
: `No gateway token file at ${tokenPath}.`;
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
`${what} That file is written by the DevFlow desktop app on first run. ` +
|
|
83
|
+
`Set DEVFLOW_GATEWAY_TOKEN to use a token from elsewhere, or ` +
|
|
84
|
+
`DEVFLOW_GATEWAY_DATA_DIR if the app stores its data in another location.`
|
|
85
|
+
);
|
|
86
|
+
}
|