edsger 0.71.0 → 0.72.1
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/dist/api/mcp-client.js
CHANGED
|
@@ -21,8 +21,15 @@ export async function callMcpEndpoint(method, params) {
|
|
|
21
21
|
if (!mcpToken) {
|
|
22
22
|
throw new Error('Not authenticated. Run `edsger login` or set EDSGER_MCP_TOKEN environment variable.');
|
|
23
23
|
}
|
|
24
|
+
// The platform splits its JSON-RPC surface across two edge functions that
|
|
25
|
+
// share the same base URL and MCP-token auth:
|
|
26
|
+
// - `/mcp` — slash-notation methods (issues/list, github/org_repos, ...)
|
|
27
|
+
// - `/agents` — dot-notation methods (agents.*, services.*, skills.*, ...)
|
|
28
|
+
// Route by the method's separator so callers don't have to care which
|
|
29
|
+
// function owns a given method.
|
|
30
|
+
const endpoint = method.includes('.') ? 'agents' : 'mcp';
|
|
24
31
|
try {
|
|
25
|
-
const response = await fetch(`${mcpServerUrl}
|
|
32
|
+
const response = await fetch(`${mcpServerUrl}/${endpoint}`, {
|
|
26
33
|
method: 'POST',
|
|
27
34
|
headers: {
|
|
28
35
|
'Content-Type': 'application/json',
|
|
@@ -29,9 +29,16 @@ export async function runSyncTerraform(teamId, options = {}) {
|
|
|
29
29
|
repoFullName = result?.team?.terraform_repo_full_name ?? null;
|
|
30
30
|
}
|
|
31
31
|
catch {
|
|
32
|
-
// Fallback:
|
|
32
|
+
// Fallback: the caller (e.g. the desktop app) may have already resolved
|
|
33
|
+
// the team's repo and injected it as EDSGER_TERRAFORM_REPO, so MCP isn't
|
|
34
|
+
// strictly required.
|
|
33
35
|
logWarning('Could not fetch team via MCP, attempting to read terraform_repo_full_name from env');
|
|
34
36
|
}
|
|
37
|
+
// Env fallback works whether or not MCP returned a repo — it lets the
|
|
38
|
+
// desktop app drive the sync without depending on the MCP team endpoint.
|
|
39
|
+
if (!repoFullName) {
|
|
40
|
+
repoFullName = process.env.EDSGER_TERRAFORM_REPO ?? null;
|
|
41
|
+
}
|
|
35
42
|
if (!repoFullName) {
|
|
36
43
|
logError('No Terraform repo configured for this team. ' +
|
|
37
44
|
'Go to Team Settings and set a Terraform repo, or use --dir to point at a local directory.');
|
package/dist/index.js
CHANGED
|
@@ -58,6 +58,7 @@ import { runWorkflow } from './commands/workflow/index.js';
|
|
|
58
58
|
import { DEFAULT_MAX_FILES as FIND_ARCHITECTURE_DEFAULT_MAX_FILES } from './phases/find-architecture/index.js';
|
|
59
59
|
import { DEFAULT_MAX_FILES as FIND_SMELLS_DEFAULT_MAX_FILES } from './phases/find-smells/index.js';
|
|
60
60
|
import { SMELL_CATEGORIES, } from './phases/find-smells/types.js';
|
|
61
|
+
import { deregisterSession, registerSession, } from './system/session-manager.js';
|
|
61
62
|
import { logError, logInfo } from './utils/logger.js';
|
|
62
63
|
// Get package.json version dynamically
|
|
63
64
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- ESM __filename/__dirname polyfill
|
|
@@ -77,6 +78,24 @@ program
|
|
|
77
78
|
.name('edsger')
|
|
78
79
|
.description('AI-powered workflow automation and code review CLI tool')
|
|
79
80
|
.version(version);
|
|
81
|
+
// When the desktop app spawns a streamed CLI it sets EDSGER_PROCESS_KEY (its
|
|
82
|
+
// process-registry key with the `:output` marker stripped, e.g.
|
|
83
|
+
// `sync-terraform` or `recipes:<productId>`). Open a cli_sessions row whose
|
|
84
|
+
// `command` is that key so the originating tab can find the still-running
|
|
85
|
+
// session and replay its logs across tab switches / restarts / devices.
|
|
86
|
+
// registerSession is idempotent: commands that open their own (richer) session
|
|
87
|
+
// just enrich the row started here (the process key stays in `command`), and
|
|
88
|
+
// deregisterSession is a no-op once they've closed it.
|
|
89
|
+
program.hook('preAction', async (_thisCommand, actionCommand) => {
|
|
90
|
+
if (process.env.EDSGER_PROCESS_KEY) {
|
|
91
|
+
await registerSession({ command: actionCommand.name() });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
program.hook('postAction', async () => {
|
|
95
|
+
if (process.env.EDSGER_PROCESS_KEY) {
|
|
96
|
+
await deregisterSession();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
80
99
|
// ============================================================
|
|
81
100
|
// Subcommand: edsger login
|
|
82
101
|
// ============================================================
|
|
@@ -20,6 +20,14 @@ export interface CliSession {
|
|
|
20
20
|
/**
|
|
21
21
|
* Register this CLI session with the server.
|
|
22
22
|
* Optionally accepts command name and product ID for filtering on detail pages.
|
|
23
|
+
*
|
|
24
|
+
* Idempotent: if a session is already active (e.g. registered by the top-level
|
|
25
|
+
* command hook), this enriches that row with the command/product instead of
|
|
26
|
+
* creating a second session, and returns the existing id.
|
|
27
|
+
*
|
|
28
|
+
* The stored `command` is the desktop process key when the CLI was spawned by
|
|
29
|
+
* the app (EDSGER_PROCESS_KEY), so a feature/tab can find this session; see
|
|
30
|
+
* `resolvedCommand`.
|
|
23
31
|
*/
|
|
24
32
|
export declare function registerSession(options?: {
|
|
25
33
|
command?: string;
|
|
@@ -25,13 +25,42 @@ function generateSessionId() {
|
|
|
25
25
|
const random = Math.random().toString(36).substring(2, 8);
|
|
26
26
|
return `cli-${timestamp}-${random}`;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* The value stored in `cli_sessions.command`.
|
|
30
|
+
*
|
|
31
|
+
* When the desktop app spawns a streamed CLI it injects EDSGER_PROCESS_KEY --
|
|
32
|
+
* its process-registry key (`:output` channel marker already stripped), e.g.
|
|
33
|
+
* `sync-terraform`, `recipes:<productId>`, `pr-review:<prId>`. That key is what
|
|
34
|
+
* the originating tab looks up by, so it takes precedence over the bare command
|
|
35
|
+
* name; standalone runs fall back to the supplied command name.
|
|
36
|
+
*/
|
|
37
|
+
function resolvedCommand(options) {
|
|
38
|
+
return process.env.EDSGER_PROCESS_KEY || options?.command;
|
|
39
|
+
}
|
|
28
40
|
/**
|
|
29
41
|
* Register this CLI session with the server.
|
|
30
42
|
* Optionally accepts command name and product ID for filtering on detail pages.
|
|
43
|
+
*
|
|
44
|
+
* Idempotent: if a session is already active (e.g. registered by the top-level
|
|
45
|
+
* command hook), this enriches that row with the command/product instead of
|
|
46
|
+
* creating a second session, and returns the existing id.
|
|
47
|
+
*
|
|
48
|
+
* The stored `command` is the desktop process key when the CLI was spawned by
|
|
49
|
+
* the app (EDSGER_PROCESS_KEY), so a feature/tab can find this session; see
|
|
50
|
+
* `resolvedCommand`.
|
|
31
51
|
*/
|
|
32
52
|
export async function registerSession(options) {
|
|
53
|
+
// Already in a session for this process — enrich, don't double-register.
|
|
54
|
+
if (currentSessionId) {
|
|
55
|
+
await enrichSession(options);
|
|
56
|
+
return currentSessionId;
|
|
57
|
+
}
|
|
33
58
|
const sessionId = generateSessionId();
|
|
34
59
|
currentSessionId = sessionId;
|
|
60
|
+
const command = resolvedCommand(options);
|
|
61
|
+
// Full invocation (subcommand + args) for readable run history. `command`
|
|
62
|
+
// stays the stable, entity-scoped lookup key; this carries the variable args.
|
|
63
|
+
const invocation = process.argv.slice(2).join(' ') || undefined;
|
|
35
64
|
try {
|
|
36
65
|
const userId = getUserId();
|
|
37
66
|
if (hasSupabaseSession() && userId) {
|
|
@@ -44,8 +73,11 @@ export async function registerSession(options) {
|
|
|
44
73
|
started_at: new Date().toISOString(),
|
|
45
74
|
last_heartbeat: new Date().toISOString(),
|
|
46
75
|
};
|
|
47
|
-
if (
|
|
48
|
-
row.command =
|
|
76
|
+
if (command) {
|
|
77
|
+
row.command = command;
|
|
78
|
+
}
|
|
79
|
+
if (invocation) {
|
|
80
|
+
row.invocation = invocation;
|
|
49
81
|
}
|
|
50
82
|
if (options?.productId) {
|
|
51
83
|
row.product_id = options.productId;
|
|
@@ -64,8 +96,11 @@ export async function registerSession(options) {
|
|
|
64
96
|
version: getVersion(),
|
|
65
97
|
status: 'running',
|
|
66
98
|
};
|
|
67
|
-
if (
|
|
68
|
-
payload.command =
|
|
99
|
+
if (command) {
|
|
100
|
+
payload.command = command;
|
|
101
|
+
}
|
|
102
|
+
if (invocation) {
|
|
103
|
+
payload.invocation = invocation;
|
|
69
104
|
}
|
|
70
105
|
if (options?.productId) {
|
|
71
106
|
payload.product_id = options.productId;
|
|
@@ -82,6 +117,41 @@ export async function registerSession(options) {
|
|
|
82
117
|
initLogSync(sessionId);
|
|
83
118
|
return sessionId;
|
|
84
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Best-effort enrichment of the already-active session row with command /
|
|
122
|
+
* product info (e.g. when a command registers itself after the top-level hook
|
|
123
|
+
* already opened the session). Direct-SDK path only; the MCP heartbeat path
|
|
124
|
+
* keeps whatever the initial register supplied.
|
|
125
|
+
*/
|
|
126
|
+
async function enrichSession(options) {
|
|
127
|
+
const command = resolvedCommand(options);
|
|
128
|
+
if (!currentSessionId || (!command && !options?.productId)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const userId = getUserId();
|
|
133
|
+
if (!hasSupabaseSession() || !userId) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const patch = {};
|
|
137
|
+
// With a desktop process key present, `command` resolves to that key, so
|
|
138
|
+
// a self-registering command can't clobber it with its bare name.
|
|
139
|
+
if (command) {
|
|
140
|
+
patch.command = command;
|
|
141
|
+
}
|
|
142
|
+
if (options?.productId) {
|
|
143
|
+
patch.product_id = options.productId;
|
|
144
|
+
}
|
|
145
|
+
await getSupabase()
|
|
146
|
+
.from('cli_sessions')
|
|
147
|
+
.update(patch)
|
|
148
|
+
.eq('session_id', currentSessionId)
|
|
149
|
+
.eq('user_id', userId);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// best-effort
|
|
153
|
+
}
|
|
154
|
+
}
|
|
85
155
|
/**
|
|
86
156
|
* Send a heartbeat to keep the session alive and check for commands
|
|
87
157
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "edsger",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.72.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"edsger": "dist/index.js"
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"commander": "^12.0.0",
|
|
51
51
|
"cosmiconfig": "^9.0.0",
|
|
52
52
|
"dotenv": "^16.4.5",
|
|
53
|
-
"edsger-contract": "0.
|
|
54
|
-
"edsger-tools": "0.9.
|
|
53
|
+
"edsger-contract": "0.9.1",
|
|
54
|
+
"edsger-tools": "0.9.1",
|
|
55
55
|
"gray-matter": "^4.0.3",
|
|
56
56
|
"zod": "^4.0.0"
|
|
57
57
|
},
|