@episoda/cli 0.2.178 → 0.2.180
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/daemon/daemon-process.js +237 -7
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +56 -4
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
|
@@ -3046,7 +3046,7 @@ var require_package = __commonJS({
|
|
|
3046
3046
|
"package.json"(exports2, module2) {
|
|
3047
3047
|
module2.exports = {
|
|
3048
3048
|
name: "@episoda/cli",
|
|
3049
|
-
version: "0.2.
|
|
3049
|
+
version: "0.2.180",
|
|
3050
3050
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
3051
3051
|
main: "dist/index.js",
|
|
3052
3052
|
types: "dist/index.d.ts",
|
|
@@ -3072,6 +3072,7 @@ var require_package = __commonJS({
|
|
|
3072
3072
|
author: "Episoda",
|
|
3073
3073
|
license: "MIT",
|
|
3074
3074
|
dependencies: {
|
|
3075
|
+
"@lydell/node-pty": "1.2.0-beta.3",
|
|
3075
3076
|
chalk: "^4.1.2",
|
|
3076
3077
|
commander: "^11.1.0",
|
|
3077
3078
|
ora: "^5.4.1",
|
|
@@ -3082,8 +3083,8 @@ var require_package = __commonJS({
|
|
|
3082
3083
|
},
|
|
3083
3084
|
optionalDependencies: {
|
|
3084
3085
|
"@anthropic-ai/claude-code": "^2.0.0",
|
|
3085
|
-
"@
|
|
3086
|
-
"@
|
|
3086
|
+
"@modelcontextprotocol/server-github": "^0.6.0",
|
|
3087
|
+
"@openai/codex": "^0.86.0"
|
|
3087
3088
|
},
|
|
3088
3089
|
devDependencies: {
|
|
3089
3090
|
"@episoda/core": "workspace:*",
|
|
@@ -12299,6 +12300,91 @@ async function getConfigForApi() {
|
|
|
12299
12300
|
}
|
|
12300
12301
|
return (0, import_core13.loadConfig)();
|
|
12301
12302
|
}
|
|
12303
|
+
function stripGitCredentials(repoUrl) {
|
|
12304
|
+
try {
|
|
12305
|
+
const parsed = new URL(repoUrl);
|
|
12306
|
+
if (parsed.protocol !== "https:") {
|
|
12307
|
+
return repoUrl;
|
|
12308
|
+
}
|
|
12309
|
+
parsed.username = "";
|
|
12310
|
+
parsed.password = "";
|
|
12311
|
+
return parsed.toString();
|
|
12312
|
+
} catch {
|
|
12313
|
+
return repoUrl;
|
|
12314
|
+
}
|
|
12315
|
+
}
|
|
12316
|
+
function withGitHubInstallationToken(repoUrl, token) {
|
|
12317
|
+
try {
|
|
12318
|
+
const parsed = new URL(stripGitCredentials(repoUrl));
|
|
12319
|
+
if (parsed.protocol !== "https:" || parsed.hostname !== "github.com") {
|
|
12320
|
+
return repoUrl;
|
|
12321
|
+
}
|
|
12322
|
+
parsed.username = "x-access-token";
|
|
12323
|
+
parsed.password = token;
|
|
12324
|
+
return parsed.toString();
|
|
12325
|
+
} catch {
|
|
12326
|
+
return repoUrl;
|
|
12327
|
+
}
|
|
12328
|
+
}
|
|
12329
|
+
async function getFreshGithubRepoUrl(repoUrl, projectId) {
|
|
12330
|
+
if (!repoUrl.startsWith("https://")) {
|
|
12331
|
+
return repoUrl;
|
|
12332
|
+
}
|
|
12333
|
+
let parsed;
|
|
12334
|
+
try {
|
|
12335
|
+
parsed = new URL(repoUrl);
|
|
12336
|
+
} catch {
|
|
12337
|
+
return repoUrl;
|
|
12338
|
+
}
|
|
12339
|
+
if (parsed.hostname !== "github.com") {
|
|
12340
|
+
return repoUrl;
|
|
12341
|
+
}
|
|
12342
|
+
const config = await getConfigForApi();
|
|
12343
|
+
if (!config?.access_token) {
|
|
12344
|
+
return repoUrl;
|
|
12345
|
+
}
|
|
12346
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
12347
|
+
const headers = {
|
|
12348
|
+
"Authorization": `Bearer ${config.access_token}`,
|
|
12349
|
+
"Content-Type": "application/json",
|
|
12350
|
+
"User-Agent": "episoda-cli"
|
|
12351
|
+
};
|
|
12352
|
+
if (config.workspace_id) headers["x-workspace-id"] = config.workspace_id;
|
|
12353
|
+
if (config.workspace_slug) headers["x-workspace-uid"] = config.workspace_slug;
|
|
12354
|
+
if (config.project_id) headers["x-project-id"] = config.project_id;
|
|
12355
|
+
if (config.project_slug) headers["x-project-uid"] = config.project_slug;
|
|
12356
|
+
try {
|
|
12357
|
+
const response = await fetch(`${apiUrl}/api/github/installation-token`, {
|
|
12358
|
+
method: "POST",
|
|
12359
|
+
headers,
|
|
12360
|
+
body: JSON.stringify({ project_id: projectId })
|
|
12361
|
+
});
|
|
12362
|
+
if (!response.ok) {
|
|
12363
|
+
const body = await response.text();
|
|
12364
|
+
console.warn(`[Worktree] EP1435: Failed to refresh GitHub installation token (${response.status}): ${body}`);
|
|
12365
|
+
return repoUrl;
|
|
12366
|
+
}
|
|
12367
|
+
const payload = await response.json();
|
|
12368
|
+
const token = payload?.data?.token || payload?.token;
|
|
12369
|
+
if (!token) {
|
|
12370
|
+
console.warn("[Worktree] EP1435: Token refresh response missing token");
|
|
12371
|
+
return repoUrl;
|
|
12372
|
+
}
|
|
12373
|
+
return withGitHubInstallationToken(repoUrl, token);
|
|
12374
|
+
} catch (error) {
|
|
12375
|
+
console.warn(`[Worktree] EP1435: Could not refresh GitHub installation token: ${error.message}`);
|
|
12376
|
+
return repoUrl;
|
|
12377
|
+
}
|
|
12378
|
+
}
|
|
12379
|
+
async function readOriginUrl(bareRepoPath) {
|
|
12380
|
+
try {
|
|
12381
|
+
const { stdout } = await execAsync2(`git -C "${bareRepoPath}" config --get remote.origin.url`);
|
|
12382
|
+
const value = stdout.trim();
|
|
12383
|
+
return value.length > 0 ? value : null;
|
|
12384
|
+
} catch {
|
|
12385
|
+
return null;
|
|
12386
|
+
}
|
|
12387
|
+
}
|
|
12302
12388
|
var execAsync2 = (0, import_util2.promisify)(import_child_process14.exec);
|
|
12303
12389
|
function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
|
|
12304
12390
|
if (process.env.EPISODA_MODE !== "cloud") {
|
|
@@ -12377,14 +12463,15 @@ async function handleWorktreeCreate(request2) {
|
|
|
12377
12463
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
12378
12464
|
const bareRepoPath = path28.join(projectPath, ".bare");
|
|
12379
12465
|
const gitEnv = projectId ? { ...process.env, EPISODA_PROJECT_ID: projectId } : process.env;
|
|
12466
|
+
const refreshedRepoUrl = await getFreshGithubRepoUrl(repoUrl, projectId);
|
|
12380
12467
|
if (!fs28.existsSync(bareRepoPath)) {
|
|
12381
12468
|
console.log(`[Worktree] K1273: Project not found, cloning lazily...`);
|
|
12382
|
-
console.log(`[Worktree] Repo URL: ${
|
|
12469
|
+
console.log(`[Worktree] Repo URL: ${stripGitCredentials(refreshedRepoUrl)}`);
|
|
12383
12470
|
const episodaDir = path28.join(projectPath, ".episoda");
|
|
12384
12471
|
fs28.mkdirSync(episodaDir, { recursive: true });
|
|
12385
12472
|
try {
|
|
12386
12473
|
console.log(`[Worktree] K1273: Starting git clone...`);
|
|
12387
|
-
await execAsync2(`git clone --bare "${
|
|
12474
|
+
await execAsync2(`git clone --bare "${refreshedRepoUrl}" "${bareRepoPath}"`, { env: gitEnv });
|
|
12388
12475
|
console.log(`[Worktree] K1273: Clone successful`);
|
|
12389
12476
|
await execAsync2(`git -C "${bareRepoPath}" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`, { env: gitEnv });
|
|
12390
12477
|
const configPath = path28.join(episodaDir, "config.json");
|
|
@@ -12406,6 +12493,17 @@ async function handleWorktreeCreate(request2) {
|
|
|
12406
12493
|
};
|
|
12407
12494
|
}
|
|
12408
12495
|
} else {
|
|
12496
|
+
const currentOriginUrl = await readOriginUrl(bareRepoPath);
|
|
12497
|
+
const sourceUrl = currentOriginUrl || refreshedRepoUrl;
|
|
12498
|
+
const refreshedOriginUrl = await getFreshGithubRepoUrl(sourceUrl, projectId);
|
|
12499
|
+
if (refreshedOriginUrl !== sourceUrl) {
|
|
12500
|
+
try {
|
|
12501
|
+
await execAsync2(`git -C "${bareRepoPath}" remote set-url origin "${refreshedOriginUrl}"`, { env: gitEnv });
|
|
12502
|
+
console.log("[Worktree] EP1435: Refreshed bare repo origin URL with a fresh installation token");
|
|
12503
|
+
} catch (setUrlError) {
|
|
12504
|
+
console.warn(`[Worktree] EP1435: Failed to refresh origin URL: ${setUrlError.message}`);
|
|
12505
|
+
}
|
|
12506
|
+
}
|
|
12409
12507
|
console.log(`[Worktree] EP1373: Project exists, skipping handler-level fetch (manager handles narrow fetch)`);
|
|
12410
12508
|
}
|
|
12411
12509
|
const manager = new WorktreeManager(projectPath);
|
|
@@ -12772,6 +12870,113 @@ async function handleProjectSetup(params) {
|
|
|
12772
12870
|
}
|
|
12773
12871
|
}
|
|
12774
12872
|
|
|
12873
|
+
// src/daemon/handlers/pty-handler.ts
|
|
12874
|
+
var pty = __toESM(require("@lydell/node-pty"));
|
|
12875
|
+
var INACTIVITY_TIMEOUT_MS3 = 30 * 60 * 1e3;
|
|
12876
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
12877
|
+
async function handlePtySpawn(payload, client) {
|
|
12878
|
+
const { moduleUid, agent_run_id, command, args, env, cwd, cols = 220, rows = 50 } = payload;
|
|
12879
|
+
if (sessions.has(agent_run_id)) {
|
|
12880
|
+
console.warn(`[PTY] Session already exists for agent_run_id ${agent_run_id}, ignoring spawn`);
|
|
12881
|
+
return;
|
|
12882
|
+
}
|
|
12883
|
+
console.log(`[PTY] Spawning PTY for ${moduleUid} / ${agent_run_id}: ${command} ${args.join(" ")}`);
|
|
12884
|
+
let proc;
|
|
12885
|
+
try {
|
|
12886
|
+
proc = pty.spawn(command, args, {
|
|
12887
|
+
name: "xterm-256color",
|
|
12888
|
+
cols,
|
|
12889
|
+
rows,
|
|
12890
|
+
cwd: cwd || process.cwd(),
|
|
12891
|
+
env: { ...process.env, ...env || {} }
|
|
12892
|
+
});
|
|
12893
|
+
} catch (err) {
|
|
12894
|
+
console.error(`[PTY] Failed to spawn PTY for ${agent_run_id}:`, err);
|
|
12895
|
+
await client.send({
|
|
12896
|
+
type: "pty_exit",
|
|
12897
|
+
moduleUid,
|
|
12898
|
+
agent_run_id,
|
|
12899
|
+
code: -1,
|
|
12900
|
+
durationMs: 0
|
|
12901
|
+
});
|
|
12902
|
+
return;
|
|
12903
|
+
}
|
|
12904
|
+
const session = {
|
|
12905
|
+
pty: proc,
|
|
12906
|
+
moduleUid,
|
|
12907
|
+
agent_run_id,
|
|
12908
|
+
startedAt: Date.now(),
|
|
12909
|
+
lastOutputAt: Date.now(),
|
|
12910
|
+
watchdogTimer: null
|
|
12911
|
+
};
|
|
12912
|
+
sessions.set(agent_run_id, session);
|
|
12913
|
+
const resetWatchdog = () => {
|
|
12914
|
+
session.lastOutputAt = Date.now();
|
|
12915
|
+
if (session.watchdogTimer) clearTimeout(session.watchdogTimer);
|
|
12916
|
+
session.watchdogTimer = setTimeout(() => {
|
|
12917
|
+
console.warn(`[PTY] Inactivity timeout for ${agent_run_id} after ${INACTIVITY_TIMEOUT_MS3 / 6e4}m \u2014 sending SIGTERM`);
|
|
12918
|
+
try {
|
|
12919
|
+
proc.kill();
|
|
12920
|
+
} catch {
|
|
12921
|
+
}
|
|
12922
|
+
}, INACTIVITY_TIMEOUT_MS3);
|
|
12923
|
+
};
|
|
12924
|
+
resetWatchdog();
|
|
12925
|
+
proc.onData((data) => {
|
|
12926
|
+
resetWatchdog();
|
|
12927
|
+
client.send({
|
|
12928
|
+
type: "pty_data",
|
|
12929
|
+
moduleUid,
|
|
12930
|
+
agent_run_id,
|
|
12931
|
+
data
|
|
12932
|
+
}).catch((err) => {
|
|
12933
|
+
console.error(`[PTY] Failed to send pty_data for ${agent_run_id}:`, err.message);
|
|
12934
|
+
});
|
|
12935
|
+
});
|
|
12936
|
+
proc.onExit(({ exitCode }) => {
|
|
12937
|
+
const durationMs = Date.now() - session.startedAt;
|
|
12938
|
+
if (session.watchdogTimer) clearTimeout(session.watchdogTimer);
|
|
12939
|
+
console.log(`[PTY] Process exited for ${agent_run_id} with code ${exitCode} after ${durationMs}ms`);
|
|
12940
|
+
sessions.delete(agent_run_id);
|
|
12941
|
+
client.send({
|
|
12942
|
+
type: "pty_exit",
|
|
12943
|
+
moduleUid,
|
|
12944
|
+
agent_run_id,
|
|
12945
|
+
code: exitCode,
|
|
12946
|
+
durationMs
|
|
12947
|
+
}).catch((err) => {
|
|
12948
|
+
console.error(`[PTY] Failed to send pty_exit for ${agent_run_id}:`, err.message);
|
|
12949
|
+
});
|
|
12950
|
+
});
|
|
12951
|
+
}
|
|
12952
|
+
function handlePtyResize(payload) {
|
|
12953
|
+
const { agent_run_id, cols, rows } = payload;
|
|
12954
|
+
const session = sessions.get(agent_run_id);
|
|
12955
|
+
if (!session) {
|
|
12956
|
+
console.warn(`[PTY] No session found for resize: ${agent_run_id}`);
|
|
12957
|
+
return;
|
|
12958
|
+
}
|
|
12959
|
+
try {
|
|
12960
|
+
session.pty.resize(cols, rows);
|
|
12961
|
+
} catch (err) {
|
|
12962
|
+
console.error(`[PTY] Resize error for ${agent_run_id}:`, err);
|
|
12963
|
+
}
|
|
12964
|
+
}
|
|
12965
|
+
function handlePtyKill(payload) {
|
|
12966
|
+
const { agent_run_id } = payload;
|
|
12967
|
+
const session = sessions.get(agent_run_id);
|
|
12968
|
+
if (!session) {
|
|
12969
|
+
console.warn(`[PTY] No session found for kill: ${agent_run_id}`);
|
|
12970
|
+
return;
|
|
12971
|
+
}
|
|
12972
|
+
try {
|
|
12973
|
+
session.pty.kill();
|
|
12974
|
+
} catch {
|
|
12975
|
+
}
|
|
12976
|
+
sessions.delete(agent_run_id);
|
|
12977
|
+
console.log(`[PTY] Killed session ${agent_run_id}`);
|
|
12978
|
+
}
|
|
12979
|
+
|
|
12775
12980
|
// src/utils/dev-server.ts
|
|
12776
12981
|
var import_child_process15 = require("child_process");
|
|
12777
12982
|
var import_core14 = __toESM(require_dist());
|
|
@@ -13553,8 +13758,8 @@ var UpdateManager = class _UpdateManager {
|
|
|
13553
13758
|
getActiveAgentSessionCount() {
|
|
13554
13759
|
try {
|
|
13555
13760
|
const agentManager = getAgentControlPlane();
|
|
13556
|
-
const
|
|
13557
|
-
return
|
|
13761
|
+
const sessions2 = agentManager.getAllSessions();
|
|
13762
|
+
return sessions2.filter((session) => ["starting", "running", "stopping"].includes(session.status)).length;
|
|
13558
13763
|
} catch {
|
|
13559
13764
|
return 0;
|
|
13560
13765
|
}
|
|
@@ -14553,6 +14758,7 @@ var ProjectMessageRouter = class {
|
|
|
14553
14758
|
this.registerCommandHandler(context);
|
|
14554
14759
|
this.registerRemoteCommandHandler(context);
|
|
14555
14760
|
this.registerTunnelCommandHandler(context);
|
|
14761
|
+
this.registerPtyCommandHandler(context);
|
|
14556
14762
|
}
|
|
14557
14763
|
registerCommandHandler({ projectId, projectPath, client, gitExecutor }) {
|
|
14558
14764
|
client.on("command", async (message) => {
|
|
@@ -14801,6 +15007,30 @@ var ProjectMessageRouter = class {
|
|
|
14801
15007
|
}
|
|
14802
15008
|
});
|
|
14803
15009
|
}
|
|
15010
|
+
// EP1441: PTY lifecycle handler
|
|
15011
|
+
registerPtyCommandHandler({ projectId, client }) {
|
|
15012
|
+
client.on("pty_spawn", async (message) => {
|
|
15013
|
+
if (message.type === "pty_spawn") {
|
|
15014
|
+
const payload = message.payload;
|
|
15015
|
+
console.log(`[Daemon] EP1441: Received pty_spawn for ${projectId}: ${payload.agent_run_id}`);
|
|
15016
|
+
client.updateActivity();
|
|
15017
|
+
await handlePtySpawn(payload, client);
|
|
15018
|
+
}
|
|
15019
|
+
});
|
|
15020
|
+
client.on("pty_resize", (message) => {
|
|
15021
|
+
if (message.type === "pty_resize") {
|
|
15022
|
+
const payload = message.payload;
|
|
15023
|
+
handlePtyResize(payload);
|
|
15024
|
+
}
|
|
15025
|
+
});
|
|
15026
|
+
client.on("pty_kill", (message) => {
|
|
15027
|
+
if (message.type === "pty_kill") {
|
|
15028
|
+
const payload = message.payload;
|
|
15029
|
+
console.log(`[Daemon] EP1441: Received pty_kill for agent_run_id ${payload.agent_run_id}`);
|
|
15030
|
+
handlePtyKill(payload);
|
|
15031
|
+
}
|
|
15032
|
+
});
|
|
15033
|
+
}
|
|
14804
15034
|
};
|
|
14805
15035
|
|
|
14806
15036
|
// src/daemon/daemon-api-client.ts
|