@gh-symphony/cli 0.0.14 → 0.0.16
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/chunk-5NV3LSAJ.js +11 -0
- package/dist/chunk-6HBZC3BE.js +468 -0
- package/dist/chunk-76QPITKI.js +109 -0
- package/dist/chunk-EFMFGOWM.js +3575 -0
- package/dist/chunk-IWR4UQEJ.js +2250 -0
- package/dist/chunk-JO3AXHQI.js +130 -0
- package/dist/chunk-MHIWAIVD.js +876 -0
- package/dist/chunk-MVRF7BES.js +68 -0
- package/dist/chunk-ROGRTUFI.js +108 -0
- package/dist/chunk-TF3QNWNC.js +1121 -0
- package/dist/chunk-TH5QPO3Y.js +67 -0
- package/dist/config-cmd-AZ7POMAA.js +110 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +568 -356
- package/dist/init-EZXQAXZM.js +17 -0
- package/dist/logs-6LNGT2GF.js +188 -0
- package/dist/project-557FE2GD.js +679 -0
- package/dist/recover-LVBI2TGH.js +131 -0
- package/dist/repo-R3XBIVAX.js +121 -0
- package/dist/run-WITYAYFZ.js +108 -0
- package/dist/start-JUFKNL3N.js +16 -0
- package/dist/status-3WK5BWRZ.js +11 -0
- package/dist/stop-AA3AP5M6.js +9 -0
- package/dist/version-VBB62JWI.js +30 -0
- package/dist/worker-entry.js +1828 -0
- package/package.json +9 -4
- package/dist/ansi.d.ts +0 -15
- package/dist/ansi.js +0 -53
- package/dist/commands/config-cmd.d.ts +0 -3
- package/dist/commands/config-cmd.js +0 -90
- package/dist/commands/help.d.ts +0 -3
- package/dist/commands/help.js +0 -55
- package/dist/commands/init.d.ts +0 -34
- package/dist/commands/init.js +0 -477
- package/dist/commands/logs.d.ts +0 -3
- package/dist/commands/logs.js +0 -184
- package/dist/commands/project.d.ts +0 -3
- package/dist/commands/project.js +0 -649
- package/dist/commands/recover.d.ts +0 -3
- package/dist/commands/recover.js +0 -119
- package/dist/commands/repo.d.ts +0 -3
- package/dist/commands/repo.js +0 -103
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/run.js +0 -95
- package/dist/commands/start.d.ts +0 -20
- package/dist/commands/start.js +0 -344
- package/dist/commands/status-refresh.d.ts +0 -9
- package/dist/commands/status-refresh.js +0 -27
- package/dist/commands/status.d.ts +0 -3
- package/dist/commands/status.js +0 -237
- package/dist/commands/stop.d.ts +0 -3
- package/dist/commands/stop.js +0 -92
- package/dist/commands/version.d.ts +0 -3
- package/dist/commands/version.js +0 -21
- package/dist/completion.d.ts +0 -1
- package/dist/completion.js +0 -204
- package/dist/config.d.ts +0 -38
- package/dist/config.js +0 -82
- package/dist/context/context-types.d.ts +0 -36
- package/dist/context/context-types.js +0 -1
- package/dist/context/generate-context-yaml.d.ts +0 -15
- package/dist/context/generate-context-yaml.js +0 -129
- package/dist/dashboard/renderer.d.ts +0 -9
- package/dist/dashboard/renderer.js +0 -220
- package/dist/detection/environment-detector.d.ts +0 -11
- package/dist/detection/environment-detector.js +0 -140
- package/dist/github/client.d.ts +0 -71
- package/dist/github/client.js +0 -348
- package/dist/github/gh-auth.d.ts +0 -34
- package/dist/github/gh-auth.js +0 -110
- package/dist/mapping/smart-defaults.d.ts +0 -17
- package/dist/mapping/smart-defaults.js +0 -86
- package/dist/orchestrator-runtime.d.ts +0 -1
- package/dist/orchestrator-runtime.js +0 -4
- package/dist/orchestrator-status-endpoint.d.ts +0 -5
- package/dist/orchestrator-status-endpoint.js +0 -27
- package/dist/project-selection.d.ts +0 -8
- package/dist/project-selection.js +0 -56
- package/dist/skills/skill-writer.d.ts +0 -14
- package/dist/skills/skill-writer.js +0 -62
- package/dist/skills/templates/commit.d.ts +0 -2
- package/dist/skills/templates/commit.js +0 -45
- package/dist/skills/templates/document.d.ts +0 -7
- package/dist/skills/templates/document.js +0 -16
- package/dist/skills/templates/gh-project.d.ts +0 -2
- package/dist/skills/templates/gh-project.js +0 -88
- package/dist/skills/templates/gh-symphony.d.ts +0 -2
- package/dist/skills/templates/gh-symphony.js +0 -125
- package/dist/skills/templates/index.d.ts +0 -8
- package/dist/skills/templates/index.js +0 -28
- package/dist/skills/templates/land.d.ts +0 -2
- package/dist/skills/templates/land.js +0 -59
- package/dist/skills/templates/pull.d.ts +0 -2
- package/dist/skills/templates/pull.js +0 -41
- package/dist/skills/templates/push.d.ts +0 -2
- package/dist/skills/templates/push.js +0 -36
- package/dist/skills/types.d.ts +0 -23
- package/dist/skills/types.js +0 -1
- package/dist/workflow/generate-reference-workflow.d.ts +0 -9
- package/dist/workflow/generate-reference-workflow.js +0 -261
- package/dist/workflow/generate-workflow-md.d.ts +0 -12
- package/dist/workflow/generate-workflow-md.js +0 -134
package/dist/config.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
export const DEFAULT_CONFIG_DIR = join(homedir(), ".gh-symphony");
|
|
5
|
-
export const CONFIG_FILE = "config.json";
|
|
6
|
-
export const DAEMON_PID_FILE = "daemon.pid";
|
|
7
|
-
export const ORCHESTRATOR_LOG_FILE = "orchestrator.log";
|
|
8
|
-
export const ORCHESTRATOR_PORT_FILE = "port";
|
|
9
|
-
export function resolveConfigDir(override) {
|
|
10
|
-
return override ?? process.env.GH_SYMPHONY_CONFIG_DIR ?? DEFAULT_CONFIG_DIR;
|
|
11
|
-
}
|
|
12
|
-
export function configFilePath(configDir) {
|
|
13
|
-
return join(configDir, CONFIG_FILE);
|
|
14
|
-
}
|
|
15
|
-
export function projectConfigDir(configDir, projectId) {
|
|
16
|
-
return join(configDir, "projects", projectId);
|
|
17
|
-
}
|
|
18
|
-
export function projectConfigPath(configDir, projectId) {
|
|
19
|
-
return join(projectConfigDir(configDir, projectId), "project.json");
|
|
20
|
-
}
|
|
21
|
-
export function daemonPidPath(configDir, projectId) {
|
|
22
|
-
return join(projectConfigDir(configDir, projectId), DAEMON_PID_FILE);
|
|
23
|
-
}
|
|
24
|
-
export function orchestratorLogPath(configDir, projectId) {
|
|
25
|
-
return join(projectConfigDir(configDir, projectId), ORCHESTRATOR_LOG_FILE);
|
|
26
|
-
}
|
|
27
|
-
export function orchestratorPortPath(configDir, projectId) {
|
|
28
|
-
return join(projectConfigDir(configDir, projectId), ORCHESTRATOR_PORT_FILE);
|
|
29
|
-
}
|
|
30
|
-
export async function loadGlobalConfig(configDir) {
|
|
31
|
-
const raw = await readJsonFile(configFilePath(configDir));
|
|
32
|
-
if (!raw) {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
return {
|
|
36
|
-
activeProject: typeof raw.activeProject === "string" ? raw.activeProject : null,
|
|
37
|
-
projects: Array.isArray(raw.projects)
|
|
38
|
-
? raw.projects.filter((projectId) => typeof projectId === "string")
|
|
39
|
-
: [],
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
export async function saveGlobalConfig(configDir, config) {
|
|
43
|
-
await writeJsonFile(configFilePath(configDir), config);
|
|
44
|
-
}
|
|
45
|
-
export async function loadProjectConfig(configDir, projectId) {
|
|
46
|
-
return readJsonFile(projectConfigPath(configDir, projectId));
|
|
47
|
-
}
|
|
48
|
-
export async function saveProjectConfig(configDir, projectId, config) {
|
|
49
|
-
await writeJsonFile(projectConfigPath(configDir, projectId), config);
|
|
50
|
-
}
|
|
51
|
-
export async function loadActiveProjectConfig(configDir) {
|
|
52
|
-
const global = await loadGlobalConfig(configDir);
|
|
53
|
-
if (!global?.activeProject) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
return loadProjectConfig(configDir, global.activeProject);
|
|
57
|
-
}
|
|
58
|
-
async function readJsonFile(path) {
|
|
59
|
-
try {
|
|
60
|
-
const raw = await readFile(path, "utf8");
|
|
61
|
-
return JSON.parse(raw);
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
if (isFileMissing(error)) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
async function writeJsonFile(path, value) {
|
|
71
|
-
await mkdir(dirname(path), { recursive: true });
|
|
72
|
-
const temporaryPath = `${path}.tmp`;
|
|
73
|
-
await writeFile(temporaryPath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
74
|
-
const { rename } = await import("node:fs/promises");
|
|
75
|
-
await rename(temporaryPath, path);
|
|
76
|
-
}
|
|
77
|
-
function isFileMissing(error) {
|
|
78
|
-
return Boolean(error &&
|
|
79
|
-
typeof error === "object" &&
|
|
80
|
-
"code" in error &&
|
|
81
|
-
(error.code === "ENOENT" || error.code === "ENOTDIR"));
|
|
82
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { DetectedEnvironment } from "../detection/environment-detector.js";
|
|
2
|
-
export type ContextYaml = {
|
|
3
|
-
schema_version: 1;
|
|
4
|
-
collected_at: string;
|
|
5
|
-
project: {
|
|
6
|
-
id: string;
|
|
7
|
-
title: string;
|
|
8
|
-
url: string;
|
|
9
|
-
};
|
|
10
|
-
status_field: {
|
|
11
|
-
id: string;
|
|
12
|
-
name: string;
|
|
13
|
-
columns: Array<{
|
|
14
|
-
id: string;
|
|
15
|
-
name: string;
|
|
16
|
-
color: string | null;
|
|
17
|
-
inferred_role: "active" | "wait" | "terminal" | null;
|
|
18
|
-
confidence: "high" | "low";
|
|
19
|
-
}>;
|
|
20
|
-
};
|
|
21
|
-
text_fields: Array<{
|
|
22
|
-
id: string;
|
|
23
|
-
name: string;
|
|
24
|
-
data_type: string;
|
|
25
|
-
}>;
|
|
26
|
-
repositories: Array<{
|
|
27
|
-
owner: string;
|
|
28
|
-
name: string;
|
|
29
|
-
clone_url: string;
|
|
30
|
-
}>;
|
|
31
|
-
detected_environment: DetectedEnvironment;
|
|
32
|
-
runtime: {
|
|
33
|
-
agent: string;
|
|
34
|
-
agent_command: string;
|
|
35
|
-
};
|
|
36
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ProjectDetail, ProjectStatusField } from "../github/client.js";
|
|
2
|
-
import type { DetectedEnvironment } from "../detection/environment-detector.js";
|
|
3
|
-
import type { ContextYaml } from "./context-types.js";
|
|
4
|
-
export type BuildContextYamlParams = {
|
|
5
|
-
projectDetail: ProjectDetail;
|
|
6
|
-
statusField: ProjectStatusField;
|
|
7
|
-
detectedEnvironment: DetectedEnvironment;
|
|
8
|
-
runtime: {
|
|
9
|
-
agent: string;
|
|
10
|
-
agent_command: string;
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
|
-
export declare function generateContextYamlString(context: ContextYaml): string;
|
|
14
|
-
export declare function writeContextYaml(outputDir: string, context: ContextYaml): Promise<void>;
|
|
15
|
-
export declare function buildContextYaml(params: BuildContextYamlParams): ContextYaml;
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
|
-
import { inferStateRole } from "../mapping/smart-defaults.js";
|
|
4
|
-
function yamlQuote(value) {
|
|
5
|
-
const specialChars = /[:#{'"\]{}()\\[]|\n/;
|
|
6
|
-
if (specialChars.test(value)) {
|
|
7
|
-
return `"${value.replace(/"/g, '\\"')}"`;
|
|
8
|
-
}
|
|
9
|
-
return value;
|
|
10
|
-
}
|
|
11
|
-
export function generateContextYamlString(context) {
|
|
12
|
-
const lines = [];
|
|
13
|
-
lines.push("schema_version: 1");
|
|
14
|
-
lines.push(`collected_at: ${context.collected_at}`);
|
|
15
|
-
lines.push("");
|
|
16
|
-
lines.push("project:");
|
|
17
|
-
lines.push(` id: ${context.project.id}`);
|
|
18
|
-
lines.push(` title: ${yamlQuote(context.project.title)}`);
|
|
19
|
-
lines.push(` url: ${context.project.url}`);
|
|
20
|
-
lines.push("");
|
|
21
|
-
lines.push("status_field:");
|
|
22
|
-
lines.push(` id: ${context.status_field.id}`);
|
|
23
|
-
lines.push(` name: ${yamlQuote(context.status_field.name)}`);
|
|
24
|
-
lines.push(" columns:");
|
|
25
|
-
for (const column of context.status_field.columns) {
|
|
26
|
-
lines.push(` - id: ${column.id}`);
|
|
27
|
-
lines.push(` name: ${yamlQuote(column.name)}`);
|
|
28
|
-
lines.push(` color: ${column.color === null ? "null" : yamlQuote(column.color)}`);
|
|
29
|
-
lines.push(` inferred_role: ${column.inferred_role === null ? "null" : column.inferred_role}`);
|
|
30
|
-
lines.push(` confidence: ${column.confidence}`);
|
|
31
|
-
}
|
|
32
|
-
lines.push("");
|
|
33
|
-
lines.push("text_fields:");
|
|
34
|
-
if (context.text_fields.length === 0) {
|
|
35
|
-
lines.push(" []");
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
for (const field of context.text_fields) {
|
|
39
|
-
lines.push(` - id: ${field.id}`);
|
|
40
|
-
lines.push(` name: ${yamlQuote(field.name)}`);
|
|
41
|
-
lines.push(` data_type: ${field.data_type}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
lines.push("");
|
|
45
|
-
lines.push("repositories:");
|
|
46
|
-
if (context.repositories.length === 0) {
|
|
47
|
-
lines.push(" []");
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
for (const repo of context.repositories) {
|
|
51
|
-
lines.push(` - owner: ${repo.owner}`);
|
|
52
|
-
lines.push(` name: ${repo.name}`);
|
|
53
|
-
lines.push(` clone_url: ${repo.clone_url}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
lines.push("");
|
|
57
|
-
lines.push("detected_environment:");
|
|
58
|
-
lines.push(` packageManager: ${context.detected_environment.packageManager === null ? "null" : yamlQuote(context.detected_environment.packageManager)}`);
|
|
59
|
-
lines.push(` lockfile: ${context.detected_environment.lockfile === null ? "null" : yamlQuote(context.detected_environment.lockfile)}`);
|
|
60
|
-
lines.push(` testCommand: ${context.detected_environment.testCommand === null ? "null" : yamlQuote(context.detected_environment.testCommand)}`);
|
|
61
|
-
lines.push(` buildCommand: ${context.detected_environment.buildCommand === null ? "null" : yamlQuote(context.detected_environment.buildCommand)}`);
|
|
62
|
-
lines.push(` lintCommand: ${context.detected_environment.lintCommand === null ? "null" : yamlQuote(context.detected_environment.lintCommand)}`);
|
|
63
|
-
lines.push(` ciPlatform: ${context.detected_environment.ciPlatform === null ? "null" : yamlQuote(context.detected_environment.ciPlatform)}`);
|
|
64
|
-
lines.push(` monorepo: ${context.detected_environment.monorepo}`);
|
|
65
|
-
lines.push(" existingSkills:");
|
|
66
|
-
if (context.detected_environment.existingSkills.length === 0) {
|
|
67
|
-
lines.push(" []");
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
for (const skill of context.detected_environment.existingSkills) {
|
|
71
|
-
lines.push(` - ${yamlQuote(skill)}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
lines.push("");
|
|
75
|
-
lines.push("runtime:");
|
|
76
|
-
lines.push(` agent: ${yamlQuote(context.runtime.agent)}`);
|
|
77
|
-
lines.push(` agent_command: ${yamlQuote(context.runtime.agent_command)}`);
|
|
78
|
-
return lines.join("\n") + "\n";
|
|
79
|
-
}
|
|
80
|
-
export async function writeContextYaml(outputDir, context) {
|
|
81
|
-
await mkdir(outputDir, { recursive: true });
|
|
82
|
-
const contextPath = `${outputDir}/.gh-symphony/context.yaml`;
|
|
83
|
-
await mkdir(dirname(contextPath), { recursive: true });
|
|
84
|
-
const temporaryPath = `${contextPath}.tmp`;
|
|
85
|
-
const yamlContent = generateContextYamlString(context);
|
|
86
|
-
await writeFile(temporaryPath, yamlContent, "utf8");
|
|
87
|
-
const { rename } = await import("node:fs/promises");
|
|
88
|
-
await rename(temporaryPath, contextPath);
|
|
89
|
-
}
|
|
90
|
-
export function buildContextYaml(params) {
|
|
91
|
-
const columns = params.statusField.options.map((option) => {
|
|
92
|
-
const roleMapping = inferStateRole(option.name);
|
|
93
|
-
return {
|
|
94
|
-
id: option.id,
|
|
95
|
-
name: option.name,
|
|
96
|
-
color: option.color,
|
|
97
|
-
inferred_role: roleMapping.role,
|
|
98
|
-
confidence: roleMapping.confidence,
|
|
99
|
-
};
|
|
100
|
-
});
|
|
101
|
-
const textFields = params.projectDetail.textFields.map((field) => ({
|
|
102
|
-
id: field.id,
|
|
103
|
-
name: field.name,
|
|
104
|
-
data_type: field.dataType,
|
|
105
|
-
}));
|
|
106
|
-
const repositories = params.projectDetail.linkedRepositories.map((repo) => ({
|
|
107
|
-
owner: repo.owner,
|
|
108
|
-
name: repo.name,
|
|
109
|
-
clone_url: repo.cloneUrl,
|
|
110
|
-
}));
|
|
111
|
-
return {
|
|
112
|
-
schema_version: 1,
|
|
113
|
-
collected_at: new Date().toISOString(),
|
|
114
|
-
project: {
|
|
115
|
-
id: params.projectDetail.id,
|
|
116
|
-
title: params.projectDetail.title,
|
|
117
|
-
url: params.projectDetail.url,
|
|
118
|
-
},
|
|
119
|
-
status_field: {
|
|
120
|
-
id: params.statusField.id,
|
|
121
|
-
name: params.statusField.name,
|
|
122
|
-
columns,
|
|
123
|
-
},
|
|
124
|
-
text_fields: textFields,
|
|
125
|
-
repositories,
|
|
126
|
-
detected_environment: params.detectedEnvironment,
|
|
127
|
-
runtime: params.runtime,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { ProjectStatusSnapshot } from "@gh-symphony/core";
|
|
2
|
-
export type DashboardOptions = {
|
|
3
|
-
terminalWidth: number;
|
|
4
|
-
noColor: boolean;
|
|
5
|
-
maxAgents?: number;
|
|
6
|
-
/** Override Date.now() for deterministic testing */
|
|
7
|
-
now?: number;
|
|
8
|
-
};
|
|
9
|
-
export declare function renderDashboard(snapshots: ProjectStatusSnapshot[], options: DashboardOptions): string;
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
// ── Dashboard Renderer (Elixir-parity) ──────────────────────────────────────
|
|
2
|
-
import { bold, dim, green, red, yellow, cyan, magenta, blue, stripAnsi, } from "../ansi.js";
|
|
3
|
-
// ── Column widths (from Elixir spec) ─────────────────────────────────────────
|
|
4
|
-
const COL_ID = 24;
|
|
5
|
-
const COL_STATUS = 14;
|
|
6
|
-
const COL_PID = 8;
|
|
7
|
-
const COL_AGE_TURN = 12;
|
|
8
|
-
const COL_TOKENS = 10;
|
|
9
|
-
const COL_SESSION = 14;
|
|
10
|
-
/** ID header width accounts for "● " prefix in data rows */
|
|
11
|
-
const COL_ID_HEADER = COL_ID + 2;
|
|
12
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
13
|
-
const identity = (s) => s;
|
|
14
|
-
function makeColors(noColor) {
|
|
15
|
-
if (noColor) {
|
|
16
|
-
return {
|
|
17
|
-
bold: identity,
|
|
18
|
-
dim: identity,
|
|
19
|
-
green: identity,
|
|
20
|
-
red: identity,
|
|
21
|
-
yellow: identity,
|
|
22
|
-
cyan: identity,
|
|
23
|
-
magenta: identity,
|
|
24
|
-
blue: identity,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
return { bold, dim, green, red, yellow, cyan, magenta, blue };
|
|
28
|
-
}
|
|
29
|
-
function pad(s, width, align = "left") {
|
|
30
|
-
const visible = stripAnsi(s);
|
|
31
|
-
if (visible.length >= width)
|
|
32
|
-
return visible.slice(0, width);
|
|
33
|
-
const padding = " ".repeat(width - visible.length);
|
|
34
|
-
return align === "right" ? padding + s : s + padding;
|
|
35
|
-
}
|
|
36
|
-
function compactSessionId(id) {
|
|
37
|
-
if (!id)
|
|
38
|
-
return "\u2014";
|
|
39
|
-
if (id.length <= 10)
|
|
40
|
-
return id;
|
|
41
|
-
return `${id.slice(0, 4)}...${id.slice(-6)}`;
|
|
42
|
-
}
|
|
43
|
-
function fmtTokens(n) {
|
|
44
|
-
return n.toLocaleString("en-US");
|
|
45
|
-
}
|
|
46
|
-
function fmtAge(startedAt, now) {
|
|
47
|
-
if (!startedAt)
|
|
48
|
-
return "0m";
|
|
49
|
-
const diffMs = now - new Date(startedAt).getTime();
|
|
50
|
-
if (diffMs < 0)
|
|
51
|
-
return "0m";
|
|
52
|
-
const totalMin = Math.floor(diffMs / 60_000);
|
|
53
|
-
if (totalMin < 60)
|
|
54
|
-
return `${totalMin}m`;
|
|
55
|
-
const h = Math.floor(totalMin / 60);
|
|
56
|
-
const m = totalMin % 60;
|
|
57
|
-
return `${h}h ${m}m`;
|
|
58
|
-
}
|
|
59
|
-
function fmtRuntime(ms) {
|
|
60
|
-
if (ms <= 0)
|
|
61
|
-
return "0h 0m";
|
|
62
|
-
const totalMin = Math.floor(ms / 60_000);
|
|
63
|
-
const h = Math.floor(totalMin / 60);
|
|
64
|
-
const m = totalMin % 60;
|
|
65
|
-
return `${h}h ${m}m`;
|
|
66
|
-
}
|
|
67
|
-
function fmtRetryTime(nextRetryAt, now) {
|
|
68
|
-
if (!nextRetryAt)
|
|
69
|
-
return "\u2014";
|
|
70
|
-
const diffMs = new Date(nextRetryAt).getTime() - now;
|
|
71
|
-
if (diffMs <= 0)
|
|
72
|
-
return "now";
|
|
73
|
-
const totalSec = Math.ceil(diffMs / 1000);
|
|
74
|
-
if (totalSec < 60)
|
|
75
|
-
return `${totalSec}s`;
|
|
76
|
-
const m = Math.floor(totalSec / 60);
|
|
77
|
-
const s = totalSec % 60;
|
|
78
|
-
return s > 0 ? `${m}m ${s}s` : `${m}m`;
|
|
79
|
-
}
|
|
80
|
-
const COL_SEPARATORS = 6;
|
|
81
|
-
function eventColWidth(termWidth) {
|
|
82
|
-
const fixed = 2 +
|
|
83
|
-
COL_ID_HEADER +
|
|
84
|
-
COL_STATUS +
|
|
85
|
-
COL_PID +
|
|
86
|
-
COL_AGE_TURN +
|
|
87
|
-
COL_TOKENS +
|
|
88
|
-
COL_SESSION +
|
|
89
|
-
COL_SEPARATORS;
|
|
90
|
-
return Math.max(5, termWidth - fixed);
|
|
91
|
-
}
|
|
92
|
-
// ── Status dot ───────────────────────────────────────────────────────────────
|
|
93
|
-
function statusDot(run, c) {
|
|
94
|
-
const event = run.lastEvent;
|
|
95
|
-
if (event === null || event === undefined || run.status === "failed")
|
|
96
|
-
return c.red("\u25CF");
|
|
97
|
-
if (event === "token_count")
|
|
98
|
-
return c.yellow("\u25CF");
|
|
99
|
-
if (event === "task_started")
|
|
100
|
-
return c.green("\u25CF");
|
|
101
|
-
if (event === "turn_completed")
|
|
102
|
-
return c.magenta("\u25CF");
|
|
103
|
-
return c.blue("\u25CF");
|
|
104
|
-
}
|
|
105
|
-
// ── Section builders ─────────────────────────────────────────────────────────
|
|
106
|
-
function titleBar(width, c) {
|
|
107
|
-
const title = " gh-symphony ";
|
|
108
|
-
const side = Math.max(0, Math.floor((width - title.length) / 2));
|
|
109
|
-
const right = Math.max(0, width - side - title.length);
|
|
110
|
-
return c.bold("\u2550".repeat(side) + title + "\u2550".repeat(right));
|
|
111
|
-
}
|
|
112
|
-
function sectionDivider(label, width, c) {
|
|
113
|
-
const prefix = `\u2500\u2500 ${label} `;
|
|
114
|
-
const fill = "\u2500".repeat(Math.max(0, width - prefix.length));
|
|
115
|
-
return c.dim(prefix + fill);
|
|
116
|
-
}
|
|
117
|
-
function buildSummaryLines(snapshots, options, c) {
|
|
118
|
-
const now = options.now ?? Date.now();
|
|
119
|
-
const lines = [];
|
|
120
|
-
const totalActive = snapshots.reduce((sum, s) => sum + s.summary.activeRuns, 0);
|
|
121
|
-
const agentStr = options.maxAgents != null
|
|
122
|
-
? `${totalActive}/${options.maxAgents}`
|
|
123
|
-
: `${totalActive}`;
|
|
124
|
-
const totIn = snapshots.reduce((sum, s) => sum + (s.codexTotals?.inputTokens ?? 0), 0);
|
|
125
|
-
const totOut = snapshots.reduce((sum, s) => sum + (s.codexTotals?.outputTokens ?? 0), 0);
|
|
126
|
-
const totAll = snapshots.reduce((sum, s) => sum + (s.codexTotals?.totalTokens ?? 0), 0);
|
|
127
|
-
const allStarts = snapshots
|
|
128
|
-
.flatMap((s) => s.activeRuns)
|
|
129
|
-
.map((r) => r.startedAt)
|
|
130
|
-
.filter((t) => t != null)
|
|
131
|
-
.map((t) => new Date(t).getTime());
|
|
132
|
-
const runtimeMs = allStarts.length > 0 ? now - Math.min(...allStarts) : 0;
|
|
133
|
-
const runtime = fmtRuntime(runtimeMs);
|
|
134
|
-
lines.push(` ${c.dim("Agents")} ${c.bold(agentStr)} ${c.dim("Runtime")} ${c.bold(runtime)} ${c.dim("Tokens")} ${fmtTokens(totIn)} in / ${fmtTokens(totOut)} out / ${c.bold(fmtTokens(totAll))} total`);
|
|
135
|
-
const hasLimits = snapshots.some((s) => s.rateLimits != null);
|
|
136
|
-
const limitStr = hasLimits ? "active" : "standard";
|
|
137
|
-
lines.push(` ${c.dim("Rate Limits")} ${limitStr}`);
|
|
138
|
-
return lines;
|
|
139
|
-
}
|
|
140
|
-
function tableHeaderRow(c) {
|
|
141
|
-
const cols = [
|
|
142
|
-
pad("ID", COL_ID_HEADER),
|
|
143
|
-
pad("STATUS", COL_STATUS),
|
|
144
|
-
pad("PID", COL_PID),
|
|
145
|
-
pad("AGE/TURN", COL_AGE_TURN),
|
|
146
|
-
pad("TOKENS", COL_TOKENS),
|
|
147
|
-
pad("SESSION", COL_SESSION),
|
|
148
|
-
"EVENT",
|
|
149
|
-
].join(" ");
|
|
150
|
-
return ` ${c.dim(cols)}`;
|
|
151
|
-
}
|
|
152
|
-
function activeRunRow(run, now, evtWidth, c) {
|
|
153
|
-
const dot = statusDot(run, c);
|
|
154
|
-
const id = pad(run.issueIdentifier, COL_ID);
|
|
155
|
-
const status = pad(run.issueState ?? run.executionPhase ?? "\u2014", COL_STATUS);
|
|
156
|
-
const pid = pad(run.processId != null ? String(run.processId) : "\u2014", COL_PID);
|
|
157
|
-
const age = fmtAge(run.startedAt, now);
|
|
158
|
-
const turn = run.turnCount ?? 0;
|
|
159
|
-
const ageTurn = pad(`${age}/${turn}`, COL_AGE_TURN);
|
|
160
|
-
const tokens = pad(fmtTokens(run.tokenUsage?.totalTokens ?? 0), COL_TOKENS, "right");
|
|
161
|
-
const sessionId = run.runtimeSession?.sessionId ?? run.runtimeSession?.threadId ?? null;
|
|
162
|
-
const session = pad(compactSessionId(sessionId), COL_SESSION);
|
|
163
|
-
const event = pad(run.lastEvent ?? "\u2014", evtWidth);
|
|
164
|
-
const columns = [id, status, pid, ageTurn, tokens, session, event].join(" ");
|
|
165
|
-
return ` ${dot} ${columns}`;
|
|
166
|
-
}
|
|
167
|
-
function retryRow(entry, snapshot, now, c) {
|
|
168
|
-
const id = entry.issueIdentifier;
|
|
169
|
-
const kind = entry.retryKind;
|
|
170
|
-
const timeStr = fmtRetryTime(entry.nextRetryAt, now);
|
|
171
|
-
const matchingRun = snapshot.activeRuns.find((r) => r.runId === entry.runId);
|
|
172
|
-
const errorHint = matchingRun?.lastEvent ?? "";
|
|
173
|
-
return ` ${c.yellow("\u21BB")} ${id} ${kind} retrying in ${timeStr}${errorHint ? " " + errorHint : ""}`;
|
|
174
|
-
}
|
|
175
|
-
// ── Main Renderer ────────────────────────────────────────────────────────────
|
|
176
|
-
export function renderDashboard(snapshots, options) {
|
|
177
|
-
const width = options.terminalWidth || 115;
|
|
178
|
-
const now = options.now ?? Date.now();
|
|
179
|
-
const c = makeColors(options.noColor);
|
|
180
|
-
const evtWidth = eventColWidth(width);
|
|
181
|
-
const lines = [];
|
|
182
|
-
lines.push(titleBar(width, c));
|
|
183
|
-
lines.push(...buildSummaryLines(snapshots, options, c));
|
|
184
|
-
lines.push("");
|
|
185
|
-
for (const snap of snapshots) {
|
|
186
|
-
const hasActiveRuns = snap.activeRuns.length > 0;
|
|
187
|
-
const hasRetries = snap.retryQueue.length > 0;
|
|
188
|
-
if (!hasActiveRuns && !hasRetries)
|
|
189
|
-
continue;
|
|
190
|
-
lines.push(sectionDivider(snap.slug, width, c));
|
|
191
|
-
if (hasActiveRuns) {
|
|
192
|
-
lines.push(tableHeaderRow(c));
|
|
193
|
-
for (const rawRun of snap.activeRuns) {
|
|
194
|
-
const run = rawRun;
|
|
195
|
-
lines.push(activeRunRow(run, now, evtWidth, c));
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
lines.push("");
|
|
199
|
-
}
|
|
200
|
-
const allRetries = [];
|
|
201
|
-
for (const snap of snapshots) {
|
|
202
|
-
for (const entry of snap.retryQueue) {
|
|
203
|
-
allRetries.push({ entry, snapshot: snap });
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (allRetries.length > 0) {
|
|
207
|
-
lines.push(sectionDivider("Backoff Queue", width, c));
|
|
208
|
-
for (const { entry, snapshot } of allRetries) {
|
|
209
|
-
lines.push(retryRow(entry, snapshot, now, c));
|
|
210
|
-
}
|
|
211
|
-
lines.push("");
|
|
212
|
-
}
|
|
213
|
-
const result = lines.map((line) => {
|
|
214
|
-
const visible = stripAnsi(line);
|
|
215
|
-
if (visible.length <= width)
|
|
216
|
-
return line;
|
|
217
|
-
return visible.slice(0, width);
|
|
218
|
-
});
|
|
219
|
-
return result.join("\n");
|
|
220
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export type DetectedEnvironment = {
|
|
2
|
-
packageManager: "pnpm" | "npm" | "yarn" | "bun" | null;
|
|
3
|
-
lockfile: string | null;
|
|
4
|
-
testCommand: string | null;
|
|
5
|
-
buildCommand: string | null;
|
|
6
|
-
lintCommand: string | null;
|
|
7
|
-
ciPlatform: "github-actions" | null;
|
|
8
|
-
monorepo: boolean;
|
|
9
|
-
existingSkills: string[];
|
|
10
|
-
};
|
|
11
|
-
export declare function detectEnvironment(cwd: string): Promise<DetectedEnvironment>;
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { access, readFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
function isFileMissing(error) {
|
|
4
|
-
return Boolean(error &&
|
|
5
|
-
typeof error === "object" &&
|
|
6
|
-
"code" in error &&
|
|
7
|
-
error.code === "ENOENT");
|
|
8
|
-
}
|
|
9
|
-
async function fileExists(path) {
|
|
10
|
-
try {
|
|
11
|
-
await access(path);
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
14
|
-
catch (error) {
|
|
15
|
-
if (isFileMissing(error)) {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
throw error;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
async function readJsonFile(path) {
|
|
22
|
-
try {
|
|
23
|
-
const raw = await readFile(path, "utf8");
|
|
24
|
-
return JSON.parse(raw);
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
if (isFileMissing(error)) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
if (error instanceof SyntaxError) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
async function detectPackageManager(cwd) {
|
|
37
|
-
const lockfiles = [
|
|
38
|
-
{ name: "pnpm-lock.yaml", manager: "pnpm" },
|
|
39
|
-
{ name: "bun.lock", manager: "bun" },
|
|
40
|
-
{ name: "bun.lockb", manager: "bun" },
|
|
41
|
-
{ name: "yarn.lock", manager: "yarn" },
|
|
42
|
-
{ name: "package-lock.json", manager: "npm" },
|
|
43
|
-
];
|
|
44
|
-
for (const { name, manager } of lockfiles) {
|
|
45
|
-
const exists = await fileExists(join(cwd, name));
|
|
46
|
-
if (exists) {
|
|
47
|
-
return { packageManager: manager, lockfile: name };
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return { packageManager: null, lockfile: null };
|
|
51
|
-
}
|
|
52
|
-
async function detectScripts(cwd) {
|
|
53
|
-
const packageJson = await readJsonFile(join(cwd, "package.json"));
|
|
54
|
-
if (!packageJson?.scripts) {
|
|
55
|
-
return { testCommand: null, buildCommand: null, lintCommand: null };
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
testCommand: packageJson.scripts.test ?? null,
|
|
59
|
-
buildCommand: packageJson.scripts.build ?? null,
|
|
60
|
-
lintCommand: packageJson.scripts.lint ?? null,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
async function detectCiPlatform(cwd) {
|
|
64
|
-
const workflowsDir = join(cwd, ".github", "workflows");
|
|
65
|
-
const exists = await fileExists(workflowsDir);
|
|
66
|
-
return exists ? "github-actions" : null;
|
|
67
|
-
}
|
|
68
|
-
async function detectMonorepo(cwd) {
|
|
69
|
-
// Check for pnpm-workspace.yaml
|
|
70
|
-
const hasPnpmWorkspace = await fileExists(join(cwd, "pnpm-workspace.yaml"));
|
|
71
|
-
if (hasPnpmWorkspace) {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
// Check for lerna.json
|
|
75
|
-
const hasLerna = await fileExists(join(cwd, "lerna.json"));
|
|
76
|
-
if (hasLerna) {
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
// Check for workspaces field in package.json
|
|
80
|
-
const packageJson = await readJsonFile(join(cwd, "package.json"));
|
|
81
|
-
if (packageJson?.workspaces) {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
async function detectExistingSkills(cwd) {
|
|
87
|
-
const skills = [];
|
|
88
|
-
// Check .claude/skills/
|
|
89
|
-
const claudeSkillsDir = join(cwd, ".claude", "skills");
|
|
90
|
-
try {
|
|
91
|
-
const { readdirSync } = await import("node:fs");
|
|
92
|
-
const entries = readdirSync(claudeSkillsDir, { withFileTypes: true });
|
|
93
|
-
for (const entry of entries) {
|
|
94
|
-
if (entry.isDirectory()) {
|
|
95
|
-
skills.push(entry.name);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
if (!isFileMissing(error)) {
|
|
101
|
-
throw error;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// Check .codex/skills/
|
|
105
|
-
const codexSkillsDir = join(cwd, ".codex", "skills");
|
|
106
|
-
try {
|
|
107
|
-
const { readdirSync } = await import("node:fs");
|
|
108
|
-
const entries = readdirSync(codexSkillsDir, { withFileTypes: true });
|
|
109
|
-
for (const entry of entries) {
|
|
110
|
-
if (entry.isDirectory()) {
|
|
111
|
-
skills.push(entry.name);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
if (!isFileMissing(error)) {
|
|
117
|
-
throw error;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return skills;
|
|
121
|
-
}
|
|
122
|
-
export async function detectEnvironment(cwd) {
|
|
123
|
-
const [{ packageManager, lockfile }, { testCommand, buildCommand, lintCommand }, ciPlatform, monorepo, existingSkills,] = await Promise.all([
|
|
124
|
-
detectPackageManager(cwd),
|
|
125
|
-
detectScripts(cwd),
|
|
126
|
-
detectCiPlatform(cwd),
|
|
127
|
-
detectMonorepo(cwd),
|
|
128
|
-
detectExistingSkills(cwd),
|
|
129
|
-
]);
|
|
130
|
-
return {
|
|
131
|
-
packageManager,
|
|
132
|
-
lockfile,
|
|
133
|
-
testCommand,
|
|
134
|
-
buildCommand,
|
|
135
|
-
lintCommand,
|
|
136
|
-
ciPlatform,
|
|
137
|
-
monorepo,
|
|
138
|
-
existingSkills,
|
|
139
|
-
};
|
|
140
|
-
}
|