@devflow-one/cli 0.1.1 → 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/README.md +91 -5
- package/package.json +4 -2
- package/src/cli.js +81 -40
- package/src/client.js +13 -3
- package/src/commands.js +44 -27
- package/src/config.js +16 -56
- package/src/context.js +249 -0
- package/src/contextResolver.js +288 -0
- package/src/gatewayDataDir.js +86 -0
- package/src/gitFacts.js +47 -0
|
@@ -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
|
+
}
|
package/src/gitFacts.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// GENERATED FILE — do not edit.
|
|
2
|
+
// Source of truth: shared/gitFacts.js. Regenerate with:
|
|
3
|
+
// node scripts/sync-context-resolver.mjs
|
|
4
|
+
|
|
5
|
+
// The only place this repo shells out to git for routing inference. Kept apart
|
|
6
|
+
// from contextResolver.js so the resolver itself stays pure and testable with a
|
|
7
|
+
// fake runner. Generated copies ship inside cli/ and mcp/ — edit THIS file only.
|
|
8
|
+
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
10
|
+
|
|
11
|
+
/** Hard ceiling so a wedged git can never hang a CLI invocation. */
|
|
12
|
+
const GIT_TIMEOUT_MS = 2000;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build a read-only, non-interactive git runner rooted at `cwd`.
|
|
16
|
+
*
|
|
17
|
+
* Every invocation is incapable of blocking on input: `--no-pager` rules out a
|
|
18
|
+
* pager, `GIT_TERMINAL_PROMPT=0` and an empty `GIT_ASKPASS`/`SSH_ASKPASS` stop a
|
|
19
|
+
* credential helper prompting, and stdin is closed. A non-repo directory or any
|
|
20
|
+
* other failure yields `null` rather than throwing.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} cwd
|
|
23
|
+
* @returns {(args: string[]) => string | null} stdout (trimmed), or null on failure
|
|
24
|
+
*/
|
|
25
|
+
export function createGitRunner(cwd) {
|
|
26
|
+
return (args) => {
|
|
27
|
+
try {
|
|
28
|
+
const stdout = execFileSync("git", ["--no-pager", ...args], {
|
|
29
|
+
cwd: cwd || process.cwd(),
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
timeout: GIT_TIMEOUT_MS,
|
|
32
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
33
|
+
env: {
|
|
34
|
+
...process.env,
|
|
35
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
36
|
+
GIT_ASKPASS: "",
|
|
37
|
+
SSH_ASKPASS: "",
|
|
38
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return typeof stdout === "string" ? stdout.trim() : null;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|