@agntk/agent-harness 0.1.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/LICENSE +21 -0
- package/NOTICE +41 -0
- package/README.md +445 -0
- package/defaults/agents/summarizer.md +49 -0
- package/defaults/instincts/lead-with-answer.md +24 -0
- package/defaults/instincts/qualify-before-recommending.md +40 -0
- package/defaults/instincts/read-before-edit.md +23 -0
- package/defaults/instincts/search-before-create.md +23 -0
- package/defaults/playbooks/ship-feature.md +31 -0
- package/defaults/rules/ask-before-assuming.md +35 -0
- package/defaults/rules/operations.md +35 -0
- package/defaults/rules/respect-the-user.md +39 -0
- package/defaults/skills/business-analyst.md +181 -0
- package/defaults/skills/content-marketer.md +184 -0
- package/defaults/skills/research.md +34 -0
- package/defaults/tools/example-web-search.md +60 -0
- package/defaults/workflows/daily-reflection.md +54 -0
- package/dist/agent-framework-K4GUIICH.js +344 -0
- package/dist/agent-framework-K4GUIICH.js.map +1 -0
- package/dist/analytics-RPT73WNM.js +12 -0
- package/dist/analytics-RPT73WNM.js.map +1 -0
- package/dist/auto-processor-OLE45UI3.js +13 -0
- package/dist/auto-processor-OLE45UI3.js.map +1 -0
- package/dist/chunk-274RV3YO.js +162 -0
- package/dist/chunk-274RV3YO.js.map +1 -0
- package/dist/chunk-4CWAGBNS.js +168 -0
- package/dist/chunk-4CWAGBNS.js.map +1 -0
- package/dist/chunk-4FDUOGSZ.js +69 -0
- package/dist/chunk-4FDUOGSZ.js.map +1 -0
- package/dist/chunk-5H34JPMB.js +199 -0
- package/dist/chunk-5H34JPMB.js.map +1 -0
- package/dist/chunk-6EMOEYGU.js +102 -0
- package/dist/chunk-6EMOEYGU.js.map +1 -0
- package/dist/chunk-A7BJPQQ6.js +236 -0
- package/dist/chunk-A7BJPQQ6.js.map +1 -0
- package/dist/chunk-AGAAFJEO.js +76 -0
- package/dist/chunk-AGAAFJEO.js.map +1 -0
- package/dist/chunk-BSKDOFRT.js +65 -0
- package/dist/chunk-BSKDOFRT.js.map +1 -0
- package/dist/chunk-CHJ5GNZC.js +100 -0
- package/dist/chunk-CHJ5GNZC.js.map +1 -0
- package/dist/chunk-CSL3ERUI.js +307 -0
- package/dist/chunk-CSL3ERUI.js.map +1 -0
- package/dist/chunk-DA7IKHC4.js +229 -0
- package/dist/chunk-DA7IKHC4.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-DTTXPHFW.js +211 -0
- package/dist/chunk-DTTXPHFW.js.map +1 -0
- package/dist/chunk-FD55B3IO.js +204 -0
- package/dist/chunk-FD55B3IO.js.map +1 -0
- package/dist/chunk-FLZU44SV.js +230 -0
- package/dist/chunk-FLZU44SV.js.map +1 -0
- package/dist/chunk-GJNNR2RA.js +200 -0
- package/dist/chunk-GJNNR2RA.js.map +1 -0
- package/dist/chunk-GNUSHD2Y.js +111 -0
- package/dist/chunk-GNUSHD2Y.js.map +1 -0
- package/dist/chunk-GUJTBGVS.js +2212 -0
- package/dist/chunk-GUJTBGVS.js.map +1 -0
- package/dist/chunk-IZ6UZ3ZL.js +207 -0
- package/dist/chunk-IZ6UZ3ZL.js.map +1 -0
- package/dist/chunk-JKMGYWXB.js +197 -0
- package/dist/chunk-JKMGYWXB.js.map +1 -0
- package/dist/chunk-KFX54TQM.js +165 -0
- package/dist/chunk-KFX54TQM.js.map +1 -0
- package/dist/chunk-M7NXUK55.js +199 -0
- package/dist/chunk-M7NXUK55.js.map +1 -0
- package/dist/chunk-MPZ3BPUI.js +374 -0
- package/dist/chunk-MPZ3BPUI.js.map +1 -0
- package/dist/chunk-OC6YSTDX.js +119 -0
- package/dist/chunk-OC6YSTDX.js.map +1 -0
- package/dist/chunk-RC6MEZB6.js +469 -0
- package/dist/chunk-RC6MEZB6.js.map +1 -0
- package/dist/chunk-RY3ZFII7.js +3440 -0
- package/dist/chunk-RY3ZFII7.js.map +1 -0
- package/dist/chunk-TAT6JU3X.js +167 -0
- package/dist/chunk-TAT6JU3X.js.map +1 -0
- package/dist/chunk-UDZIS2AQ.js +79 -0
- package/dist/chunk-UDZIS2AQ.js.map +1 -0
- package/dist/chunk-UPLBF4RZ.js +115 -0
- package/dist/chunk-UPLBF4RZ.js.map +1 -0
- package/dist/chunk-UWQTZMNI.js +154 -0
- package/dist/chunk-UWQTZMNI.js.map +1 -0
- package/dist/chunk-W4T7PGI2.js +346 -0
- package/dist/chunk-W4T7PGI2.js.map +1 -0
- package/dist/chunk-XTBKL5BI.js +111 -0
- package/dist/chunk-XTBKL5BI.js.map +1 -0
- package/dist/chunk-YIJY5DBV.js +399 -0
- package/dist/chunk-YIJY5DBV.js.map +1 -0
- package/dist/chunk-YUFNYN2H.js +242 -0
- package/dist/chunk-YUFNYN2H.js.map +1 -0
- package/dist/chunk-Z2PUCXTZ.js +94 -0
- package/dist/chunk-Z2PUCXTZ.js.map +1 -0
- package/dist/chunk-ZZJOFKAT.js +13 -0
- package/dist/chunk-ZZJOFKAT.js.map +1 -0
- package/dist/cli/index.js +3661 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config-WVMRUOCA.js +13 -0
- package/dist/config-WVMRUOCA.js.map +1 -0
- package/dist/context-loader-3ORBPMHJ.js +13 -0
- package/dist/context-loader-3ORBPMHJ.js.map +1 -0
- package/dist/conversation-QDEIDQPH.js +22 -0
- package/dist/conversation-QDEIDQPH.js.map +1 -0
- package/dist/cost-tracker-RS3W7SVY.js +24 -0
- package/dist/cost-tracker-RS3W7SVY.js.map +1 -0
- package/dist/delegate-VJCJLYEK.js +29 -0
- package/dist/delegate-VJCJLYEK.js.map +1 -0
- package/dist/emotional-state-VQVRA6ED.js +206 -0
- package/dist/emotional-state-VQVRA6ED.js.map +1 -0
- package/dist/env-discovery-2BLVMAIM.js +251 -0
- package/dist/env-discovery-2BLVMAIM.js.map +1 -0
- package/dist/export-6GCYHEHQ.js +165 -0
- package/dist/export-6GCYHEHQ.js.map +1 -0
- package/dist/graph-YUIPOSOO.js +14 -0
- package/dist/graph-YUIPOSOO.js.map +1 -0
- package/dist/harness-LCHA3DWP.js +10 -0
- package/dist/harness-LCHA3DWP.js.map +1 -0
- package/dist/harness-WE4SLCML.js +26 -0
- package/dist/harness-WE4SLCML.js.map +1 -0
- package/dist/health-NZ6WNIMV.js +23 -0
- package/dist/health-NZ6WNIMV.js.map +1 -0
- package/dist/index.d.ts +3612 -0
- package/dist/index.js +13501 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer-LONANRRM.js +16 -0
- package/dist/indexer-LONANRRM.js.map +1 -0
- package/dist/instinct-learner-SRM72DHF.js +20 -0
- package/dist/instinct-learner-SRM72DHF.js.map +1 -0
- package/dist/intake-4M3HNU43.js +21 -0
- package/dist/intake-4M3HNU43.js.map +1 -0
- package/dist/intelligence-HJOCA4SJ.js +1081 -0
- package/dist/intelligence-HJOCA4SJ.js.map +1 -0
- package/dist/journal-WANJL3MI.js +24 -0
- package/dist/journal-WANJL3MI.js.map +1 -0
- package/dist/loader-C3TKIKZR.js +23 -0
- package/dist/loader-C3TKIKZR.js.map +1 -0
- package/dist/mcp-WTQJJZAO.js +15 -0
- package/dist/mcp-WTQJJZAO.js.map +1 -0
- package/dist/mcp-discovery-WPAQFL6S.js +377 -0
- package/dist/mcp-discovery-WPAQFL6S.js.map +1 -0
- package/dist/mcp-installer-6O2XXD3V.js +394 -0
- package/dist/mcp-installer-6O2XXD3V.js.map +1 -0
- package/dist/metrics-KXGNFAAB.js +20 -0
- package/dist/metrics-KXGNFAAB.js.map +1 -0
- package/dist/primitive-registry-I6VTIR4W.js +512 -0
- package/dist/primitive-registry-I6VTIR4W.js.map +1 -0
- package/dist/project-discovery-C4UMD7JI.js +246 -0
- package/dist/project-discovery-C4UMD7JI.js.map +1 -0
- package/dist/provider-LQHQX7Z7.js +26 -0
- package/dist/provider-LQHQX7Z7.js.map +1 -0
- package/dist/provider-SXPQZ74H.js +28 -0
- package/dist/provider-SXPQZ74H.js.map +1 -0
- package/dist/rate-limiter-RLRVM325.js +22 -0
- package/dist/rate-limiter-RLRVM325.js.map +1 -0
- package/dist/rule-engine-YGQ3RYZM.js +182 -0
- package/dist/rule-engine-YGQ3RYZM.js.map +1 -0
- package/dist/scaffold-A3VRRCBV.js +347 -0
- package/dist/scaffold-A3VRRCBV.js.map +1 -0
- package/dist/scheduler-XHHIVHRI.js +397 -0
- package/dist/scheduler-XHHIVHRI.js.map +1 -0
- package/dist/search-V3W5JMJG.js +75 -0
- package/dist/search-V3W5JMJG.js.map +1 -0
- package/dist/semantic-search-2DTOO5UX.js +241 -0
- package/dist/semantic-search-2DTOO5UX.js.map +1 -0
- package/dist/serve-DTQ3HENY.js +291 -0
- package/dist/serve-DTQ3HENY.js.map +1 -0
- package/dist/sessions-CZGVXKQE.js +21 -0
- package/dist/sessions-CZGVXKQE.js.map +1 -0
- package/dist/sources-RW5DT56F.js +32 -0
- package/dist/sources-RW5DT56F.js.map +1 -0
- package/dist/starter-packs-76YUVHEU.js +893 -0
- package/dist/starter-packs-76YUVHEU.js.map +1 -0
- package/dist/state-GMXILIHW.js +13 -0
- package/dist/state-GMXILIHW.js.map +1 -0
- package/dist/state-merge-NKO5FRBA.js +174 -0
- package/dist/state-merge-NKO5FRBA.js.map +1 -0
- package/dist/telemetry-UC6PBXC7.js +22 -0
- package/dist/telemetry-UC6PBXC7.js.map +1 -0
- package/dist/tool-executor-MJ7IG7PQ.js +28 -0
- package/dist/tool-executor-MJ7IG7PQ.js.map +1 -0
- package/dist/tools-DZ4KETET.js +20 -0
- package/dist/tools-DZ4KETET.js.map +1 -0
- package/dist/types-EW7AIB3R.js +18 -0
- package/dist/types-EW7AIB3R.js.map +1 -0
- package/dist/types-WGDLSPO6.js +16 -0
- package/dist/types-WGDLSPO6.js.map +1 -0
- package/dist/universal-installer-QGS4SJGX.js +578 -0
- package/dist/universal-installer-QGS4SJGX.js.map +1 -0
- package/dist/validator-7WXMDIHH.js +22 -0
- package/dist/validator-7WXMDIHH.js.map +1 -0
- package/dist/verification-gate-FYXUX6LH.js +246 -0
- package/dist/verification-gate-FYXUX6LH.js.map +1 -0
- package/dist/versioning-Z3XNE2Q2.js +271 -0
- package/dist/versioning-Z3XNE2Q2.js.map +1 -0
- package/dist/watcher-ISJC7YKL.js +109 -0
- package/dist/watcher-ISJC7YKL.js.map +1 -0
- package/dist/web-server-DD7ZOP46.js +28 -0
- package/dist/web-server-DD7ZOP46.js.map +1 -0
- package/package.json +76 -0
- package/sources.yaml +121 -0
- package/templates/assistant/CORE.md +24 -0
- package/templates/assistant/SYSTEM.md +24 -0
- package/templates/assistant/config.yaml +51 -0
- package/templates/base/CORE.md +17 -0
- package/templates/base/SYSTEM.md +24 -0
- package/templates/base/config.yaml +51 -0
- package/templates/claude-opus/config.yaml +51 -0
- package/templates/code-reviewer/CORE.md +25 -0
- package/templates/code-reviewer/SYSTEM.md +30 -0
- package/templates/code-reviewer/config.yaml +51 -0
- package/templates/gpt4/config.yaml +51 -0
- package/templates/local/config.yaml +51 -0
|
@@ -0,0 +1,3661 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
setGlobalLogLevel
|
|
5
|
+
} from "../chunk-BSKDOFRT.js";
|
|
6
|
+
import "../chunk-ZZJOFKAT.js";
|
|
7
|
+
|
|
8
|
+
// src/cli/index.ts
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
import { resolve, join, basename } from "path";
|
|
11
|
+
import { existsSync } from "fs";
|
|
12
|
+
import { config as loadDotenv } from "dotenv";
|
|
13
|
+
var __defaultWarningHandlers = process.listeners("warning");
|
|
14
|
+
process.removeAllListeners("warning");
|
|
15
|
+
process.on("warning", (warning) => {
|
|
16
|
+
if (warning.name === "ExperimentalWarning" && /fetch/i.test(warning.message)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (const handler of __defaultWarningHandlers) {
|
|
20
|
+
handler(warning);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
var __dotenvQuiet = process.env.HARNESS_VERBOSE !== "1";
|
|
24
|
+
loadDotenv({ quiet: __dotenvQuiet });
|
|
25
|
+
loadDotenv({ path: resolve(".env.local"), quiet: __dotenvQuiet });
|
|
26
|
+
var program = new Command();
|
|
27
|
+
var MODEL_ALIASES = {
|
|
28
|
+
"gemma": "google/gemma-4-26b-a4b-it",
|
|
29
|
+
"gemma-31b": "google/gemma-4-31b-it",
|
|
30
|
+
"qwen": "qwen/qwen3.5-35b-a3b",
|
|
31
|
+
"glm": "z-ai/glm-4.7-flash",
|
|
32
|
+
"claude": "anthropic/claude-sonnet-4",
|
|
33
|
+
"gpt4o": "openai/gpt-4o",
|
|
34
|
+
"gpt4o-mini": "openai/gpt-4o-mini"
|
|
35
|
+
};
|
|
36
|
+
function resolveModel(model) {
|
|
37
|
+
if (!model) return void 0;
|
|
38
|
+
return MODEL_ALIASES[model] || model;
|
|
39
|
+
}
|
|
40
|
+
function loadEnvFromDir(dir) {
|
|
41
|
+
const envPath = join(dir, ".env");
|
|
42
|
+
if (existsSync(envPath)) {
|
|
43
|
+
loadDotenv({ path: envPath });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function formatError(err) {
|
|
47
|
+
if (!err) return "Unknown error";
|
|
48
|
+
if (typeof err === "string") return err;
|
|
49
|
+
const e = err;
|
|
50
|
+
if (e.data && typeof e.data === "object") {
|
|
51
|
+
const data = e.data;
|
|
52
|
+
if (data.error && typeof data.error === "object") {
|
|
53
|
+
const apiErr = data.error;
|
|
54
|
+
if (typeof apiErr.message === "string") return apiErr.message;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const message = e.message;
|
|
58
|
+
if (typeof message !== "string") return String(err);
|
|
59
|
+
if (message.includes("API key") || message.includes("OPENROUTER_API_KEY"))
|
|
60
|
+
return message;
|
|
61
|
+
if (message.includes("not a valid model") || message.includes("model not found"))
|
|
62
|
+
return `Invalid model: ${message}`;
|
|
63
|
+
if (message.includes("ECONNREFUSED") || message.includes("ENOTFOUND"))
|
|
64
|
+
return `Network error: Could not reach API. Check your internet connection.`;
|
|
65
|
+
if (message.includes("ETIMEDOUT"))
|
|
66
|
+
return `Request timed out. The API may be overloaded \u2014 try again.`;
|
|
67
|
+
if (message.includes("429") || message.includes("rate limit"))
|
|
68
|
+
return `Rate limited by API. Wait a moment and try again.`;
|
|
69
|
+
if (message.includes("Invalid config"))
|
|
70
|
+
return message;
|
|
71
|
+
if (message.includes("Expected") && message.includes("received"))
|
|
72
|
+
return `Validation error: ${message}`;
|
|
73
|
+
if (message.includes("ENOENT"))
|
|
74
|
+
return `File not found: ${message.replace(/.*ENOENT[^']*'([^']+)'.*/, "$1")}`;
|
|
75
|
+
if (message.includes("EACCES"))
|
|
76
|
+
return `Permission denied: ${message.replace(/.*EACCES[^']*'([^']+)'.*/, "$1")}`;
|
|
77
|
+
return message;
|
|
78
|
+
}
|
|
79
|
+
function requireHarness(dir) {
|
|
80
|
+
if (!existsSync(join(dir, "CORE.md")) && !existsSync(join(dir, "config.yaml"))) {
|
|
81
|
+
console.error(`Error: No harness found in ${dir}`);
|
|
82
|
+
console.error(`Run "harness init <name>" to create one.`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
program.name("harness").description("Agent Harness \u2014 build AI agents by editing files, not writing code.").version("0.1.0").option("-q, --quiet", "Suppress non-error output").option("-v, --verbose", "Enable debug output").option("--log-level <level>", "Set log level (debug, info, warn, error, silent)").hook("preAction", () => {
|
|
87
|
+
const opts = program.opts();
|
|
88
|
+
if (opts.quiet) setGlobalLogLevel("error");
|
|
89
|
+
else if (opts.verbose) setGlobalLogLevel("debug");
|
|
90
|
+
else if (opts.logLevel) setGlobalLogLevel(opts.logLevel);
|
|
91
|
+
});
|
|
92
|
+
function askQuestion(rl, question, defaultValue) {
|
|
93
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
94
|
+
return new Promise((resolve2) => {
|
|
95
|
+
rl.question(`${question}${suffix}: `, (answer) => {
|
|
96
|
+
resolve2(answer.trim() || defaultValue || "");
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
program.command("init [name]").description("Scaffold a new agent harness directory (interactive if no name given)").option("-d, --dir <path>", "Parent directory", ".").option("-t, --template <name>", "Config template (base, claude-opus, gpt4, local)", "base").option("-p, --purpose <description>", "Agent purpose description").option("-i, --interactive", "Force interactive mode", false).option("--generate", "Generate CORE.md using LLM (requires API key)", false).option("--no-discover-mcp", "Skip MCP server auto-discovery").option("--no-discover-env", "Skip environment variable scanning").option("--no-discover-project", "Skip project context detection").option("-y, --yes", "Accept defaults for all prompts (skip MCP confirmation)", false).action(async (name, opts) => {
|
|
101
|
+
const { scaffoldHarness, generateCoreMd, listTemplates } = await import("../scaffold-A3VRRCBV.js");
|
|
102
|
+
const isInteractive = !name || opts.interactive;
|
|
103
|
+
let agentName = name ?? "";
|
|
104
|
+
let purpose = opts.purpose ?? "";
|
|
105
|
+
let template = opts.template;
|
|
106
|
+
let shouldGenerate = opts.generate;
|
|
107
|
+
if (isInteractive && process.stdin.isTTY) {
|
|
108
|
+
const readline = await import("readline");
|
|
109
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
110
|
+
try {
|
|
111
|
+
console.log("\n Agent Harness Setup\n");
|
|
112
|
+
if (!agentName) {
|
|
113
|
+
agentName = await askQuestion(rl, " Agent name", "my-agent");
|
|
114
|
+
}
|
|
115
|
+
if (!purpose) {
|
|
116
|
+
purpose = await askQuestion(rl, " What does this agent do? (purpose)");
|
|
117
|
+
}
|
|
118
|
+
const templates = listTemplates();
|
|
119
|
+
if (templates.length > 1) {
|
|
120
|
+
console.log(` Available templates: ${templates.join(", ")}`);
|
|
121
|
+
template = await askQuestion(rl, " Template", template);
|
|
122
|
+
}
|
|
123
|
+
if (purpose && !shouldGenerate) {
|
|
124
|
+
const hasKey = !!(process.env.OPENROUTER_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
|
|
125
|
+
if (hasKey) {
|
|
126
|
+
const gen = await askQuestion(rl, " Generate CORE.md using AI? (y/n)", "y");
|
|
127
|
+
shouldGenerate = gen.toLowerCase() === "y" || gen.toLowerCase() === "yes";
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
console.log();
|
|
131
|
+
} finally {
|
|
132
|
+
rl.close();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!agentName) {
|
|
136
|
+
console.error("Error: agent name is required. Usage: harness init <name>");
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
const looksLikePath = agentName.includes("/") || agentName.startsWith(".");
|
|
140
|
+
const targetDir = looksLikePath ? resolve(agentName) : resolve(opts.dir, agentName);
|
|
141
|
+
const parentDir = looksLikePath ? resolve(targetDir, "..") : resolve(opts.dir);
|
|
142
|
+
if (looksLikePath) {
|
|
143
|
+
agentName = basename(targetDir);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
let coreContent;
|
|
147
|
+
if (shouldGenerate && purpose) {
|
|
148
|
+
console.log("Generating CORE.md...");
|
|
149
|
+
try {
|
|
150
|
+
coreContent = await generateCoreMd(agentName, purpose, {});
|
|
151
|
+
console.log("\u2713 CORE.md generated via LLM");
|
|
152
|
+
} catch (err) {
|
|
153
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
154
|
+
console.log(` LLM generation failed: ${message}`);
|
|
155
|
+
console.log(" Using template instead");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
scaffoldHarness(targetDir, agentName, { template, purpose: purpose || void 0, coreContent });
|
|
159
|
+
console.log(`
|
|
160
|
+
\u2713 Agent harness created: ${targetDir}`);
|
|
161
|
+
if (opts.discoverMcp !== false) {
|
|
162
|
+
const { discoverMcpServers, discoveredServersToYaml, filterUnsafeServers } = await import("../mcp-discovery-WPAQFL6S.js");
|
|
163
|
+
const discovery = discoverMcpServers();
|
|
164
|
+
const safeServers = filterUnsafeServers(discovery.servers);
|
|
165
|
+
if (safeServers.length > 0) {
|
|
166
|
+
console.log(`
|
|
167
|
+
\u2713 Discovered ${safeServers.length} MCP server(s) from existing tools:`);
|
|
168
|
+
const safeNames = new Set(safeServers.map((s) => s.name));
|
|
169
|
+
for (const source of discovery.sources) {
|
|
170
|
+
const kept = source.servers.filter((s) => safeNames.has(s.name));
|
|
171
|
+
if (kept.length > 0) {
|
|
172
|
+
console.log(` ${source.tool}: ${kept.map((s) => s.name).join(", ")}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
let shouldAdd = true;
|
|
176
|
+
if (!opts.yes && process.stdin.isTTY) {
|
|
177
|
+
const readline = await import("readline");
|
|
178
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
179
|
+
try {
|
|
180
|
+
const answer = await new Promise((res) => {
|
|
181
|
+
rl.question(`
|
|
182
|
+
Add these ${safeServers.length} server(s) to config.yaml? [Y/n] `, (a) => res(a.trim()));
|
|
183
|
+
});
|
|
184
|
+
shouldAdd = answer === "" || answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
185
|
+
} finally {
|
|
186
|
+
rl.close();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (shouldAdd) {
|
|
190
|
+
const { appendFileSync } = await import("fs");
|
|
191
|
+
const configPath = resolve(targetDir, "config.yaml");
|
|
192
|
+
const yaml = discoveredServersToYaml(safeServers);
|
|
193
|
+
appendFileSync(configPath, "\n" + yaml + "\n");
|
|
194
|
+
console.log(` \u2192 Added to config.yaml`);
|
|
195
|
+
console.log(` \u2192 Run 'harness mcp test' to verify connections`);
|
|
196
|
+
} else {
|
|
197
|
+
console.log(` \u2192 Skipped MCP discovery. Add servers manually with 'harness mcp install <name>'.`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (opts.discoverEnv !== false) {
|
|
202
|
+
const { discoverEnvKeys } = await import("../env-discovery-2BLVMAIM.js");
|
|
203
|
+
const envResult = discoverEnvKeys({ dir: parentDir, extraDirs: [targetDir] });
|
|
204
|
+
if (envResult.suggestions.length > 0) {
|
|
205
|
+
console.log(`
|
|
206
|
+
\u2713 Detected ${envResult.keys.length} API key(s) in environment:`);
|
|
207
|
+
for (const suggestion of envResult.suggestions) {
|
|
208
|
+
console.log(` ${suggestion.triggeredBy} \u2192 ${suggestion.message}`);
|
|
209
|
+
console.log(` Install: harness mcp install "${suggestion.serverQuery}" -d ${name}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (opts.discoverProject !== false) {
|
|
214
|
+
const { discoverProjectContext } = await import("../project-discovery-C4UMD7JI.js");
|
|
215
|
+
const projectResult = discoverProjectContext({ dir: parentDir });
|
|
216
|
+
if (projectResult.signals.length > 0) {
|
|
217
|
+
const stack = projectResult.signals.map((s) => s.name).join(", ");
|
|
218
|
+
console.log(`
|
|
219
|
+
\u2713 Detected project stack: ${stack}`);
|
|
220
|
+
if (projectResult.suggestions.length > 0) {
|
|
221
|
+
console.log(` Suggestions:`);
|
|
222
|
+
for (const suggestion of projectResult.suggestions) {
|
|
223
|
+
if (suggestion.type === "mcp-server") {
|
|
224
|
+
console.log(` Install MCP: harness mcp install "${suggestion.target}" -d ${name}`);
|
|
225
|
+
} else {
|
|
226
|
+
console.log(` Create ${suggestion.type}: ${suggestion.target}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
console.log(`
|
|
233
|
+
Next steps \u2014 try these in order:`);
|
|
234
|
+
console.log(` cd ${targetDir}`);
|
|
235
|
+
console.log(` harness run "What can you do?" # see what's loaded`);
|
|
236
|
+
console.log(` harness run "Help me decide between two options: A or B"`);
|
|
237
|
+
console.log(` harness run "Plan a weekend project for me" # see it qualify`);
|
|
238
|
+
console.log(` harness journal # see what it learned`);
|
|
239
|
+
console.log(` harness learn --install # teach it to remember`);
|
|
240
|
+
console.log(`
|
|
241
|
+
The agent gets better the more you use it. See README.md inside`);
|
|
242
|
+
console.log(`the harness directory for the full walkthrough.`);
|
|
243
|
+
console.log();
|
|
244
|
+
} catch (err) {
|
|
245
|
+
console.error(`Error: ${formatError(err)}`);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
program.command("run <prompt>").description("Run a prompt through the agent").option("-d, --dir <path>", "Harness directory", ".").option("-s, --stream", "Stream output", false).option("-m, --model <model>", "Model override (or alias: gemma, qwen, glm, claude)").option("-p, --provider <provider>", "Provider override (openrouter, anthropic, openai)").option("-k, --api-key <key>", "API key override (default: from environment)").action(async (prompt, opts) => {
|
|
250
|
+
const { createHarness } = await import("../harness-WE4SLCML.js");
|
|
251
|
+
const dir = resolve(opts.dir);
|
|
252
|
+
loadEnvFromDir(dir);
|
|
253
|
+
requireHarness(dir);
|
|
254
|
+
const modelId = resolveModel(opts.model);
|
|
255
|
+
try {
|
|
256
|
+
const agent = createHarness({
|
|
257
|
+
dir,
|
|
258
|
+
model: modelId,
|
|
259
|
+
provider: opts.provider,
|
|
260
|
+
apiKey: opts.apiKey
|
|
261
|
+
});
|
|
262
|
+
if (opts.stream) {
|
|
263
|
+
const streamResult = agent.stream(prompt);
|
|
264
|
+
process.stdout.write("\n");
|
|
265
|
+
for await (const chunk of streamResult.textStream) {
|
|
266
|
+
process.stdout.write(chunk);
|
|
267
|
+
}
|
|
268
|
+
process.stdout.write("\n\n");
|
|
269
|
+
const result = await streamResult.result;
|
|
270
|
+
const toolInfo = result.toolCalls.length > 0 ? ` | ${result.toolCalls.length} tool call(s)` : "";
|
|
271
|
+
const stepInfo = result.steps > 1 ? ` | ${result.steps} steps` : "";
|
|
272
|
+
console.error(
|
|
273
|
+
`[${result.usage.totalTokens} tokens${stepInfo}${toolInfo} | session: ${result.session_id}]`
|
|
274
|
+
);
|
|
275
|
+
} else {
|
|
276
|
+
const result = await agent.run(prompt);
|
|
277
|
+
console.log("\n" + result.text + "\n");
|
|
278
|
+
const toolInfo = result.toolCalls.length > 0 ? ` | ${result.toolCalls.length} tool call(s)` : "";
|
|
279
|
+
const stepInfo = result.steps > 1 ? ` | ${result.steps} steps` : "";
|
|
280
|
+
console.error(
|
|
281
|
+
`[${result.usage.totalTokens} tokens${stepInfo}${toolInfo} | session: ${result.session_id}]`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
await agent.shutdown();
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.error(`Error: ${formatError(err)}`);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
program.command("chat").description("Start an interactive chat session with conversation memory").option("-d, --dir <path>", "Harness directory", ".").option("-m, --model <model>", "Model override").option("-p, --provider <provider>", "Provider override (openrouter, anthropic, openai)").option("-k, --api-key <key>", "API key override (default: from environment)").option("--fresh", "Start fresh (clear conversation history)", false).action(async (opts) => {
|
|
291
|
+
const { Conversation } = await import("../conversation-QDEIDQPH.js");
|
|
292
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
293
|
+
const { buildToolSet } = await import("../tool-executor-MJ7IG7PQ.js");
|
|
294
|
+
const { createMcpManager } = await import("../mcp-WTQJJZAO.js");
|
|
295
|
+
const readline = await import("readline");
|
|
296
|
+
const dir = resolve(opts.dir);
|
|
297
|
+
requireHarness(dir);
|
|
298
|
+
const config = loadConfig(dir);
|
|
299
|
+
let mcpTools = {};
|
|
300
|
+
const mcpManager = createMcpManager(config);
|
|
301
|
+
if (mcpManager.hasServers()) {
|
|
302
|
+
try {
|
|
303
|
+
await mcpManager.connect();
|
|
304
|
+
mcpTools = mcpManager.getTools();
|
|
305
|
+
} catch (err) {
|
|
306
|
+
console.error(`Warning: MCP connection failed: ${formatError(err)}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const toolSet = buildToolSet(dir, void 0, mcpTools);
|
|
310
|
+
const toolCount = Object.keys(toolSet).length;
|
|
311
|
+
const conv = new Conversation(dir, opts.apiKey, { tools: toolSet });
|
|
312
|
+
const modelId = resolveModel(opts.model);
|
|
313
|
+
if (modelId) conv.setModelOverride(modelId);
|
|
314
|
+
if (opts.provider) conv.setProviderOverride(opts.provider);
|
|
315
|
+
if (opts.fresh) conv.clear();
|
|
316
|
+
await conv.init();
|
|
317
|
+
const history = conv.getHistory();
|
|
318
|
+
console.log(`
|
|
319
|
+
${config.agent.name} is ready. ${history.length > 0 ? `(${history.length} messages in history)` : ""}${toolCount > 0 ? ` | ${toolCount} tools` : ""}`);
|
|
320
|
+
console.log(`Type your message, "clear" to reset, or "exit" to quit.
|
|
321
|
+
`);
|
|
322
|
+
const rl = readline.createInterface({
|
|
323
|
+
input: process.stdin,
|
|
324
|
+
output: process.stdout
|
|
325
|
+
});
|
|
326
|
+
let closed = false;
|
|
327
|
+
let sending = false;
|
|
328
|
+
let pendingClose = false;
|
|
329
|
+
const cleanup = async () => {
|
|
330
|
+
if (mcpManager.hasServers()) {
|
|
331
|
+
await mcpManager.close();
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
const doClose = async () => {
|
|
335
|
+
if (closed) return;
|
|
336
|
+
closed = true;
|
|
337
|
+
await cleanup();
|
|
338
|
+
};
|
|
339
|
+
rl.on("close", async () => {
|
|
340
|
+
if (sending) {
|
|
341
|
+
pendingClose = true;
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
await doClose();
|
|
345
|
+
});
|
|
346
|
+
const ask = () => {
|
|
347
|
+
if (closed) return;
|
|
348
|
+
rl.question("> ", async (input) => {
|
|
349
|
+
if (closed) return;
|
|
350
|
+
const trimmed = input.trim();
|
|
351
|
+
if (!trimmed || trimmed === "exit" || trimmed === "quit") {
|
|
352
|
+
await doClose();
|
|
353
|
+
rl.close();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (trimmed === "clear") {
|
|
357
|
+
conv.clear();
|
|
358
|
+
console.log("[conversation cleared]\n");
|
|
359
|
+
ask();
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
sending = true;
|
|
363
|
+
try {
|
|
364
|
+
const streamResult = conv.sendStream(trimmed);
|
|
365
|
+
process.stdout.write("\n");
|
|
366
|
+
for await (const chunk of streamResult.textStream) {
|
|
367
|
+
process.stdout.write(chunk);
|
|
368
|
+
}
|
|
369
|
+
process.stdout.write("\n");
|
|
370
|
+
const meta = await streamResult.result;
|
|
371
|
+
if (meta.usage.totalTokens > 0) {
|
|
372
|
+
const toolInfo = meta.toolCalls.length > 0 ? ` | ${meta.toolCalls.length} tool call(s)` : "";
|
|
373
|
+
const stepInfo = meta.steps > 1 ? ` | ${meta.steps} steps` : "";
|
|
374
|
+
console.error(`[${meta.usage.totalTokens} tokens${stepInfo}${toolInfo}]`);
|
|
375
|
+
}
|
|
376
|
+
process.stdout.write("\n");
|
|
377
|
+
} catch (err) {
|
|
378
|
+
console.error(`Error: ${formatError(err)}`);
|
|
379
|
+
} finally {
|
|
380
|
+
sending = false;
|
|
381
|
+
if (pendingClose) {
|
|
382
|
+
await doClose();
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
ask();
|
|
387
|
+
});
|
|
388
|
+
};
|
|
389
|
+
ask();
|
|
390
|
+
});
|
|
391
|
+
program.command("info").description("Show harness info and loaded context").option("-d, --dir <path>", "Harness directory", ".").option("--json", "Output as JSON").action(async (opts) => {
|
|
392
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
393
|
+
const { buildSystemPrompt } = await import("../context-loader-3ORBPMHJ.js");
|
|
394
|
+
const { loadState } = await import("../state-GMXILIHW.js");
|
|
395
|
+
const dir = resolve(opts.dir);
|
|
396
|
+
requireHarness(dir);
|
|
397
|
+
try {
|
|
398
|
+
const config = loadConfig(dir);
|
|
399
|
+
const ctx = buildSystemPrompt(dir, config);
|
|
400
|
+
const state = loadState(dir);
|
|
401
|
+
const mcpServers = config.mcp?.servers ?? {};
|
|
402
|
+
const mcpEntries = Object.entries(mcpServers);
|
|
403
|
+
if (opts.json) {
|
|
404
|
+
const info = {
|
|
405
|
+
agent: { name: config.agent.name, version: config.agent.version },
|
|
406
|
+
model: config.model.id,
|
|
407
|
+
provider: config.model.provider,
|
|
408
|
+
state: { mode: state.mode, last_interaction: state.last_interaction },
|
|
409
|
+
context: {
|
|
410
|
+
max_tokens: ctx.budget.max_tokens,
|
|
411
|
+
used_tokens: ctx.budget.used_tokens,
|
|
412
|
+
remaining: ctx.budget.remaining,
|
|
413
|
+
loaded_files: ctx.budget.loaded_files
|
|
414
|
+
},
|
|
415
|
+
mcp: mcpEntries.map(([name, s]) => ({
|
|
416
|
+
name,
|
|
417
|
+
transport: s.transport,
|
|
418
|
+
enabled: s.enabled !== false
|
|
419
|
+
}))
|
|
420
|
+
};
|
|
421
|
+
console.log(JSON.stringify(info, null, 2));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
console.log(`
|
|
425
|
+
Agent: ${config.agent.name} v${config.agent.version}`);
|
|
426
|
+
console.log(`Model: ${config.model.id}`);
|
|
427
|
+
console.log(`State: ${state.mode}`);
|
|
428
|
+
console.log(`Last interaction: ${state.last_interaction}`);
|
|
429
|
+
console.log(`
|
|
430
|
+
Context budget:`);
|
|
431
|
+
console.log(` Max tokens: ${ctx.budget.max_tokens}`);
|
|
432
|
+
console.log(` Used: ~${ctx.budget.used_tokens}`);
|
|
433
|
+
console.log(` Remaining: ~${ctx.budget.remaining}`);
|
|
434
|
+
console.log(` Files loaded: ${ctx.budget.loaded_files.length}`);
|
|
435
|
+
ctx.budget.loaded_files.forEach((f) => console.log(` - ${f}`));
|
|
436
|
+
if (mcpEntries.length > 0) {
|
|
437
|
+
const enabledCount = mcpEntries.filter(([, s]) => s.enabled !== false).length;
|
|
438
|
+
console.log(`
|
|
439
|
+
MCP servers: ${mcpEntries.length} configured (${enabledCount} enabled)`);
|
|
440
|
+
for (const [name, s] of mcpEntries) {
|
|
441
|
+
const enabled = s.enabled !== false;
|
|
442
|
+
console.log(` ${enabled ? "+" : "-"} ${name} (${s.transport})`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
console.log();
|
|
446
|
+
} catch (err) {
|
|
447
|
+
console.error(`Error: ${formatError(err)}`);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
program.command("prompt").description("Show the full assembled system prompt").option("-d, --dir <path>", "Harness directory", ".").option("--json", "Output metadata as JSON (includes prompt, budget, warnings)").action(async (opts) => {
|
|
452
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
453
|
+
const { buildSystemPrompt } = await import("../context-loader-3ORBPMHJ.js");
|
|
454
|
+
const dir = resolve(opts.dir);
|
|
455
|
+
requireHarness(dir);
|
|
456
|
+
try {
|
|
457
|
+
const config = loadConfig(dir);
|
|
458
|
+
const ctx = buildSystemPrompt(dir, config);
|
|
459
|
+
if (opts.json) {
|
|
460
|
+
console.log(JSON.stringify({
|
|
461
|
+
systemPrompt: ctx.systemPrompt,
|
|
462
|
+
budget: ctx.budget,
|
|
463
|
+
warnings: ctx.warnings,
|
|
464
|
+
parseErrors: ctx.parseErrors
|
|
465
|
+
}, null, 2));
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
console.log(ctx.systemPrompt);
|
|
469
|
+
} catch (err) {
|
|
470
|
+
console.error(`Error: ${formatError(err)}`);
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
program.command("dev").description("Start dev mode \u2014 watches for file changes, rebuilds indexes, runs scheduled workflows, serves dashboard").option("-d, --dir <path>", "Harness directory", ".").option("-k, --api-key <key>", "API key override (default: from environment)").option("--no-schedule", "Disable workflow scheduler").option("--no-auto-process", "Disable auto-processing of primitives on save").option("--no-web", "Disable web dashboard server").option("-p, --port <number>", "Web dashboard port", "3000").action(async (opts) => {
|
|
475
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
476
|
+
const { rebuildAllIndexes } = await import("../indexer-LONANRRM.js");
|
|
477
|
+
const { createWatcher } = await import("../watcher-ISJC7YKL.js");
|
|
478
|
+
const { Scheduler } = await import("../scheduler-XHHIVHRI.js");
|
|
479
|
+
const { autoProcessAll } = await import("../auto-processor-OLE45UI3.js");
|
|
480
|
+
const { generateSystemMd } = await import("../scaffold-A3VRRCBV.js");
|
|
481
|
+
const { writeFileSync } = await import("fs");
|
|
482
|
+
const dir = resolve(opts.dir);
|
|
483
|
+
loadEnvFromDir(dir);
|
|
484
|
+
requireHarness(dir);
|
|
485
|
+
const config = loadConfig(dir);
|
|
486
|
+
const doAutoProcess = opts.autoProcess && config.runtime?.auto_process !== false;
|
|
487
|
+
console.log(`
|
|
488
|
+
[dev] Watching "${config.agent.name}" harness at ${dir}`);
|
|
489
|
+
if (doAutoProcess) {
|
|
490
|
+
const processed = autoProcessAll(dir);
|
|
491
|
+
if (processed.length > 0) {
|
|
492
|
+
console.log(`[dev] Auto-processed ${processed.length} file(s) on startup`);
|
|
493
|
+
for (const r of processed) {
|
|
494
|
+
const rel = r.path.replace(dir + "/", "");
|
|
495
|
+
console.log(` ${rel}: ${r.fixes.join(", ")}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const systemPath = join(dir, "SYSTEM.md");
|
|
500
|
+
const newSystem = generateSystemMd(dir, config.agent.name);
|
|
501
|
+
writeFileSync(systemPath, newSystem, "utf-8");
|
|
502
|
+
console.log(`[dev] SYSTEM.md regenerated from directory structure`);
|
|
503
|
+
const extDirs = config.extensions?.directories ?? [];
|
|
504
|
+
rebuildAllIndexes(dir, extDirs);
|
|
505
|
+
console.log(`[dev] Indexes rebuilt${extDirs.length ? ` (+ ${extDirs.length} extension dir(s))` : ""}`);
|
|
506
|
+
let scheduler = null;
|
|
507
|
+
if (opts.schedule) {
|
|
508
|
+
scheduler = new Scheduler({
|
|
509
|
+
harnessDir: dir,
|
|
510
|
+
apiKey: opts.apiKey,
|
|
511
|
+
autoJournal: config.intelligence?.auto_journal ?? false,
|
|
512
|
+
autoLearn: config.intelligence?.auto_learn ?? false,
|
|
513
|
+
onRun: (id, result) => {
|
|
514
|
+
console.log(`[scheduler] \u2713 ${id}: ${result.slice(0, 100)}`);
|
|
515
|
+
},
|
|
516
|
+
onError: (id, error) => {
|
|
517
|
+
console.error(`[scheduler] \u2717 ${id}: ${error.message}`);
|
|
518
|
+
},
|
|
519
|
+
onSchedule: (id, cronExpr) => {
|
|
520
|
+
console.log(`[scheduler] Scheduled: ${id} (${cronExpr})`);
|
|
521
|
+
},
|
|
522
|
+
onArchival: (sessions, journals) => {
|
|
523
|
+
if (sessions + journals > 0) {
|
|
524
|
+
console.log(`[scheduler] Archived ${sessions} session(s), ${journals} journal(s)`);
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
onJournal: (date, sessionsCount) => {
|
|
528
|
+
console.log(`[scheduler] Auto-journal: synthesized ${sessionsCount} session(s) for ${date}`);
|
|
529
|
+
},
|
|
530
|
+
onLearn: (installed, skipped) => {
|
|
531
|
+
console.log(`[scheduler] Auto-learn: ${installed} instinct(s) installed, ${skipped} skipped`);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
scheduler.start();
|
|
535
|
+
const scheduled = scheduler.listScheduled();
|
|
536
|
+
const features = [];
|
|
537
|
+
if (scheduled.length > 0) features.push(`${scheduled.length} workflow(s)`);
|
|
538
|
+
if (config.intelligence?.auto_journal) features.push("auto-journal");
|
|
539
|
+
if (config.intelligence?.auto_learn) features.push("auto-learn");
|
|
540
|
+
if (features.length > 0) {
|
|
541
|
+
console.log(`[dev] Scheduler started: ${features.join(", ")}`);
|
|
542
|
+
} else {
|
|
543
|
+
console.log(`[dev] Scheduler running (no workflows or intelligence features configured)`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
let webServer = null;
|
|
547
|
+
if (opts.web) {
|
|
548
|
+
const { startWebServer } = await import("../web-server-DD7ZOP46.js");
|
|
549
|
+
const port = parseInt(opts.port, 10) || 3e3;
|
|
550
|
+
webServer = await startWebServer({
|
|
551
|
+
harnessDir: dir,
|
|
552
|
+
port,
|
|
553
|
+
apiKey: opts.apiKey,
|
|
554
|
+
onStart: (p) => console.log(`[dev] Dashboard: http://localhost:${p}`)
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
const sseBroadcast = (type, data) => {
|
|
558
|
+
webServer?.broadcaster.broadcast({ type, data, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
559
|
+
};
|
|
560
|
+
createWatcher({
|
|
561
|
+
harnessDir: dir,
|
|
562
|
+
extraDirs: extDirs,
|
|
563
|
+
watchConfig: true,
|
|
564
|
+
autoProcess: doAutoProcess,
|
|
565
|
+
onChange: (path, event) => {
|
|
566
|
+
const rel = path.replace(dir + "/", "");
|
|
567
|
+
console.log(`[dev] ${event}: ${rel}`);
|
|
568
|
+
sseBroadcast("file_change", { path: rel, event });
|
|
569
|
+
},
|
|
570
|
+
onIndexRebuild: (directory) => {
|
|
571
|
+
console.log(`[dev] Index rebuilt: ${directory}/_index.md`);
|
|
572
|
+
sseBroadcast("index_rebuild", { directory });
|
|
573
|
+
},
|
|
574
|
+
onAutoProcess: (result) => {
|
|
575
|
+
if (result.modified) {
|
|
576
|
+
const rel = result.path.replace(dir + "/", "");
|
|
577
|
+
console.log(`[dev] Auto-processed: ${rel} (${result.fixes.join(", ")})`);
|
|
578
|
+
sseBroadcast("auto_process", { path: rel, fixes: result.fixes });
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
onConfigChange: () => {
|
|
582
|
+
try {
|
|
583
|
+
const newConfig = loadConfig(dir);
|
|
584
|
+
console.log(`[dev] Config reloaded: model=${newConfig.model.id}`);
|
|
585
|
+
sseBroadcast("config_change", { model: newConfig.model.id });
|
|
586
|
+
} catch (err) {
|
|
587
|
+
console.error(`[dev] Config reload failed: ${formatError(err)}`);
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
onError: (err) => {
|
|
591
|
+
console.error(`[dev] Watcher error: ${err.message}`);
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
console.log(`[dev] Watching for changes... (Ctrl+C to stop)
|
|
595
|
+
`);
|
|
596
|
+
const cleanup = () => {
|
|
597
|
+
console.log(`
|
|
598
|
+
[dev] Shutting down...`);
|
|
599
|
+
if (scheduler) scheduler.stop();
|
|
600
|
+
if (webServer?.server && typeof webServer.server.close === "function") {
|
|
601
|
+
webServer.server.close();
|
|
602
|
+
}
|
|
603
|
+
process.exit(0);
|
|
604
|
+
};
|
|
605
|
+
process.on("SIGINT", cleanup);
|
|
606
|
+
process.on("SIGTERM", cleanup);
|
|
607
|
+
});
|
|
608
|
+
program.command("index").description("Rebuild all index files").option("-d, --dir <path>", "Harness directory", ".").action(async (opts) => {
|
|
609
|
+
const { rebuildAllIndexes } = await import("../indexer-LONANRRM.js");
|
|
610
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
611
|
+
const dir = resolve(opts.dir);
|
|
612
|
+
let extDirs = [];
|
|
613
|
+
try {
|
|
614
|
+
const config = loadConfig(dir);
|
|
615
|
+
extDirs = config.extensions?.directories ?? [];
|
|
616
|
+
} catch (err) {
|
|
617
|
+
if (process.env.DEBUG) console.error(`Config load skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
618
|
+
}
|
|
619
|
+
rebuildAllIndexes(dir, extDirs);
|
|
620
|
+
console.log(`\u2713 All indexes rebuilt in ${dir}`);
|
|
621
|
+
});
|
|
622
|
+
program.command("process").description("Auto-process all primitives: fill missing frontmatter and generate L0/L1 summaries").option("-d, --dir <path>", "Harness directory", ".").option("--no-frontmatter", "Skip frontmatter generation").option("--no-summaries", "Skip L0/L1 summary generation").action(async (opts) => {
|
|
623
|
+
const { autoProcessAll } = await import("../auto-processor-OLE45UI3.js");
|
|
624
|
+
const dir = resolve(opts.dir);
|
|
625
|
+
requireHarness(dir);
|
|
626
|
+
const results = autoProcessAll(dir, {
|
|
627
|
+
generateFrontmatter: opts.frontmatter,
|
|
628
|
+
generateSummaries: opts.summaries
|
|
629
|
+
});
|
|
630
|
+
if (results.length === 0) {
|
|
631
|
+
console.log("All primitives are up to date.");
|
|
632
|
+
} else {
|
|
633
|
+
for (const r of results) {
|
|
634
|
+
const rel = r.path.replace(dir + "/", "");
|
|
635
|
+
if (r.modified) {
|
|
636
|
+
console.log(`\u2713 ${rel}: ${r.fixes.join(", ")}`);
|
|
637
|
+
}
|
|
638
|
+
for (const err of r.errors) {
|
|
639
|
+
console.error(`\u2717 ${rel}: ${err}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const modified = results.filter((r) => r.modified).length;
|
|
643
|
+
const errors = results.filter((r) => r.errors.length > 0).length;
|
|
644
|
+
console.log(`
|
|
645
|
+
Processed ${modified} file(s)${errors > 0 ? `, ${errors} error(s)` : ""}`);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
program.command("system").description("Regenerate SYSTEM.md from current directory structure").option("-d, --dir <path>", "Harness directory", ".").action(async (opts) => {
|
|
649
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
650
|
+
const { generateSystemMd } = await import("../scaffold-A3VRRCBV.js");
|
|
651
|
+
const { writeFileSync } = await import("fs");
|
|
652
|
+
const dir = resolve(opts.dir);
|
|
653
|
+
requireHarness(dir);
|
|
654
|
+
const config = loadConfig(dir);
|
|
655
|
+
const systemPath = join(dir, "SYSTEM.md");
|
|
656
|
+
const content = generateSystemMd(dir, config.agent.name);
|
|
657
|
+
writeFileSync(systemPath, content, "utf-8");
|
|
658
|
+
console.log(`\u2713 SYSTEM.md regenerated at ${systemPath}`);
|
|
659
|
+
});
|
|
660
|
+
program.command("journal").description("Synthesize sessions into journal entries").option("-d, --dir <path>", "Harness directory", ".").option("--date <date>", "Date to synthesize (YYYY-MM-DD)").option("--from <date>", "Start of date range (YYYY-MM-DD)").option("--to <date>", "End of date range (YYYY-MM-DD, default: today)").option("--all", "Synthesize all dates with sessions", false).option("--force", "Re-synthesize even if journal exists", false).option("--pending", "Show dates with sessions but no journal", false).option("--auto-harvest", "Auto-install instinct candidates from synthesized journals", false).action(async (opts) => {
|
|
661
|
+
const dir = resolve(opts.dir);
|
|
662
|
+
loadEnvFromDir(dir);
|
|
663
|
+
requireHarness(dir);
|
|
664
|
+
if (opts.pending) {
|
|
665
|
+
const { listUnjournaled } = await import("../journal-WANJL3MI.js");
|
|
666
|
+
const dates = listUnjournaled(dir);
|
|
667
|
+
if (dates.length === 0) {
|
|
668
|
+
console.log("All sessions have been journaled.");
|
|
669
|
+
} else {
|
|
670
|
+
console.log(`
|
|
671
|
+
${dates.length} date(s) with unjournaled sessions:
|
|
672
|
+
`);
|
|
673
|
+
dates.forEach((d) => console.log(` ${d}`));
|
|
674
|
+
console.log(`
|
|
675
|
+
Run "harness journal --all" to synthesize them.
|
|
676
|
+
`);
|
|
677
|
+
}
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (opts.from || opts.all) {
|
|
681
|
+
const { synthesizeJournalRange } = await import("../journal-WANJL3MI.js");
|
|
682
|
+
try {
|
|
683
|
+
const label = opts.all ? "all dates" : `${opts.from}${opts.to ? ` to ${opts.to}` : " to today"}`;
|
|
684
|
+
console.log(`Synthesizing journals for ${label}${opts.force ? " (force)" : ""}...`);
|
|
685
|
+
const entries = await synthesizeJournalRange(dir, {
|
|
686
|
+
from: opts.from,
|
|
687
|
+
to: opts.to,
|
|
688
|
+
all: opts.all,
|
|
689
|
+
force: opts.force
|
|
690
|
+
});
|
|
691
|
+
if (entries.length === 0) {
|
|
692
|
+
console.log("No sessions to synthesize (or all dates already journaled).");
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
console.log(`
|
|
696
|
+
\u2713 ${entries.length} journal(s) synthesized:
|
|
697
|
+
`);
|
|
698
|
+
for (const entry of entries) {
|
|
699
|
+
const sessionCount = entry.sessions.length;
|
|
700
|
+
const instinctCount = entry.instinct_candidates.length;
|
|
701
|
+
console.log(` ${entry.date}: ${sessionCount} session(s), ${entry.tokens_used} tokens${instinctCount > 0 ? `, ${instinctCount} instinct candidate(s)` : ""}`);
|
|
702
|
+
}
|
|
703
|
+
console.log();
|
|
704
|
+
if (opts.autoHarvest) {
|
|
705
|
+
const { harvestInstincts } = await import("../instinct-learner-SRM72DHF.js");
|
|
706
|
+
const dates = entries.map((e) => e.date).sort();
|
|
707
|
+
const harvest = harvestInstincts(dir, {
|
|
708
|
+
from: dates[0],
|
|
709
|
+
to: dates[dates.length - 1],
|
|
710
|
+
install: true
|
|
711
|
+
});
|
|
712
|
+
if (harvest.installed.length > 0) {
|
|
713
|
+
console.log(`Auto-harvested ${harvest.installed.length} instinct(s):`);
|
|
714
|
+
harvest.installed.forEach((id) => console.log(` \u2713 ${id}`));
|
|
715
|
+
console.log();
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
} catch (err) {
|
|
719
|
+
console.error(`Error: ${formatError(err)}`);
|
|
720
|
+
process.exit(1);
|
|
721
|
+
}
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const { synthesizeJournal } = await import("../journal-WANJL3MI.js");
|
|
725
|
+
try {
|
|
726
|
+
console.log(`Synthesizing journal...`);
|
|
727
|
+
const entry = await synthesizeJournal(dir, opts.date);
|
|
728
|
+
console.log(`
|
|
729
|
+
\u2713 Journal for ${entry.date}`);
|
|
730
|
+
console.log(` Sessions: ${entry.sessions.length}`);
|
|
731
|
+
console.log(` Tokens: ${entry.tokens_used}`);
|
|
732
|
+
if (entry.instinct_candidates.length > 0) {
|
|
733
|
+
console.log(` Instinct candidates:`);
|
|
734
|
+
entry.instinct_candidates.forEach((c) => console.log(` - ${c}`));
|
|
735
|
+
if (opts.autoHarvest) {
|
|
736
|
+
const { harvestInstincts } = await import("../instinct-learner-SRM72DHF.js");
|
|
737
|
+
const harvest = harvestInstincts(dir, {
|
|
738
|
+
from: entry.date,
|
|
739
|
+
to: entry.date,
|
|
740
|
+
install: true
|
|
741
|
+
});
|
|
742
|
+
if (harvest.installed.length > 0) {
|
|
743
|
+
console.log(` Auto-harvested:`);
|
|
744
|
+
harvest.installed.forEach((id) => console.log(` \u2713 ${id}`));
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
console.log(`
|
|
749
|
+
${entry.synthesis}`);
|
|
750
|
+
} catch (err) {
|
|
751
|
+
console.error(`Error: ${formatError(err)}`);
|
|
752
|
+
process.exit(1);
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
program.command("compress").description("Compress daily journals into weekly roll-up summaries").option("-d, --dir <path>", "Harness directory", ".").option("--force", "Overwrite existing weekly summaries", false).action(async (opts) => {
|
|
756
|
+
const { compressJournals } = await import("../journal-WANJL3MI.js");
|
|
757
|
+
const dir = resolve(opts.dir);
|
|
758
|
+
requireHarness(dir);
|
|
759
|
+
const results = compressJournals(dir, { force: opts.force });
|
|
760
|
+
if (results.length === 0) {
|
|
761
|
+
console.log("\nNo complete past weeks to compress (or all already compressed).\n");
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
console.log(`
|
|
765
|
+
\u2713 ${results.length} weekly summary(ies) created:
|
|
766
|
+
`);
|
|
767
|
+
for (const week of results) {
|
|
768
|
+
const insights = week.allInsights.length;
|
|
769
|
+
const instincts = week.allInstinctCandidates.length;
|
|
770
|
+
console.log(` ${week.weekStart} to ${week.weekEnd} (${week.journalDates.length} days)`);
|
|
771
|
+
console.log(` ${insights} insight(s), ${instincts} instinct candidate(s)`);
|
|
772
|
+
}
|
|
773
|
+
console.log();
|
|
774
|
+
});
|
|
775
|
+
program.command("learn").description("Analyze sessions and propose new instincts").option("-d, --dir <path>", "Harness directory", ".").option("--install", "Auto-install proposed instincts", false).action(async (opts) => {
|
|
776
|
+
const { learnFromSessions } = await import("../instinct-learner-SRM72DHF.js");
|
|
777
|
+
const dir = resolve(opts.dir);
|
|
778
|
+
requireHarness(dir);
|
|
779
|
+
try {
|
|
780
|
+
console.log(`Analyzing sessions for instinct candidates...`);
|
|
781
|
+
const result = await learnFromSessions(dir, opts.install);
|
|
782
|
+
if (result.candidates.length === 0) {
|
|
783
|
+
console.log(`No instinct candidates found.`);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
console.log(`
|
|
787
|
+
${result.candidates.length} instinct candidate(s):
|
|
788
|
+
`);
|
|
789
|
+
for (const c of result.candidates) {
|
|
790
|
+
const status = result.installed.includes(c.id) ? "\u2713 installed" : result.skipped.includes(c.id) ? "\u2298 skipped (exists)" : "\u25CB proposed";
|
|
791
|
+
console.log(` [${status}] ${c.id} (${c.confidence})`);
|
|
792
|
+
console.log(` ${c.behavior}`);
|
|
793
|
+
console.log(` Provenance: ${c.provenance}
|
|
794
|
+
`);
|
|
795
|
+
}
|
|
796
|
+
} catch (err) {
|
|
797
|
+
console.error(`Error: ${formatError(err)}`);
|
|
798
|
+
process.exit(1);
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
program.command("harvest").description("Extract instinct candidates from journal entries and optionally install them").option("-d, --dir <path>", "Harness directory", ".").option("--from <date>", "Start date (YYYY-MM-DD)").option("--to <date>", "End date (YYYY-MM-DD)").option("--install", "Auto-install candidates as draft instincts", false).action(async (opts) => {
|
|
802
|
+
const { harvestInstincts } = await import("../instinct-learner-SRM72DHF.js");
|
|
803
|
+
const dir = resolve(opts.dir);
|
|
804
|
+
requireHarness(dir);
|
|
805
|
+
const result = harvestInstincts(dir, {
|
|
806
|
+
from: opts.from,
|
|
807
|
+
to: opts.to,
|
|
808
|
+
install: opts.install
|
|
809
|
+
});
|
|
810
|
+
console.log(`
|
|
811
|
+
Scanned ${result.journalsScanned} journal(s)`);
|
|
812
|
+
if (result.candidates.length === 0) {
|
|
813
|
+
console.log(`No new instinct candidates found.
|
|
814
|
+
`);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
console.log(`Found ${result.candidates.length} candidate(s):
|
|
818
|
+
`);
|
|
819
|
+
for (const c of result.candidates) {
|
|
820
|
+
const status = result.installed.includes(c.id) ? "\u2713 installed" : result.skipped.includes(c.id) ? "\u2298 skipped (exists)" : "\u25CB proposed";
|
|
821
|
+
console.log(` [${status}] ${c.id}`);
|
|
822
|
+
console.log(` ${c.behavior}`);
|
|
823
|
+
console.log(` Source: ${c.provenance}
|
|
824
|
+
`);
|
|
825
|
+
}
|
|
826
|
+
if (!opts.install && result.candidates.length > 0) {
|
|
827
|
+
console.log(`Run with --install to create instinct files.
|
|
828
|
+
`);
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
program.command("intake").description("Process all pending files in the intake/ directory").option("-d, --dir <path>", "Harness directory", ".").action(async (opts) => {
|
|
832
|
+
const { processIntake } = await import("../intake-4M3HNU43.js");
|
|
833
|
+
const dir = resolve(opts.dir);
|
|
834
|
+
const results = processIntake(dir);
|
|
835
|
+
if (results.length === 0) {
|
|
836
|
+
console.log(`No files in intake/`);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
for (const { file, result } of results) {
|
|
840
|
+
if (result.installed) {
|
|
841
|
+
console.log(`\u2713 ${file} \u2192 ${result.evalResult.type}`);
|
|
842
|
+
} else {
|
|
843
|
+
console.log(`\u2717 ${file}: ${result.evalResult.errors.join(", ")}`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
program.command("validate").description("Validate harness structure and configuration").option("-d, --dir <path>", "Harness directory", ".").option("--json", "Output as JSON").action(async (opts) => {
|
|
848
|
+
const { validateHarness } = await import("../validator-7WXMDIHH.js");
|
|
849
|
+
const dir = resolve(opts.dir);
|
|
850
|
+
const result = validateHarness(dir);
|
|
851
|
+
if (opts.json) {
|
|
852
|
+
console.log(JSON.stringify(result, null, 2));
|
|
853
|
+
if (result.errors.length > 0) process.exit(1);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
console.log(`
|
|
857
|
+
Harness validation: ${dir}
|
|
858
|
+
`);
|
|
859
|
+
if (result.ok.length > 0) {
|
|
860
|
+
for (const msg of result.ok) {
|
|
861
|
+
console.log(` \u2713 ${msg}`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
if (result.warnings.length > 0) {
|
|
865
|
+
console.log();
|
|
866
|
+
for (const msg of result.warnings) {
|
|
867
|
+
console.log(` \u26A0 ${msg}`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
if (result.errors.length > 0) {
|
|
871
|
+
console.log();
|
|
872
|
+
for (const msg of result.errors) {
|
|
873
|
+
console.log(` \u2717 ${msg}`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
console.log(`
|
|
877
|
+
Summary: ${result.ok.length} passed, ${result.warnings.length} warnings, ${result.errors.length} errors`);
|
|
878
|
+
console.log(`Primitives: ${result.totalPrimitives} loaded${result.parseErrors.length > 0 ? `, ${result.parseErrors.length} parse error(s)` : ""}
|
|
879
|
+
`);
|
|
880
|
+
if (result.errors.length > 0) {
|
|
881
|
+
process.exit(1);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
program.command("doctor").description("Validate harness and auto-fix all fixable issues in one pass").option("-d, --dir <path>", "Harness directory", ".").action(async (opts) => {
|
|
885
|
+
const { doctorHarness } = await import("../validator-7WXMDIHH.js");
|
|
886
|
+
const dir = resolve(opts.dir);
|
|
887
|
+
console.log(`
|
|
888
|
+
Running doctor on: ${dir}
|
|
889
|
+
`);
|
|
890
|
+
const result = doctorHarness(dir);
|
|
891
|
+
if (result.fixes.length > 0) {
|
|
892
|
+
console.log(` Auto-fixed ${result.fixes.length} issue(s):`);
|
|
893
|
+
for (const fix of result.fixes) {
|
|
894
|
+
console.log(` \u2713 ${fix}`);
|
|
895
|
+
}
|
|
896
|
+
console.log();
|
|
897
|
+
}
|
|
898
|
+
if (result.ok.length > 0) {
|
|
899
|
+
for (const msg of result.ok) {
|
|
900
|
+
console.log(` \u2713 ${msg}`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (result.warnings.length > 0) {
|
|
904
|
+
console.log();
|
|
905
|
+
for (const msg of result.warnings) {
|
|
906
|
+
console.log(` \u26A0 ${msg}`);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (result.errors.length > 0) {
|
|
910
|
+
console.log();
|
|
911
|
+
for (const msg of result.errors) {
|
|
912
|
+
console.log(` \u2717 ${msg}`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const fixLabel = result.fixes.length > 0 ? `, ${result.fixes.length} fixed` : "";
|
|
916
|
+
console.log(`
|
|
917
|
+
Summary: ${result.ok.length} ok, ${result.warnings.length} warnings, ${result.errors.length} errors${fixLabel}`);
|
|
918
|
+
console.log(`Primitives: ${result.totalPrimitives}
|
|
919
|
+
`);
|
|
920
|
+
if (result.errors.length > 0) {
|
|
921
|
+
process.exit(1);
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
program.command("fix <file>").description("Auto-fix common issues in a capability markdown file (missing id, status, L0/L1)").action(async (file) => {
|
|
925
|
+
const { fixCapability } = await import("../intake-4M3HNU43.js");
|
|
926
|
+
const filePath = resolve(file);
|
|
927
|
+
const result = fixCapability(filePath);
|
|
928
|
+
if (result.fixes_applied.length > 0) {
|
|
929
|
+
console.log(`Fixed ${result.fixes_applied.length} issue(s) in ${filePath}:`);
|
|
930
|
+
for (const fix of result.fixes_applied) {
|
|
931
|
+
console.log(` \u2713 ${fix}`);
|
|
932
|
+
}
|
|
933
|
+
} else {
|
|
934
|
+
console.log("No auto-fixable issues found.");
|
|
935
|
+
}
|
|
936
|
+
if (result.warnings.length > 0) {
|
|
937
|
+
for (const w of result.warnings) {
|
|
938
|
+
console.log(` \u26A0 ${w}`);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (result.errors.length > 0) {
|
|
942
|
+
console.error(`
|
|
943
|
+
Remaining errors (manual fix required):`);
|
|
944
|
+
for (const e of result.errors) {
|
|
945
|
+
console.error(` \u2717 ${e}`);
|
|
946
|
+
}
|
|
947
|
+
process.exit(1);
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
program.command("cleanup").description("Archive sessions and journals older than retention period").option("-d, --dir <path>", "Harness directory", ".").option("--dry-run", "Show what would be archived without acting", false).option("--delete", "Permanently delete instead of archiving", false).action(async (opts) => {
|
|
951
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
952
|
+
const dir = resolve(opts.dir);
|
|
953
|
+
requireHarness(dir);
|
|
954
|
+
const config = loadConfig(dir);
|
|
955
|
+
const sessionDays = config.memory.session_retention_days;
|
|
956
|
+
const journalDays = config.memory.journal_retention_days;
|
|
957
|
+
if (opts.dryRun) {
|
|
958
|
+
const { listExpiredFiles } = await import("../sessions-CZGVXKQE.js");
|
|
959
|
+
const expired = listExpiredFiles(dir, sessionDays, journalDays);
|
|
960
|
+
const action = opts.delete ? "delete" : "archive";
|
|
961
|
+
console.log(`
|
|
962
|
+
Dry run \u2014 retention policy (sessions: ${sessionDays}d, journals: ${journalDays}d)
|
|
963
|
+
`);
|
|
964
|
+
console.log(`Would ${action} ${expired.sessionFiles.length} session(s):`);
|
|
965
|
+
expired.sessionFiles.forEach((f) => console.log(` - ${f}`));
|
|
966
|
+
console.log(`Would ${action} ${expired.journalFiles.length} journal(s):`);
|
|
967
|
+
expired.journalFiles.forEach((f) => console.log(` - ${f}`));
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
if (opts.delete) {
|
|
971
|
+
const { cleanupOldFiles } = await import("../sessions-CZGVXKQE.js");
|
|
972
|
+
const result = cleanupOldFiles(dir, sessionDays, journalDays);
|
|
973
|
+
console.log(`
|
|
974
|
+
Deleted ${result.sessionsRemoved} session(s), ${result.journalsRemoved} journal(s)`);
|
|
975
|
+
if (result.sessionFiles.length > 0) {
|
|
976
|
+
result.sessionFiles.forEach((f) => console.log(` - ${f}`));
|
|
977
|
+
}
|
|
978
|
+
if (result.journalFiles.length > 0) {
|
|
979
|
+
result.journalFiles.forEach((f) => console.log(` - ${f}`));
|
|
980
|
+
}
|
|
981
|
+
} else {
|
|
982
|
+
const { archiveOldFiles } = await import("../sessions-CZGVXKQE.js");
|
|
983
|
+
const result = archiveOldFiles(dir, sessionDays, journalDays);
|
|
984
|
+
console.log(`
|
|
985
|
+
Archived ${result.sessionsArchived} session(s), ${result.journalsArchived} journal(s)`);
|
|
986
|
+
if (result.sessionFiles.length > 0) {
|
|
987
|
+
console.log(` Sessions \u2192 memory/sessions/archive/`);
|
|
988
|
+
result.sessionFiles.forEach((f) => console.log(` - ${f}`));
|
|
989
|
+
}
|
|
990
|
+
if (result.journalFiles.length > 0) {
|
|
991
|
+
console.log(` Journals \u2192 memory/journal/archive/`);
|
|
992
|
+
result.journalFiles.forEach((f) => console.log(` - ${f}`));
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
console.log();
|
|
996
|
+
});
|
|
997
|
+
program.command("status").description("Show harness status: primitives, sessions, config, state").option("-d, --dir <path>", "Harness directory", ".").action(async (opts) => {
|
|
998
|
+
const { existsSync: existsSync2, readdirSync } = await import("fs");
|
|
999
|
+
const { validateHarness } = await import("../validator-7WXMDIHH.js");
|
|
1000
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
1001
|
+
const { loadState } = await import("../state-GMXILIHW.js");
|
|
1002
|
+
const { listSessions } = await import("../sessions-CZGVXKQE.js");
|
|
1003
|
+
const dir = resolve(opts.dir);
|
|
1004
|
+
requireHarness(dir);
|
|
1005
|
+
const config = loadConfig(dir);
|
|
1006
|
+
const state = loadState(dir);
|
|
1007
|
+
const validation = validateHarness(dir);
|
|
1008
|
+
const sessions = listSessions(dir);
|
|
1009
|
+
console.log(`
|
|
1010
|
+
${config.agent.name} v${config.agent.version}`);
|
|
1011
|
+
console.log(` Model: ${config.model.provider}/${config.model.id}`);
|
|
1012
|
+
console.log(` Mode: ${state.mode}`);
|
|
1013
|
+
console.log(`
|
|
1014
|
+
Primitives (${validation.totalPrimitives} total):`);
|
|
1015
|
+
for (const [dir2, count] of validation.primitiveCounts) {
|
|
1016
|
+
if (count > 0) console.log(` ${dir2}: ${count}`);
|
|
1017
|
+
}
|
|
1018
|
+
const emptyDirs = Array.from(validation.primitiveCounts.entries()).filter(([, c]) => c === 0).map(([d]) => d);
|
|
1019
|
+
if (emptyDirs.length > 0) {
|
|
1020
|
+
console.log(` (empty: ${emptyDirs.join(", ")})`);
|
|
1021
|
+
}
|
|
1022
|
+
console.log(`
|
|
1023
|
+
Sessions: ${sessions.length} total`);
|
|
1024
|
+
if (sessions.length > 0) {
|
|
1025
|
+
const recent = sessions.slice(0, 3);
|
|
1026
|
+
for (const s of recent) {
|
|
1027
|
+
console.log(` ${s.id}`);
|
|
1028
|
+
}
|
|
1029
|
+
if (sessions.length > 3) console.log(` ... and ${sessions.length - 3} more`);
|
|
1030
|
+
}
|
|
1031
|
+
const journalDir = join(dir, "memory", "journal");
|
|
1032
|
+
let journalCount = 0;
|
|
1033
|
+
if (existsSync2(journalDir)) {
|
|
1034
|
+
journalCount = readdirSync(journalDir).filter(
|
|
1035
|
+
(f) => f.endsWith(".md") && !f.startsWith(".") && !f.startsWith("_")
|
|
1036
|
+
).length;
|
|
1037
|
+
}
|
|
1038
|
+
console.log(` Journals: ${journalCount}`);
|
|
1039
|
+
const mcpServers = config.mcp?.servers ?? {};
|
|
1040
|
+
const mcpEntries = Object.entries(mcpServers);
|
|
1041
|
+
if (mcpEntries.length > 0) {
|
|
1042
|
+
const enabledCount = mcpEntries.filter(([, s]) => s.enabled !== false).length;
|
|
1043
|
+
console.log(`
|
|
1044
|
+
MCP Servers: ${mcpEntries.length} configured (${enabledCount} enabled)`);
|
|
1045
|
+
for (const [name, serverConfig] of mcpEntries) {
|
|
1046
|
+
const enabled = serverConfig.enabled !== false;
|
|
1047
|
+
const icon = enabled ? "+" : "-";
|
|
1048
|
+
const detail = serverConfig.transport === "stdio" ? serverConfig.command ?? "" : serverConfig.url ?? "";
|
|
1049
|
+
console.log(` [${icon}] ${name} (${serverConfig.transport}) ${detail}`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
if (state.goals.length > 0) {
|
|
1053
|
+
console.log(`
|
|
1054
|
+
Goals:`);
|
|
1055
|
+
for (const g of state.goals) {
|
|
1056
|
+
console.log(` - ${g}`);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (state.active_workflows.length > 0) {
|
|
1060
|
+
console.log(`
|
|
1061
|
+
Active workflows:`);
|
|
1062
|
+
for (const w of state.active_workflows) {
|
|
1063
|
+
console.log(` - ${w}`);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (state.unfinished_business.length > 0) {
|
|
1067
|
+
console.log(`
|
|
1068
|
+
Unfinished business:`);
|
|
1069
|
+
for (const u of state.unfinished_business) {
|
|
1070
|
+
console.log(` - ${u}`);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
const healthIssues = validation.errors.length + validation.warnings.length;
|
|
1074
|
+
if (healthIssues > 0) {
|
|
1075
|
+
console.log(`
|
|
1076
|
+
Health: ${validation.errors.length} error(s), ${validation.warnings.length} warning(s)`);
|
|
1077
|
+
if (validation.errors.length > 0) {
|
|
1078
|
+
console.log(` Run 'harness validate' for details`);
|
|
1079
|
+
}
|
|
1080
|
+
} else {
|
|
1081
|
+
console.log(`
|
|
1082
|
+
Health: OK`);
|
|
1083
|
+
}
|
|
1084
|
+
console.log(` Last interaction: ${state.last_interaction}
|
|
1085
|
+
`);
|
|
1086
|
+
});
|
|
1087
|
+
program.command("scratch").description("Write a note to scratch.md (working memory)").argument("<note...>", "Note to write").option("-d, --dir <path>", "Harness directory", ".").option("--clear", "Clear scratch before writing", false).option("--show", "Show current scratch contents", false).action(async (note, opts) => {
|
|
1088
|
+
const { readFileSync, writeFileSync, existsSync: existsSync2, mkdirSync } = await import("fs");
|
|
1089
|
+
const scratchPath = join(resolve(opts.dir), "memory", "scratch.md");
|
|
1090
|
+
const memoryDir = join(resolve(opts.dir), "memory");
|
|
1091
|
+
if (!existsSync2(memoryDir)) {
|
|
1092
|
+
mkdirSync(memoryDir, { recursive: true });
|
|
1093
|
+
}
|
|
1094
|
+
if (opts.show) {
|
|
1095
|
+
if (existsSync2(scratchPath)) {
|
|
1096
|
+
const content = readFileSync(scratchPath, "utf-8");
|
|
1097
|
+
console.log(content || "(empty)");
|
|
1098
|
+
} else {
|
|
1099
|
+
console.log("(no scratch.md)");
|
|
1100
|
+
}
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
const noteText = note.join(" ");
|
|
1104
|
+
if (opts.clear) {
|
|
1105
|
+
writeFileSync(scratchPath, noteText + "\n", "utf-8");
|
|
1106
|
+
console.log("\u2713 Scratch cleared and updated");
|
|
1107
|
+
} else {
|
|
1108
|
+
const existing = existsSync2(scratchPath) ? readFileSync(scratchPath, "utf-8") : "";
|
|
1109
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
1110
|
+
const entry = `[${timestamp}] ${noteText}
|
|
1111
|
+
`;
|
|
1112
|
+
writeFileSync(scratchPath, existing + entry, "utf-8");
|
|
1113
|
+
console.log("\u2713 Note added to scratch");
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
var workflowCmd = program.command("workflow").description("Manage workflows");
|
|
1117
|
+
workflowCmd.command("list").description("List all workflows and their schedules").option("-d, --dir <path>", "Harness directory", ".").action(async (opts) => {
|
|
1118
|
+
const { loadDirectory } = await import("../loader-C3TKIKZR.js");
|
|
1119
|
+
const dir = resolve(opts.dir);
|
|
1120
|
+
requireHarness(dir);
|
|
1121
|
+
const workflowDir = join(dir, "workflows");
|
|
1122
|
+
if (!existsSync(workflowDir)) {
|
|
1123
|
+
console.log("\nNo workflows/ directory. Create workflow files to enable scheduling.\n");
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
const docs = loadDirectory(workflowDir);
|
|
1127
|
+
if (docs.length === 0) {
|
|
1128
|
+
console.log("\nNo workflows defined.\n");
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
console.log(`
|
|
1132
|
+
${docs.length} workflow(s):
|
|
1133
|
+
`);
|
|
1134
|
+
for (const doc of docs) {
|
|
1135
|
+
const schedule = doc.frontmatter.schedule || "(no schedule)";
|
|
1136
|
+
const status = doc.frontmatter.status === "active" ? "" : ` [${doc.frontmatter.status}]`;
|
|
1137
|
+
const withAgent = doc.frontmatter.with ? ` \u2192 ${doc.frontmatter.with}` : "";
|
|
1138
|
+
console.log(` ${doc.frontmatter.id}${status}`);
|
|
1139
|
+
console.log(` Schedule: ${schedule}${withAgent}`);
|
|
1140
|
+
if (doc.l0) console.log(` ${doc.l0}`);
|
|
1141
|
+
}
|
|
1142
|
+
console.log();
|
|
1143
|
+
});
|
|
1144
|
+
workflowCmd.command("run <id>").description("Execute a workflow by ID (bypasses quiet hours)").option("-d, --dir <path>", "Harness directory", ".").action(async (workflowId, opts) => {
|
|
1145
|
+
const { Scheduler } = await import("../scheduler-XHHIVHRI.js");
|
|
1146
|
+
const dir = resolve(opts.dir);
|
|
1147
|
+
loadEnvFromDir(dir);
|
|
1148
|
+
requireHarness(dir);
|
|
1149
|
+
console.log(`
|
|
1150
|
+
Executing workflow: ${workflowId}...`);
|
|
1151
|
+
const scheduler = new Scheduler({
|
|
1152
|
+
harnessDir: dir,
|
|
1153
|
+
autoArchival: false
|
|
1154
|
+
});
|
|
1155
|
+
try {
|
|
1156
|
+
const result = await scheduler.runOnce(workflowId);
|
|
1157
|
+
console.log(`
|
|
1158
|
+
\u2713 Workflow "${workflowId}" complete.
|
|
1159
|
+
`);
|
|
1160
|
+
if (result) {
|
|
1161
|
+
console.log(result.slice(0, 500));
|
|
1162
|
+
if (result.length > 500) console.log(`
|
|
1163
|
+
... (${result.length} chars total)`);
|
|
1164
|
+
}
|
|
1165
|
+
} catch (err) {
|
|
1166
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1167
|
+
console.error(`
|
|
1168
|
+
\u2717 Workflow failed: ${msg}
|
|
1169
|
+
`);
|
|
1170
|
+
process.exit(1);
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
program.command("search [query]").description("Search primitives by text query and/or filters").option("-d, --dir <path>", "Harness directory", ".").option("-t, --tag <tag>", "Filter by tag").option("--type <type>", "Filter by primitive type (e.g., rules, skills)").option("--status <status>", "Filter by status (active, draft, archived, deprecated)").option("--author <author>", "Filter by author (human, agent, infrastructure)").option("--json", "Output as JSON").action(async (query, opts) => {
|
|
1174
|
+
const { searchPrimitives } = await import("../search-V3W5JMJG.js");
|
|
1175
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
1176
|
+
const dir = resolve(opts.dir);
|
|
1177
|
+
requireHarness(dir);
|
|
1178
|
+
let config;
|
|
1179
|
+
try {
|
|
1180
|
+
config = loadConfig(dir);
|
|
1181
|
+
} catch (err) {
|
|
1182
|
+
if (process.env.DEBUG) console.error(`Config load skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
1183
|
+
}
|
|
1184
|
+
const results = searchPrimitives(dir, query, {
|
|
1185
|
+
tag: opts.tag,
|
|
1186
|
+
type: opts.type,
|
|
1187
|
+
status: opts.status,
|
|
1188
|
+
author: opts.author
|
|
1189
|
+
}, config);
|
|
1190
|
+
if (opts.json) {
|
|
1191
|
+
console.log(JSON.stringify(results.map((r) => ({
|
|
1192
|
+
id: r.doc.frontmatter.id,
|
|
1193
|
+
directory: r.directory,
|
|
1194
|
+
status: r.doc.frontmatter.status,
|
|
1195
|
+
tags: r.doc.frontmatter.tags,
|
|
1196
|
+
l0: r.doc.l0,
|
|
1197
|
+
matchReason: r.matchReason
|
|
1198
|
+
})), null, 2));
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
if (results.length === 0) {
|
|
1202
|
+
const filters = [query, opts.tag && `tag:${opts.tag}`, opts.type && `type:${opts.type}`, opts.status && `status:${opts.status}`, opts.author && `author:${opts.author}`].filter(Boolean).join(", ");
|
|
1203
|
+
console.log(`
|
|
1204
|
+
No results for: ${filters || "(no filters)"}
|
|
1205
|
+
`);
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
console.log(`
|
|
1209
|
+
${results.length} result(s):
|
|
1210
|
+
`);
|
|
1211
|
+
for (const r of results) {
|
|
1212
|
+
const tags = r.doc.frontmatter.tags.length > 0 ? ` [${r.doc.frontmatter.tags.join(", ")}]` : "";
|
|
1213
|
+
const status = r.doc.frontmatter.status !== "active" ? ` (${r.doc.frontmatter.status})` : "";
|
|
1214
|
+
console.log(` ${r.directory}/${r.doc.frontmatter.id}${status}${tags}`);
|
|
1215
|
+
console.log(` ${r.matchReason}`);
|
|
1216
|
+
if (r.doc.l0) console.log(` ${r.doc.l0}`);
|
|
1217
|
+
}
|
|
1218
|
+
console.log();
|
|
1219
|
+
});
|
|
1220
|
+
var configCmd = program.command("config").description("Show or inspect configuration");
|
|
1221
|
+
configCmd.command("show").description("Show full resolved configuration (merged defaults + file + env)").option("-d, --dir <path>", "Harness directory", ".").action(async (opts) => {
|
|
1222
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
1223
|
+
const YAML = await import("yaml");
|
|
1224
|
+
const dir = resolve(opts.dir);
|
|
1225
|
+
requireHarness(dir);
|
|
1226
|
+
try {
|
|
1227
|
+
const config = loadConfig(dir);
|
|
1228
|
+
console.log(`
|
|
1229
|
+
# Resolved config for: ${dir}
|
|
1230
|
+
`);
|
|
1231
|
+
console.log(YAML.stringify(config).trimEnd());
|
|
1232
|
+
console.log();
|
|
1233
|
+
} catch (err) {
|
|
1234
|
+
console.error(`Error: ${formatError(err)}`);
|
|
1235
|
+
process.exit(1);
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
configCmd.command("get <key>").description("Get a specific config value (dot-notation, e.g. model.id)").option("-d, --dir <path>", "Harness directory", ".").action(async (key, opts) => {
|
|
1239
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
1240
|
+
const dir = resolve(opts.dir);
|
|
1241
|
+
requireHarness(dir);
|
|
1242
|
+
try {
|
|
1243
|
+
const config = loadConfig(dir);
|
|
1244
|
+
const parts = key.split(".");
|
|
1245
|
+
let value = config;
|
|
1246
|
+
for (const part of parts) {
|
|
1247
|
+
if (value === null || value === void 0 || typeof value !== "object") {
|
|
1248
|
+
console.error(`Error: Key "${key}" not found (stopped at "${part}")`);
|
|
1249
|
+
process.exit(1);
|
|
1250
|
+
}
|
|
1251
|
+
value = value[part];
|
|
1252
|
+
}
|
|
1253
|
+
if (value === void 0) {
|
|
1254
|
+
console.error(`Error: Key "${key}" not found`);
|
|
1255
|
+
process.exit(1);
|
|
1256
|
+
}
|
|
1257
|
+
if (typeof value === "object" && value !== null) {
|
|
1258
|
+
const YAML = await import("yaml");
|
|
1259
|
+
console.log(YAML.stringify(value).trimEnd());
|
|
1260
|
+
} else {
|
|
1261
|
+
console.log(String(value));
|
|
1262
|
+
}
|
|
1263
|
+
} catch (err) {
|
|
1264
|
+
console.error(`Error: ${formatError(err)}`);
|
|
1265
|
+
process.exit(1);
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
configCmd.command("set <key> <value>").description("Set a config value (writes to config.yaml)").option("-d, --dir <path>", "Harness directory", ".").action(async (key, value, opts) => {
|
|
1269
|
+
const { readFileSync, writeFileSync, existsSync: existsSync2 } = await import("fs");
|
|
1270
|
+
const YAML = await import("yaml");
|
|
1271
|
+
const dir = resolve(opts.dir);
|
|
1272
|
+
requireHarness(dir);
|
|
1273
|
+
const configPath = join(dir, "config.yaml");
|
|
1274
|
+
if (!existsSync2(configPath)) {
|
|
1275
|
+
console.error(`Error: No config.yaml found in ${dir}`);
|
|
1276
|
+
process.exit(1);
|
|
1277
|
+
}
|
|
1278
|
+
try {
|
|
1279
|
+
const content = readFileSync(configPath, "utf-8");
|
|
1280
|
+
const doc = YAML.parseDocument(content);
|
|
1281
|
+
let parsed = value;
|
|
1282
|
+
if (value === "true") parsed = true;
|
|
1283
|
+
else if (value === "false") parsed = false;
|
|
1284
|
+
else if (/^\d+$/.test(value)) parsed = parseInt(value, 10);
|
|
1285
|
+
else if (/^\d+\.\d+$/.test(value)) parsed = parseFloat(value);
|
|
1286
|
+
const parts = key.split(".");
|
|
1287
|
+
doc.setIn(parts, parsed);
|
|
1288
|
+
writeFileSync(configPath, doc.toString(), "utf-8");
|
|
1289
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
1290
|
+
try {
|
|
1291
|
+
loadConfig(dir);
|
|
1292
|
+
console.log(`\u2713 ${key} = ${String(parsed)}`);
|
|
1293
|
+
} catch (err) {
|
|
1294
|
+
console.error(`Warning: Config saved but validation failed: ${formatError(err)}`);
|
|
1295
|
+
console.error(`You may want to revert: harness config set ${key} <previous-value>`);
|
|
1296
|
+
}
|
|
1297
|
+
} catch (err) {
|
|
1298
|
+
console.error(`Error: ${formatError(err)}`);
|
|
1299
|
+
process.exit(1);
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
var metricsCmd = program.command("metrics").description("View workflow execution metrics and stats");
|
|
1303
|
+
metricsCmd.command("show").description("Show stats for all workflows (default)").option("-d, --dir <path>", "Harness directory", ".").option("--workflow <id>", "Show stats for a specific workflow").action(async (opts) => {
|
|
1304
|
+
const { getAllWorkflowStats, getWorkflowStats } = await import("../metrics-KXGNFAAB.js");
|
|
1305
|
+
const dir = resolve(opts.dir);
|
|
1306
|
+
requireHarness(dir);
|
|
1307
|
+
if (opts.workflow) {
|
|
1308
|
+
const stats = getWorkflowStats(dir, opts.workflow);
|
|
1309
|
+
if (!stats) {
|
|
1310
|
+
console.log(`
|
|
1311
|
+
No metrics recorded for workflow "${opts.workflow}".
|
|
1312
|
+
`);
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
console.log(`
|
|
1316
|
+
Workflow: ${stats.workflow_id}
|
|
1317
|
+
`);
|
|
1318
|
+
console.log(` Runs: ${stats.total_runs}`);
|
|
1319
|
+
console.log(` Successes: ${stats.successes}`);
|
|
1320
|
+
console.log(` Failures: ${stats.failures}`);
|
|
1321
|
+
console.log(` Success rate: ${(stats.success_rate * 100).toFixed(1)}%`);
|
|
1322
|
+
console.log(` Avg duration: ${formatDuration(stats.avg_duration_ms)}`);
|
|
1323
|
+
console.log(` Total tokens: ${stats.total_tokens}`);
|
|
1324
|
+
console.log(` Last run: ${stats.last_run}`);
|
|
1325
|
+
if (stats.last_success) console.log(` Last success: ${stats.last_success}`);
|
|
1326
|
+
if (stats.last_failure) console.log(` Last failure: ${stats.last_failure}`);
|
|
1327
|
+
console.log();
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
const allStats = getAllWorkflowStats(dir);
|
|
1331
|
+
if (allStats.length === 0) {
|
|
1332
|
+
console.log("\nNo workflow metrics recorded yet.\n");
|
|
1333
|
+
console.log("Metrics are automatically recorded when workflows run via scheduler or `harness workflow run`.\n");
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
console.log(`
|
|
1337
|
+
${allStats.length} workflow(s) with metrics:
|
|
1338
|
+
`);
|
|
1339
|
+
for (const stats of allStats) {
|
|
1340
|
+
const rate = (stats.success_rate * 100).toFixed(0);
|
|
1341
|
+
console.log(` ${stats.workflow_id}`);
|
|
1342
|
+
console.log(` ${stats.total_runs} runs (${rate}% success) | avg ${formatDuration(stats.avg_duration_ms)} | ${stats.total_tokens} tokens`);
|
|
1343
|
+
console.log(` Last: ${stats.last_run}`);
|
|
1344
|
+
}
|
|
1345
|
+
console.log();
|
|
1346
|
+
});
|
|
1347
|
+
metricsCmd.command("clear").description("Clear metrics for a specific workflow or all workflows").option("-d, --dir <path>", "Harness directory", ".").option("--workflow <id>", "Clear only this workflow (clears all if omitted)").action(async (opts) => {
|
|
1348
|
+
const { clearMetrics } = await import("../metrics-KXGNFAAB.js");
|
|
1349
|
+
const dir = resolve(opts.dir);
|
|
1350
|
+
requireHarness(dir);
|
|
1351
|
+
const removed = clearMetrics(dir, opts.workflow);
|
|
1352
|
+
if (opts.workflow) {
|
|
1353
|
+
console.log(`Cleared ${removed} metric(s) for workflow "${opts.workflow}".`);
|
|
1354
|
+
} else {
|
|
1355
|
+
console.log(`Cleared ${removed} total metric(s).`);
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
metricsCmd.command("history").description("Show recent workflow run history").option("-d, --dir <path>", "Harness directory", ".").option("--workflow <id>", "Filter by workflow ID").option("-n, --limit <count>", "Number of recent runs to show", "10").action(async (opts) => {
|
|
1359
|
+
const { loadMetrics } = await import("../metrics-KXGNFAAB.js");
|
|
1360
|
+
const dir = resolve(opts.dir);
|
|
1361
|
+
requireHarness(dir);
|
|
1362
|
+
const store = loadMetrics(dir);
|
|
1363
|
+
let runs = store.runs;
|
|
1364
|
+
if (opts.workflow) {
|
|
1365
|
+
runs = runs.filter((r) => r.workflow_id === opts.workflow);
|
|
1366
|
+
}
|
|
1367
|
+
const limit = parseInt(opts.limit, 10) || 10;
|
|
1368
|
+
const recent = runs.slice(-limit).reverse();
|
|
1369
|
+
if (recent.length === 0) {
|
|
1370
|
+
console.log("\nNo workflow runs recorded.\n");
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
console.log(`
|
|
1374
|
+
${recent.length} recent run(s)${opts.workflow ? ` for "${opts.workflow}"` : ""}:
|
|
1375
|
+
`);
|
|
1376
|
+
for (const run of recent) {
|
|
1377
|
+
const status = run.success ? "OK" : "FAIL";
|
|
1378
|
+
const tokens = run.tokens_used ? ` | ${run.tokens_used} tokens` : "";
|
|
1379
|
+
const retries = run.attempt > 1 ? ` (attempt ${run.attempt}/${run.max_retries + 1})` : "";
|
|
1380
|
+
const error = run.error ? `
|
|
1381
|
+
Error: ${run.error.slice(0, 100)}` : "";
|
|
1382
|
+
console.log(` [${status}] ${run.workflow_id} \u2014 ${formatDuration(run.duration_ms)}${tokens}${retries}`);
|
|
1383
|
+
console.log(` ${run.started}${error}`);
|
|
1384
|
+
}
|
|
1385
|
+
console.log();
|
|
1386
|
+
});
|
|
1387
|
+
function formatDuration(ms) {
|
|
1388
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
1389
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
1390
|
+
const minutes = Math.floor(ms / 6e4);
|
|
1391
|
+
const seconds = Math.round(ms % 6e4 / 1e3);
|
|
1392
|
+
return `${minutes}m${seconds}s`;
|
|
1393
|
+
}
|
|
1394
|
+
var toolsCmd = program.command("tools").description("List and inspect tool definitions");
|
|
1395
|
+
toolsCmd.command("list").description("List all defined tools with auth status").option("-d, --dir <path>", "Harness directory", ".").action(async (opts) => {
|
|
1396
|
+
const { listToolSummaries } = await import("../tools-DZ4KETET.js");
|
|
1397
|
+
const dir = resolve(opts.dir);
|
|
1398
|
+
requireHarness(dir);
|
|
1399
|
+
const tools = listToolSummaries(dir);
|
|
1400
|
+
if (tools.length === 0) {
|
|
1401
|
+
console.log("\nNo tools defined. Create tool files in tools/ to register external services.\n");
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
console.log(`
|
|
1405
|
+
${tools.length} tool(s):
|
|
1406
|
+
`);
|
|
1407
|
+
for (const tool of tools) {
|
|
1408
|
+
const auth = tool.authReady ? "ready" : "missing auth";
|
|
1409
|
+
const status = tool.status !== "active" ? ` [${tool.status}]` : "";
|
|
1410
|
+
console.log(` ${tool.id}${status} (${auth})`);
|
|
1411
|
+
if (tool.l0) console.log(` ${tool.l0}`);
|
|
1412
|
+
console.log(` ${tool.operationCount} operation(s) | tags: ${tool.tags.join(", ") || "none"}`);
|
|
1413
|
+
}
|
|
1414
|
+
console.log();
|
|
1415
|
+
});
|
|
1416
|
+
toolsCmd.command("show <id>").description("Show detailed info for a specific tool").option("-d, --dir <path>", "Harness directory", ".").action(async (toolId, opts) => {
|
|
1417
|
+
const { getToolById } = await import("../tools-DZ4KETET.js");
|
|
1418
|
+
const dir = resolve(opts.dir);
|
|
1419
|
+
requireHarness(dir);
|
|
1420
|
+
const tool = getToolById(dir, toolId);
|
|
1421
|
+
if (!tool) {
|
|
1422
|
+
console.error(`Tool not found: ${toolId}`);
|
|
1423
|
+
process.exit(1);
|
|
1424
|
+
}
|
|
1425
|
+
console.log(`
|
|
1426
|
+
Tool: ${tool.id}`);
|
|
1427
|
+
console.log(` Status: ${tool.status}`);
|
|
1428
|
+
console.log(` Tags: ${tool.tags.join(", ") || "none"}`);
|
|
1429
|
+
if (tool.auth.length > 0) {
|
|
1430
|
+
console.log(`
|
|
1431
|
+
Authentication:`);
|
|
1432
|
+
for (const a of tool.auth) {
|
|
1433
|
+
const status = a.present ? "set" : "MISSING";
|
|
1434
|
+
console.log(` ${a.envVar}: ${status}`);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
if (tool.operations.length > 0) {
|
|
1438
|
+
console.log(`
|
|
1439
|
+
Operations (${tool.operations.length}):`);
|
|
1440
|
+
for (const op of tool.operations) {
|
|
1441
|
+
console.log(` ${op.method} ${op.endpoint}`);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (tool.rateLimits.length > 0) {
|
|
1445
|
+
console.log(`
|
|
1446
|
+
Rate Limits:`);
|
|
1447
|
+
for (const rl of tool.rateLimits) {
|
|
1448
|
+
console.log(` - ${rl}`);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
if (tool.gotchas.length > 0) {
|
|
1452
|
+
console.log(`
|
|
1453
|
+
Gotchas:`);
|
|
1454
|
+
for (const g of tool.gotchas) {
|
|
1455
|
+
console.log(` - ${g}`);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
console.log();
|
|
1459
|
+
});
|
|
1460
|
+
toolsCmd.command("auth").description("Check auth status for all tools").option("-d, --dir <path>", "Harness directory", ".").action(async (opts) => {
|
|
1461
|
+
const { checkToolAuth } = await import("../tools-DZ4KETET.js");
|
|
1462
|
+
const dir = resolve(opts.dir);
|
|
1463
|
+
requireHarness(dir);
|
|
1464
|
+
const results = checkToolAuth(dir);
|
|
1465
|
+
if (results.length === 0) {
|
|
1466
|
+
console.log("\nNo tools defined.\n");
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
console.log("\nTool auth status:\n");
|
|
1470
|
+
for (const { tool, auth } of results) {
|
|
1471
|
+
if (auth.length === 0) {
|
|
1472
|
+
console.log(` ${tool}: no auth required`);
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
const allPresent = auth.every((a) => a.present);
|
|
1476
|
+
console.log(` ${tool}: ${allPresent ? "ready" : "INCOMPLETE"}`);
|
|
1477
|
+
for (const a of auth) {
|
|
1478
|
+
const icon = a.present ? "set" : "MISSING";
|
|
1479
|
+
console.log(` ${a.envVar}: ${icon}`);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
console.log();
|
|
1483
|
+
});
|
|
1484
|
+
program.command("export [output]").description("Export harness to a portable JSON bundle").option("-d, --dir <path>", "Harness directory", ".").option("--no-sessions", "Exclude session files").option("--no-journals", "Exclude journal files").option("--no-metrics", "Exclude metrics").option("--no-state", "Exclude state and scratch").action(async (output, opts) => {
|
|
1485
|
+
const { exportHarness, writeBundle } = await import("../export-6GCYHEHQ.js");
|
|
1486
|
+
const dir = resolve(opts.dir);
|
|
1487
|
+
requireHarness(dir);
|
|
1488
|
+
const bundle = exportHarness(dir, {
|
|
1489
|
+
sessions: opts.sessions,
|
|
1490
|
+
journals: opts.journals,
|
|
1491
|
+
metrics: opts.metrics,
|
|
1492
|
+
state: opts.state
|
|
1493
|
+
});
|
|
1494
|
+
const outputPath = output ? resolve(output) : resolve(`${bundle.agent_name}-export.json`);
|
|
1495
|
+
writeBundle(bundle, outputPath);
|
|
1496
|
+
const { metadata } = bundle;
|
|
1497
|
+
console.log(`
|
|
1498
|
+
Exported "${bundle.agent_name}" to ${outputPath}`);
|
|
1499
|
+
console.log(` ${bundle.entries.length} files (${metadata.primitives} primitives, ${metadata.sessions} sessions, ${metadata.journals} journals)
|
|
1500
|
+
`);
|
|
1501
|
+
});
|
|
1502
|
+
program.command("import <bundle>").description("Import a harness bundle into current directory").option("-d, --dir <path>", "Harness directory", ".").option("--overwrite", "Overwrite existing files", false).action(async (bundlePath, opts) => {
|
|
1503
|
+
const { readBundle, importBundle } = await import("../export-6GCYHEHQ.js");
|
|
1504
|
+
const dir = resolve(opts.dir);
|
|
1505
|
+
try {
|
|
1506
|
+
const bundle = readBundle(resolve(bundlePath));
|
|
1507
|
+
console.log(`
|
|
1508
|
+
Importing bundle: "${bundle.agent_name}" (exported ${bundle.exported_at})`);
|
|
1509
|
+
console.log(` ${bundle.entries.length} files in bundle
|
|
1510
|
+
`);
|
|
1511
|
+
const result = importBundle(dir, bundle, { overwrite: opts.overwrite });
|
|
1512
|
+
console.log(` Imported: ${result.imported}`);
|
|
1513
|
+
console.log(` Skipped (exists): ${result.skipped}`);
|
|
1514
|
+
if (result.errors.length > 0) {
|
|
1515
|
+
console.log(` Errors: ${result.errors.length}`);
|
|
1516
|
+
for (const err of result.errors) {
|
|
1517
|
+
console.log(` - ${err}`);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
console.log();
|
|
1521
|
+
} catch (err) {
|
|
1522
|
+
console.error(`Error: ${formatError(err)}`);
|
|
1523
|
+
process.exit(1);
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1526
|
+
program.command("bundle <output>").description("Pack primitives into a shareable bundle with manifest.yaml").option("-d, --dir <path>", "Harness directory", ".").option("-n, --name <name>", "Bundle name").option("--description <text>", "Bundle description", "").option("--author <name>", "Author name").option("--version <ver>", "Bundle version", "1.0.0").option("-t, --types <types...>", "Primitive types to include (e.g., rules instincts)").option("-f, --files <files...>", "Specific files to include (relative paths)").option("--tags <tags...>", "Tags for search/discovery").option("--license <id>", "License identifier (e.g., MIT)").option("--json", "Output as JSON", false).action(async (output, opts) => {
|
|
1527
|
+
const { packBundle, writeBundleDir } = await import("../primitive-registry-I6VTIR4W.js");
|
|
1528
|
+
const dir = resolve(opts.dir);
|
|
1529
|
+
requireHarness(dir);
|
|
1530
|
+
const bundleName = opts.name ?? basename(dir);
|
|
1531
|
+
const bundle = packBundle(dir, {
|
|
1532
|
+
name: bundleName,
|
|
1533
|
+
description: opts.description,
|
|
1534
|
+
author: opts.author,
|
|
1535
|
+
version: opts.version,
|
|
1536
|
+
types: opts.types,
|
|
1537
|
+
files: opts.files,
|
|
1538
|
+
tags: opts.tags,
|
|
1539
|
+
license: opts.license
|
|
1540
|
+
});
|
|
1541
|
+
const outputPath = resolve(output);
|
|
1542
|
+
writeBundleDir(bundle, outputPath);
|
|
1543
|
+
if (opts.json) {
|
|
1544
|
+
console.log(JSON.stringify(bundle.manifest, null, 2));
|
|
1545
|
+
} else {
|
|
1546
|
+
console.log(`
|
|
1547
|
+
Bundled "${bundleName}" v${opts.version}`);
|
|
1548
|
+
console.log(` ${bundle.files.length} files in ${bundle.manifest.types.join(", ")}`);
|
|
1549
|
+
console.log(` Output: ${outputPath}/`);
|
|
1550
|
+
console.log(` Manifest: ${outputPath}/manifest.yaml
|
|
1551
|
+
`);
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
program.command("bundle-install <source>").description("Install primitives from a bundle directory, JSON file, or URL").option("-d, --dir <path>", "Harness directory", ".").option("--overwrite", "Overwrite existing files", false).option("--force", "Skip dependency checks", false).option("--json", "Output as JSON", false).action(async (source, opts) => {
|
|
1555
|
+
const { readBundleDir, installBundle, fetchRemoteBundle } = await import("../primitive-registry-I6VTIR4W.js");
|
|
1556
|
+
const { readBundle } = await import("../export-6GCYHEHQ.js");
|
|
1557
|
+
const dir = resolve(opts.dir);
|
|
1558
|
+
try {
|
|
1559
|
+
let bundle;
|
|
1560
|
+
if (source.startsWith("https://") || source.startsWith("http://")) {
|
|
1561
|
+
console.log(`Downloading bundle from ${source}...`);
|
|
1562
|
+
bundle = await fetchRemoteBundle(source);
|
|
1563
|
+
} else {
|
|
1564
|
+
const sourcePath = resolve(source);
|
|
1565
|
+
if (existsSync(join(sourcePath, "manifest.yaml"))) {
|
|
1566
|
+
bundle = readBundleDir(sourcePath);
|
|
1567
|
+
} else if (source.endsWith(".json")) {
|
|
1568
|
+
const jsonBundle = readBundle(sourcePath);
|
|
1569
|
+
const { CORE_PRIMITIVE_DIRS } = await import("../types-EW7AIB3R.js");
|
|
1570
|
+
const files = jsonBundle.entries;
|
|
1571
|
+
const types = /* @__PURE__ */ new Set();
|
|
1572
|
+
for (const entry of files) {
|
|
1573
|
+
const entryDir = entry.path.split("/")[0];
|
|
1574
|
+
if (CORE_PRIMITIVE_DIRS.includes(entryDir)) types.add(entryDir);
|
|
1575
|
+
}
|
|
1576
|
+
bundle = {
|
|
1577
|
+
manifest: {
|
|
1578
|
+
version: "1.0",
|
|
1579
|
+
name: jsonBundle.agent_name ?? "imported",
|
|
1580
|
+
description: "Imported from JSON bundle",
|
|
1581
|
+
author: "unknown",
|
|
1582
|
+
bundle_version: "1.0.0",
|
|
1583
|
+
created: jsonBundle.exported_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1584
|
+
types: [...types],
|
|
1585
|
+
tags: [],
|
|
1586
|
+
files: files.map((f) => ({ path: f.path, type: f.path.split("/")[0], id: basename(f.path, ".md"), l0: "" }))
|
|
1587
|
+
},
|
|
1588
|
+
files
|
|
1589
|
+
};
|
|
1590
|
+
} else {
|
|
1591
|
+
console.error(`Error: ${sourcePath} is not a bundle directory (no manifest.yaml) or JSON file`);
|
|
1592
|
+
process.exit(1);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
const result = installBundle(dir, bundle, { overwrite: opts.overwrite, force: opts.force });
|
|
1596
|
+
if (opts.json) {
|
|
1597
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1598
|
+
} else {
|
|
1599
|
+
if (result.installed) {
|
|
1600
|
+
console.log(`
|
|
1601
|
+
Installed "${result.name}"`);
|
|
1602
|
+
console.log(` Files: ${result.files.length} installed, ${result.skipped.length} skipped`);
|
|
1603
|
+
if (result.files.length > 0) {
|
|
1604
|
+
for (const f of result.files) console.log(` + ${f}`);
|
|
1605
|
+
}
|
|
1606
|
+
if (result.skipped.length > 0) {
|
|
1607
|
+
for (const f of result.skipped) console.log(` = ${f} (exists)`);
|
|
1608
|
+
}
|
|
1609
|
+
} else {
|
|
1610
|
+
console.error(`
|
|
1611
|
+
Installation failed:`);
|
|
1612
|
+
for (const err of result.errors) console.error(` - ${err}`);
|
|
1613
|
+
}
|
|
1614
|
+
console.log();
|
|
1615
|
+
}
|
|
1616
|
+
} catch (err) {
|
|
1617
|
+
console.error(`Error: ${formatError(err)}`);
|
|
1618
|
+
process.exit(1);
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
program.command("uninstall <bundle-name>").description("Uninstall a previously installed bundle (moves files to archive/)").option("-d, --dir <path>", "Harness directory", ".").option("--hard", "Permanently delete files instead of archiving", false).option("--json", "Output as JSON", false).action(async (bundleName, opts) => {
|
|
1622
|
+
const { uninstallBundle } = await import("../primitive-registry-I6VTIR4W.js");
|
|
1623
|
+
const dir = resolve(opts.dir);
|
|
1624
|
+
requireHarness(dir);
|
|
1625
|
+
const result = uninstallBundle(dir, bundleName, { hard: opts.hard });
|
|
1626
|
+
if (opts.json) {
|
|
1627
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1628
|
+
} else {
|
|
1629
|
+
if (result.uninstalled) {
|
|
1630
|
+
console.log(`
|
|
1631
|
+
Uninstalled "${bundleName}"`);
|
|
1632
|
+
console.log(` ${result.archived.length} files ${opts.hard ? "deleted" : "archived"}`);
|
|
1633
|
+
for (const f of result.archived) console.log(` - ${f}`);
|
|
1634
|
+
} else {
|
|
1635
|
+
console.error(`
|
|
1636
|
+
Uninstall failed:`);
|
|
1637
|
+
for (const err of result.errors) console.error(` - ${err}`);
|
|
1638
|
+
if (result.dependents.length > 0) {
|
|
1639
|
+
console.error(` Dependents: ${result.dependents.join(", ")}`);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
console.log();
|
|
1643
|
+
}
|
|
1644
|
+
});
|
|
1645
|
+
program.command("update <source>").description("Update an installed bundle from a new version").option("-d, --dir <path>", "Harness directory", ".").option("--remove-deleted", "Archive files removed in new version", false).option("--json", "Output as JSON", false).action(async (source, opts) => {
|
|
1646
|
+
const { readBundleDir, diffBundle, updateBundle, fetchRemoteBundle } = await import("../primitive-registry-I6VTIR4W.js");
|
|
1647
|
+
const dir = resolve(opts.dir);
|
|
1648
|
+
requireHarness(dir);
|
|
1649
|
+
try {
|
|
1650
|
+
let bundle;
|
|
1651
|
+
if (source.startsWith("https://") || source.startsWith("http://")) {
|
|
1652
|
+
console.log(`Downloading bundle from ${source}...`);
|
|
1653
|
+
bundle = await fetchRemoteBundle(source);
|
|
1654
|
+
} else {
|
|
1655
|
+
const sourcePath = resolve(source);
|
|
1656
|
+
if (!existsSync(join(sourcePath, "manifest.yaml"))) {
|
|
1657
|
+
console.error(`Error: ${sourcePath} is not a bundle directory (no manifest.yaml)`);
|
|
1658
|
+
process.exit(1);
|
|
1659
|
+
}
|
|
1660
|
+
bundle = readBundleDir(sourcePath);
|
|
1661
|
+
}
|
|
1662
|
+
const diff = diffBundle(dir, bundle);
|
|
1663
|
+
if (diff.added.length === 0 && diff.modified.length === 0 && diff.removed.length === 0) {
|
|
1664
|
+
console.log(`
|
|
1665
|
+
"${bundle.manifest.name}" is already up to date.
|
|
1666
|
+
`);
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
if (!opts.json) {
|
|
1670
|
+
console.log(`
|
|
1671
|
+
Update "${bundle.manifest.name}" to v${bundle.manifest.bundle_version}:`);
|
|
1672
|
+
if (diff.added.length > 0) {
|
|
1673
|
+
console.log(` Added (${diff.added.length}):`);
|
|
1674
|
+
for (const f of diff.added) console.log(` + ${f}`);
|
|
1675
|
+
}
|
|
1676
|
+
if (diff.modified.length > 0) {
|
|
1677
|
+
console.log(` Modified (${diff.modified.length}):`);
|
|
1678
|
+
for (const f of diff.modified) console.log(` ~ ${f}`);
|
|
1679
|
+
}
|
|
1680
|
+
if (diff.removed.length > 0) {
|
|
1681
|
+
console.log(` Removed (${diff.removed.length}):`);
|
|
1682
|
+
for (const f of diff.removed) console.log(` - ${f}`);
|
|
1683
|
+
}
|
|
1684
|
+
console.log();
|
|
1685
|
+
}
|
|
1686
|
+
const result = updateBundle(dir, bundle, { removeDeleted: opts.removeDeleted });
|
|
1687
|
+
if (opts.json) {
|
|
1688
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1689
|
+
} else {
|
|
1690
|
+
if (result.updated) {
|
|
1691
|
+
console.log(`Updated "${result.name}" ${result.oldVersion ?? "?"} \u2192 ${result.newVersion ?? "?"}`);
|
|
1692
|
+
console.log(` ${result.added.length} added, ${result.modified.length} modified, ${result.removed.length} removed`);
|
|
1693
|
+
} else {
|
|
1694
|
+
console.error(`Update failed:`);
|
|
1695
|
+
for (const err of result.errors) console.error(` - ${err}`);
|
|
1696
|
+
}
|
|
1697
|
+
console.log();
|
|
1698
|
+
}
|
|
1699
|
+
} catch (err) {
|
|
1700
|
+
console.error(`Error: ${formatError(err)}`);
|
|
1701
|
+
process.exit(1);
|
|
1702
|
+
}
|
|
1703
|
+
});
|
|
1704
|
+
program.command("installed").description("List installed bundles").option("-d, --dir <path>", "Harness directory", ".").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
1705
|
+
const { listInstalledBundles } = await import("../primitive-registry-I6VTIR4W.js");
|
|
1706
|
+
const dir = resolve(opts.dir);
|
|
1707
|
+
requireHarness(dir);
|
|
1708
|
+
const bundles = listInstalledBundles(dir);
|
|
1709
|
+
if (opts.json) {
|
|
1710
|
+
console.log(JSON.stringify(bundles, null, 2));
|
|
1711
|
+
} else {
|
|
1712
|
+
if (bundles.length === 0) {
|
|
1713
|
+
console.log("\nNo bundles installed.\n");
|
|
1714
|
+
} else {
|
|
1715
|
+
console.log(`
|
|
1716
|
+
${bundles.length} bundle(s) installed:
|
|
1717
|
+
`);
|
|
1718
|
+
for (const b of bundles) {
|
|
1719
|
+
console.log(` ${b.name} v${b.version} \u2014 ${b.description}`);
|
|
1720
|
+
console.log(` ${b.fileCount} files, types: ${b.types.join(", ")}`);
|
|
1721
|
+
}
|
|
1722
|
+
console.log();
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
var registryCmd = program.command("registry").description("Search and install bundles from configured registries");
|
|
1727
|
+
registryCmd.command("search <query>").description("Search all configured registries for bundles").option("-d, --dir <path>", "Harness directory", ".").option("--limit <n>", "Max results", "20").option("--json", "Output as JSON", false).action(async (query, opts) => {
|
|
1728
|
+
const { searchConfiguredRegistries } = await import("../primitive-registry-I6VTIR4W.js");
|
|
1729
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
1730
|
+
const dir = resolve(opts.dir);
|
|
1731
|
+
requireHarness(dir);
|
|
1732
|
+
const config = loadConfig(dir);
|
|
1733
|
+
const registries = config.registries ?? [];
|
|
1734
|
+
if (registries.length === 0) {
|
|
1735
|
+
console.error("No registries configured. Add registries: to config.yaml");
|
|
1736
|
+
process.exit(1);
|
|
1737
|
+
}
|
|
1738
|
+
try {
|
|
1739
|
+
const result = await searchConfiguredRegistries(registries, query, { limit: parseInt(opts.limit, 10) });
|
|
1740
|
+
if (opts.json) {
|
|
1741
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
console.log(`
|
|
1745
|
+
Searched ${result.registriesSearched} registry(ies) for "${query}"
|
|
1746
|
+
`);
|
|
1747
|
+
if (result.errors.length > 0) {
|
|
1748
|
+
for (const err of result.errors) {
|
|
1749
|
+
console.log(` [warn] ${err.registry}: ${err.error}`);
|
|
1750
|
+
}
|
|
1751
|
+
console.log();
|
|
1752
|
+
}
|
|
1753
|
+
if (result.results.length === 0) {
|
|
1754
|
+
console.log("No bundles found.\n");
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
for (const r of result.results) {
|
|
1758
|
+
console.log(` ${r.name} v${r.version} \u2014 ${r.description}`);
|
|
1759
|
+
console.log(` types: ${r.types.join(", ")} | tags: ${r.tags.join(", ") || "none"} | from: ${r.registryName}`);
|
|
1760
|
+
}
|
|
1761
|
+
console.log(`
|
|
1762
|
+
${result.total} result(s) total
|
|
1763
|
+
`);
|
|
1764
|
+
} catch (err) {
|
|
1765
|
+
console.error(`Error: ${formatError(err)}`);
|
|
1766
|
+
process.exit(1);
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
registryCmd.command("install <bundle-name>").description("Install a bundle from configured registries").option("-d, --dir <path>", "Harness directory", ".").option("--version <ver>", "Specific version to install").option("--overwrite", "Overwrite existing files", false).option("--force", "Skip dependency checks", false).option("--json", "Output as JSON", false).action(async (bundleName, opts) => {
|
|
1770
|
+
const { installFromRegistry } = await import("../primitive-registry-I6VTIR4W.js");
|
|
1771
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
1772
|
+
const dir = resolve(opts.dir);
|
|
1773
|
+
requireHarness(dir);
|
|
1774
|
+
const config = loadConfig(dir);
|
|
1775
|
+
const registries = config.registries ?? [];
|
|
1776
|
+
if (registries.length === 0) {
|
|
1777
|
+
console.error("No registries configured. Add registries: to config.yaml");
|
|
1778
|
+
process.exit(1);
|
|
1779
|
+
}
|
|
1780
|
+
try {
|
|
1781
|
+
console.log(`
|
|
1782
|
+
Searching registries for "${bundleName}"...`);
|
|
1783
|
+
const result = await installFromRegistry(dir, registries, bundleName, {
|
|
1784
|
+
version: opts.version,
|
|
1785
|
+
overwrite: opts.overwrite,
|
|
1786
|
+
force: opts.force
|
|
1787
|
+
});
|
|
1788
|
+
if (opts.json) {
|
|
1789
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
if (result.installed) {
|
|
1793
|
+
console.log(`Installed "${result.name}" from ${result.registryUrl ?? "registry"}`);
|
|
1794
|
+
console.log(` Files: ${result.files.length} installed, ${result.skipped.length} skipped`);
|
|
1795
|
+
if (result.files.length > 0) {
|
|
1796
|
+
for (const f of result.files) console.log(` + ${f}`);
|
|
1797
|
+
}
|
|
1798
|
+
if (result.skipped.length > 0) {
|
|
1799
|
+
for (const f of result.skipped) console.log(` = ${f} (exists)`);
|
|
1800
|
+
}
|
|
1801
|
+
} else {
|
|
1802
|
+
console.error(`
|
|
1803
|
+
Installation failed:`);
|
|
1804
|
+
for (const err of result.errors) console.error(` - ${err}`);
|
|
1805
|
+
}
|
|
1806
|
+
console.log();
|
|
1807
|
+
} catch (err) {
|
|
1808
|
+
console.error(`Error: ${formatError(err)}`);
|
|
1809
|
+
process.exit(1);
|
|
1810
|
+
}
|
|
1811
|
+
});
|
|
1812
|
+
registryCmd.command("list").description("List configured registries").option("-d, --dir <path>", "Harness directory", ".").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
1813
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
1814
|
+
const dir = resolve(opts.dir);
|
|
1815
|
+
requireHarness(dir);
|
|
1816
|
+
const config = loadConfig(dir);
|
|
1817
|
+
const registries = config.registries ?? [];
|
|
1818
|
+
if (opts.json) {
|
|
1819
|
+
console.log(JSON.stringify(registries, null, 2));
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
if (registries.length === 0) {
|
|
1823
|
+
console.log("\nNo registries configured.");
|
|
1824
|
+
console.log("Add to config.yaml:\n");
|
|
1825
|
+
console.log(" registries:");
|
|
1826
|
+
console.log(" - url: https://registry.example.com");
|
|
1827
|
+
console.log(" name: My Registry\n");
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
console.log(`
|
|
1831
|
+
${registries.length} registry(ies) configured:
|
|
1832
|
+
`);
|
|
1833
|
+
for (const reg of registries) {
|
|
1834
|
+
const name = reg.name ?? reg.url;
|
|
1835
|
+
const auth = reg.token ? " (authenticated)" : "";
|
|
1836
|
+
console.log(` ${name}${auth}`);
|
|
1837
|
+
console.log(` ${reg.url}`);
|
|
1838
|
+
}
|
|
1839
|
+
console.log();
|
|
1840
|
+
});
|
|
1841
|
+
program.command("graph").description("Analyze primitive dependency graph (related:/with: fields)").option("-d, --dir <path>", "Harness directory", ".").option("--json", "Output as JSON").action(async (opts) => {
|
|
1842
|
+
const { buildDependencyGraph, getGraphStats } = await import("../graph-YUIPOSOO.js");
|
|
1843
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
1844
|
+
const dir = resolve(opts.dir);
|
|
1845
|
+
requireHarness(dir);
|
|
1846
|
+
let config;
|
|
1847
|
+
try {
|
|
1848
|
+
config = loadConfig(dir);
|
|
1849
|
+
} catch (err) {
|
|
1850
|
+
if (process.env.DEBUG) console.error(`Config load skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
1851
|
+
}
|
|
1852
|
+
const graph = buildDependencyGraph(dir, config);
|
|
1853
|
+
const stats = getGraphStats(dir, config);
|
|
1854
|
+
if (opts.json) {
|
|
1855
|
+
console.log(JSON.stringify({ graph, stats }, null, 2));
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
console.log(`
|
|
1859
|
+
Dependency Graph
|
|
1860
|
+
`);
|
|
1861
|
+
console.log(` Nodes: ${stats.totalNodes}`);
|
|
1862
|
+
console.log(` Edges: ${stats.totalEdges}`);
|
|
1863
|
+
console.log(` Clusters: ${stats.clusterCount}`);
|
|
1864
|
+
console.log(` Orphans: ${stats.orphanCount}`);
|
|
1865
|
+
if (stats.mostConnected.length > 0) {
|
|
1866
|
+
console.log(`
|
|
1867
|
+
Most connected:`);
|
|
1868
|
+
for (const mc of stats.mostConnected) {
|
|
1869
|
+
console.log(` ${mc.id}: ${mc.connections} connection(s)`);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
if (graph.orphans.length > 0) {
|
|
1873
|
+
console.log(`
|
|
1874
|
+
Orphaned primitives (no relationships):`);
|
|
1875
|
+
for (const id of graph.orphans) {
|
|
1876
|
+
const node = graph.nodes.find((n) => n.id === id);
|
|
1877
|
+
console.log(` ${id} (${node?.directory || "unknown"})`);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
if (stats.brokenRefs.length > 0) {
|
|
1881
|
+
console.log(`
|
|
1882
|
+
Broken references:`);
|
|
1883
|
+
for (const br of stats.brokenRefs) {
|
|
1884
|
+
console.log(` ${br.from} -> "${br.ref}" (not found)`);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
if (graph.clusters.length > 0) {
|
|
1888
|
+
console.log(`
|
|
1889
|
+
Clusters:`);
|
|
1890
|
+
for (let i = 0; i < graph.clusters.length; i++) {
|
|
1891
|
+
const cluster = graph.clusters[i];
|
|
1892
|
+
console.log(` [${i + 1}] ${cluster.join(", ")}`);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
console.log();
|
|
1896
|
+
});
|
|
1897
|
+
program.command("analytics").description("Show session analytics and usage patterns").option("-d, --dir <path>", "Harness directory", ".").option("--from <date>", "Start date (YYYY-MM-DD)").option("--to <date>", "End date (YYYY-MM-DD)").option("--json", "Output as JSON").action(async (opts) => {
|
|
1898
|
+
const { getSessionAnalytics, getSessionsInRange } = await import("../analytics-RPT73WNM.js");
|
|
1899
|
+
const dir = resolve(opts.dir);
|
|
1900
|
+
requireHarness(dir);
|
|
1901
|
+
if (opts.from || opts.to) {
|
|
1902
|
+
const sessions = getSessionsInRange(dir, opts.from, opts.to);
|
|
1903
|
+
const label = opts.from && opts.to ? `${opts.from} to ${opts.to}` : opts.from ? `from ${opts.from}` : `to ${opts.to}`;
|
|
1904
|
+
if (opts.json) {
|
|
1905
|
+
console.log(JSON.stringify({ range: label, sessions }, null, 2));
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
if (sessions.length === 0) {
|
|
1909
|
+
console.log(`
|
|
1910
|
+
No sessions found for ${label}.
|
|
1911
|
+
`);
|
|
1912
|
+
return;
|
|
1913
|
+
}
|
|
1914
|
+
const totalTokens = sessions.reduce((sum, s) => sum + s.tokens, 0);
|
|
1915
|
+
console.log(`
|
|
1916
|
+
${sessions.length} session(s) ${label}:
|
|
1917
|
+
`);
|
|
1918
|
+
for (const s of sessions) {
|
|
1919
|
+
const model = s.model ? ` [${s.model}]` : "";
|
|
1920
|
+
const delegate = s.delegatedTo ? ` -> ${s.delegatedTo}` : "";
|
|
1921
|
+
console.log(` ${s.id}: ${s.tokens} tokens, ${s.durationMinutes}min${model}${delegate}`);
|
|
1922
|
+
}
|
|
1923
|
+
console.log(`
|
|
1924
|
+
Total: ${totalTokens} tokens
|
|
1925
|
+
`);
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
const analytics = getSessionAnalytics(dir);
|
|
1929
|
+
if (opts.json) {
|
|
1930
|
+
const serializable = {
|
|
1931
|
+
...analytics,
|
|
1932
|
+
modelUsage: Object.fromEntries(analytics.modelUsage)
|
|
1933
|
+
};
|
|
1934
|
+
console.log(JSON.stringify(serializable, null, 2));
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
if (analytics.totalSessions === 0) {
|
|
1938
|
+
console.log("\nNo sessions recorded yet.\n");
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
console.log(`
|
|
1942
|
+
Session Analytics
|
|
1943
|
+
`);
|
|
1944
|
+
console.log(` Total sessions: ${analytics.totalSessions}`);
|
|
1945
|
+
console.log(` Total tokens: ${analytics.totalTokens.toLocaleString()}`);
|
|
1946
|
+
console.log(` Avg tokens: ${analytics.avgTokensPerSession.toLocaleString()}/session`);
|
|
1947
|
+
console.log(` Avg duration: ${analytics.avgDurationMinutes}min/session`);
|
|
1948
|
+
console.log(` Delegations: ${analytics.delegationCount}`);
|
|
1949
|
+
if (analytics.dateRange) {
|
|
1950
|
+
console.log(` Date range: ${analytics.dateRange.earliest} to ${analytics.dateRange.latest}`);
|
|
1951
|
+
}
|
|
1952
|
+
if (analytics.modelUsage.size > 0) {
|
|
1953
|
+
console.log(`
|
|
1954
|
+
Model usage:`);
|
|
1955
|
+
const sorted = Array.from(analytics.modelUsage.entries()).sort((a, b) => b[1] - a[1]);
|
|
1956
|
+
for (const [model, count] of sorted) {
|
|
1957
|
+
console.log(` ${model}: ${count} session(s)`);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
if (analytics.topDays.length > 0) {
|
|
1961
|
+
console.log(`
|
|
1962
|
+
Busiest days:`);
|
|
1963
|
+
for (const day of analytics.topDays) {
|
|
1964
|
+
console.log(` ${day.date}: ${day.sessions} session(s), ${day.tokens.toLocaleString()} tokens`);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
console.log();
|
|
1968
|
+
});
|
|
1969
|
+
program.command("auto-promote").description("Find instinct patterns appearing 3+ times across journals and optionally install them").option("-d, --dir <path>", "Harness directory", ".").option("--threshold <n>", "Minimum occurrences across different dates", "3").option("--install", "Auto-install promoted instincts", false).option("--json", "Output as JSON", false).action(async (opts) => {
|
|
1970
|
+
const { autoPromoteInstincts } = await import("../intelligence-HJOCA4SJ.js");
|
|
1971
|
+
const dir = resolve(opts.dir);
|
|
1972
|
+
requireHarness(dir);
|
|
1973
|
+
const result = autoPromoteInstincts(dir, {
|
|
1974
|
+
threshold: parseInt(opts.threshold, 10),
|
|
1975
|
+
install: opts.install
|
|
1976
|
+
});
|
|
1977
|
+
if (opts.json) {
|
|
1978
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
console.log(`
|
|
1982
|
+
Scanned ${result.journalsScanned} journal(s)
|
|
1983
|
+
`);
|
|
1984
|
+
if (result.patterns.length === 0) {
|
|
1985
|
+
console.log("No patterns found meeting the threshold.\n");
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
console.log(`${result.patterns.length} pattern(s) found:
|
|
1989
|
+
`);
|
|
1990
|
+
for (const p of result.patterns) {
|
|
1991
|
+
const status = result.promoted.includes(behaviorToCliId(p.behavior)) ? "\u2713 promoted" : result.skipped.includes(behaviorToCliId(p.behavior)) ? "\u2298 exists" : "\u25CB candidate";
|
|
1992
|
+
console.log(` [${status}] ${p.behavior}`);
|
|
1993
|
+
console.log(` ${p.count}x across: ${p.journalDates.join(", ")}
|
|
1994
|
+
`);
|
|
1995
|
+
}
|
|
1996
|
+
if (!opts.install && result.patterns.length > 0) {
|
|
1997
|
+
console.log("Run with --install to create instinct files.\n");
|
|
1998
|
+
}
|
|
1999
|
+
});
|
|
2000
|
+
function behaviorToCliId(behavior) {
|
|
2001
|
+
return behavior.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").slice(0, 50).replace(/-+$/, "");
|
|
2002
|
+
}
|
|
2003
|
+
program.command("dead-primitives").description("Detect orphaned primitives not modified in 30+ days").option("-d, --dir <path>", "Harness directory", ".").option("--days <n>", "Threshold days since last modification", "30").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
2004
|
+
const { detectDeadPrimitives } = await import("../intelligence-HJOCA4SJ.js");
|
|
2005
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
2006
|
+
const dir = resolve(opts.dir);
|
|
2007
|
+
requireHarness(dir);
|
|
2008
|
+
let config;
|
|
2009
|
+
try {
|
|
2010
|
+
config = loadConfig(dir);
|
|
2011
|
+
} catch (err) {
|
|
2012
|
+
if (process.env.DEBUG) console.error(`Config load skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
2013
|
+
}
|
|
2014
|
+
const result = detectDeadPrimitives(dir, config, {
|
|
2015
|
+
thresholdDays: parseInt(opts.days, 10)
|
|
2016
|
+
});
|
|
2017
|
+
if (opts.json) {
|
|
2018
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
console.log(`
|
|
2022
|
+
Scanned ${result.totalScanned} primitive(s) (threshold: ${result.thresholdDays} days)
|
|
2023
|
+
`);
|
|
2024
|
+
if (result.dead.length === 0) {
|
|
2025
|
+
console.log("No dead primitives found.\n");
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
console.log(`${result.dead.length} dead primitive(s):
|
|
2029
|
+
`);
|
|
2030
|
+
for (const d of result.dead) {
|
|
2031
|
+
console.log(` ${d.id} (${d.directory})`);
|
|
2032
|
+
console.log(` ${d.path} \u2014 last modified ${d.lastModified} (${d.daysSinceModified}d ago)
|
|
2033
|
+
`);
|
|
2034
|
+
}
|
|
2035
|
+
});
|
|
2036
|
+
program.command("contradictions").description("Detect contradictions between rules and instincts").option("-d, --dir <path>", "Harness directory", ".").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
2037
|
+
const { detectContradictions } = await import("../intelligence-HJOCA4SJ.js");
|
|
2038
|
+
const dir = resolve(opts.dir);
|
|
2039
|
+
requireHarness(dir);
|
|
2040
|
+
const result = detectContradictions(dir);
|
|
2041
|
+
if (opts.json) {
|
|
2042
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
console.log(`
|
|
2046
|
+
Checked ${result.rulesChecked} rule(s) and ${result.instinctsChecked} instinct(s)
|
|
2047
|
+
`);
|
|
2048
|
+
if (result.contradictions.length === 0) {
|
|
2049
|
+
console.log("No contradictions detected.\n");
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
console.log(`${result.contradictions.length} contradiction(s) found:
|
|
2053
|
+
`);
|
|
2054
|
+
for (const c of result.contradictions) {
|
|
2055
|
+
console.log(` [${c.severity}] ${c.reason}`);
|
|
2056
|
+
console.log(` ${c.primitiveA.type}/${c.primitiveA.id}: "${c.primitiveA.text}"`);
|
|
2057
|
+
console.log(` ${c.primitiveB.type}/${c.primitiveB.id}: "${c.primitiveB.text}"
|
|
2058
|
+
`);
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
program.command("enrich").description("Enrich sessions with extracted topics, tools, and primitive references").option("-d, --dir <path>", "Harness directory", ".").option("--from <date>", "Start date (YYYY-MM-DD)").option("--to <date>", "End date (YYYY-MM-DD)").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
2062
|
+
const { enrichSessions } = await import("../intelligence-HJOCA4SJ.js");
|
|
2063
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
2064
|
+
const dir = resolve(opts.dir);
|
|
2065
|
+
requireHarness(dir);
|
|
2066
|
+
let config;
|
|
2067
|
+
try {
|
|
2068
|
+
config = loadConfig(dir);
|
|
2069
|
+
} catch (err) {
|
|
2070
|
+
if (process.env.DEBUG) console.error(`Config load skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
2071
|
+
}
|
|
2072
|
+
const result = enrichSessions(dir, config, { from: opts.from, to: opts.to });
|
|
2073
|
+
if (opts.json) {
|
|
2074
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
console.log(`
|
|
2078
|
+
Enriched ${result.sessionsScanned} session(s)
|
|
2079
|
+
`);
|
|
2080
|
+
if (result.enriched.length === 0) {
|
|
2081
|
+
console.log("No sessions to enrich.\n");
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
for (const s of result.enriched) {
|
|
2085
|
+
console.log(` ${s.sessionId}`);
|
|
2086
|
+
if (s.topics.length > 0) console.log(` topics: ${s.topics.join(", ")}`);
|
|
2087
|
+
if (s.toolsUsed.length > 0) console.log(` tools: ${s.toolsUsed.join(", ")}`);
|
|
2088
|
+
if (s.primitivesReferenced.length > 0) console.log(` refs: ${s.primitivesReferenced.join(", ")}`);
|
|
2089
|
+
console.log(` ${s.tokenCount} tokens, ${s.stepCount} steps, ${s.model}
|
|
2090
|
+
`);
|
|
2091
|
+
}
|
|
2092
|
+
});
|
|
2093
|
+
program.command("suggest").description("Suggest capabilities (skills/playbooks) for frequent uncovered session topics").option("-d, --dir <path>", "Harness directory", ".").option("--min-frequency <n>", "Minimum topic frequency", "3").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
2094
|
+
const { suggestCapabilities } = await import("../intelligence-HJOCA4SJ.js");
|
|
2095
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
2096
|
+
const dir = resolve(opts.dir);
|
|
2097
|
+
requireHarness(dir);
|
|
2098
|
+
let config;
|
|
2099
|
+
try {
|
|
2100
|
+
config = loadConfig(dir);
|
|
2101
|
+
} catch (err) {
|
|
2102
|
+
if (process.env.DEBUG) console.error(`Config load skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
2103
|
+
}
|
|
2104
|
+
const result = suggestCapabilities(dir, config, {
|
|
2105
|
+
minFrequency: parseInt(opts.minFrequency, 10)
|
|
2106
|
+
});
|
|
2107
|
+
if (opts.json) {
|
|
2108
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
console.log(`
|
|
2112
|
+
Analyzed ${result.topicsAnalyzed} topic(s) from ${result.sessionsScanned} session(s)
|
|
2113
|
+
`);
|
|
2114
|
+
if (result.suggestions.length === 0) {
|
|
2115
|
+
console.log("No capability gaps found.\n");
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
console.log(`${result.suggestions.length} suggestion(s):
|
|
2119
|
+
`);
|
|
2120
|
+
for (const s of result.suggestions) {
|
|
2121
|
+
console.log(` "${s.topic}" \u2014 ${s.frequency}x in sessions`);
|
|
2122
|
+
console.log(` Suggest: create a ${s.suggestedType}
|
|
2123
|
+
`);
|
|
2124
|
+
}
|
|
2125
|
+
});
|
|
2126
|
+
program.command("agents").description("List available sub-agents").option("-d, --dir <path>", "Harness directory", ".").action(async (opts) => {
|
|
2127
|
+
const { listAgents } = await import("../delegate-VJCJLYEK.js");
|
|
2128
|
+
const dir = resolve(opts.dir);
|
|
2129
|
+
const agents = listAgents(dir);
|
|
2130
|
+
if (agents.length === 0) {
|
|
2131
|
+
console.log("\nNo agents defined.");
|
|
2132
|
+
console.log("Create agent files in agents/ to enable delegation.\n");
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
console.log(`
|
|
2136
|
+
${agents.length} agent(s) available:
|
|
2137
|
+
`);
|
|
2138
|
+
for (const agent of agents) {
|
|
2139
|
+
const status = agent.status === "active" ? "" : ` [${agent.status}]`;
|
|
2140
|
+
console.log(` ${agent.id}${status}`);
|
|
2141
|
+
if (agent.l0) console.log(` ${agent.l0}`);
|
|
2142
|
+
if (agent.tags.length > 0) console.log(` tags: ${agent.tags.join(", ")}`);
|
|
2143
|
+
console.log();
|
|
2144
|
+
}
|
|
2145
|
+
});
|
|
2146
|
+
program.command("delegate <agent-id> <prompt>").description("Delegate a prompt to a sub-agent").option("-d, --dir <path>", "Harness directory", ".").option("-m, --model <model>", "Model override (or alias: gemma, qwen, glm, claude)").option("-s, --stream", "Stream output", false).action(async (agentId, prompt, opts) => {
|
|
2147
|
+
const dir = resolve(opts.dir);
|
|
2148
|
+
loadEnvFromDir(dir);
|
|
2149
|
+
requireHarness(dir);
|
|
2150
|
+
const modelId = resolveModel(opts.model);
|
|
2151
|
+
const delegateOpts = {
|
|
2152
|
+
harnessDir: dir,
|
|
2153
|
+
agentId,
|
|
2154
|
+
prompt,
|
|
2155
|
+
modelOverride: modelId
|
|
2156
|
+
};
|
|
2157
|
+
try {
|
|
2158
|
+
console.error(`[delegate] Invoking agent "${agentId}"${opts.stream ? " (streaming)" : ""}...`);
|
|
2159
|
+
if (opts.stream) {
|
|
2160
|
+
const { delegateStream } = await import("../delegate-VJCJLYEK.js");
|
|
2161
|
+
const result = delegateStream(delegateOpts);
|
|
2162
|
+
process.stdout.write("\n");
|
|
2163
|
+
for await (const chunk of result.textStream) {
|
|
2164
|
+
process.stdout.write(chunk);
|
|
2165
|
+
}
|
|
2166
|
+
process.stdout.write("\n\n");
|
|
2167
|
+
console.error(
|
|
2168
|
+
`[delegate] Agent: ${result.agentId} | session: ${result.sessionId}`
|
|
2169
|
+
);
|
|
2170
|
+
} else {
|
|
2171
|
+
const { delegateTo } = await import("../delegate-VJCJLYEK.js");
|
|
2172
|
+
const result = await delegateTo(delegateOpts);
|
|
2173
|
+
console.log("\n" + result.text + "\n");
|
|
2174
|
+
console.error(
|
|
2175
|
+
`[delegate] Agent: ${result.agentId} | ${result.usage.totalTokens} tokens | session: ${result.sessionId}`
|
|
2176
|
+
);
|
|
2177
|
+
}
|
|
2178
|
+
} catch (err) {
|
|
2179
|
+
console.error(`Error: ${formatError(err)}`);
|
|
2180
|
+
process.exit(1);
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
var costsCmd = program.command("costs").description("View and manage API spending");
|
|
2184
|
+
costsCmd.command("show").description("Show spending summary (default: today)").option("-d, --dir <path>", "Harness directory", ".").option("--from <date>", "Start date (YYYY-MM-DD)").option("--to <date>", "End date (YYYY-MM-DD)").option("--json", "Output as JSON").action(async (opts) => {
|
|
2185
|
+
const { getSpending } = await import("../cost-tracker-RS3W7SVY.js");
|
|
2186
|
+
const dir = resolve(opts.dir);
|
|
2187
|
+
requireHarness(dir);
|
|
2188
|
+
const summary = getSpending(dir, opts.from, opts.to);
|
|
2189
|
+
const label = opts.from || opts.to ? `${opts.from ?? "start"} to ${opts.to ?? "now"}` : "today";
|
|
2190
|
+
if (opts.json) {
|
|
2191
|
+
console.log(JSON.stringify({ period: label, ...summary }, null, 2));
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
if (summary.entries === 0) {
|
|
2195
|
+
console.log(`
|
|
2196
|
+
No spending recorded for ${label}.
|
|
2197
|
+
`);
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
console.log(`
|
|
2201
|
+
Spending \u2014 ${label}
|
|
2202
|
+
`);
|
|
2203
|
+
console.log(` Total: $${summary.total_cost_usd.toFixed(6)}`);
|
|
2204
|
+
console.log(` Entries: ${summary.entries}`);
|
|
2205
|
+
console.log(` Tokens: ${summary.total_input_tokens.toLocaleString()} in / ${summary.total_output_tokens.toLocaleString()} out`);
|
|
2206
|
+
const models = Object.entries(summary.by_model);
|
|
2207
|
+
if (models.length > 0) {
|
|
2208
|
+
console.log(`
|
|
2209
|
+
By model:`);
|
|
2210
|
+
for (const [model, data] of models.sort((a, b) => b[1].cost_usd - a[1].cost_usd)) {
|
|
2211
|
+
console.log(` ${model}: $${data.cost_usd.toFixed(6)} (${data.count} calls)`);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
const providers = Object.entries(summary.by_provider);
|
|
2215
|
+
if (providers.length > 0) {
|
|
2216
|
+
console.log(`
|
|
2217
|
+
By provider:`);
|
|
2218
|
+
for (const [provider, data] of providers.sort((a, b) => b[1].cost_usd - a[1].cost_usd)) {
|
|
2219
|
+
console.log(` ${provider}: $${data.cost_usd.toFixed(6)} (${data.count} calls)`);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
console.log();
|
|
2223
|
+
});
|
|
2224
|
+
costsCmd.command("budget").description("Check spending against budget limits").option("-d, --dir <path>", "Harness directory", ".").option("--daily <usd>", "Daily budget limit in USD").option("--monthly <usd>", "Monthly budget limit in USD").action(async (opts) => {
|
|
2225
|
+
const { checkBudget } = await import("../cost-tracker-RS3W7SVY.js");
|
|
2226
|
+
const dir = resolve(opts.dir);
|
|
2227
|
+
requireHarness(dir);
|
|
2228
|
+
const dailyLimit = opts.daily ? parseFloat(opts.daily) : void 0;
|
|
2229
|
+
const monthlyLimit = opts.monthly ? parseFloat(opts.monthly) : void 0;
|
|
2230
|
+
if (dailyLimit === void 0 && monthlyLimit === void 0) {
|
|
2231
|
+
console.error("Error: Specify at least --daily or --monthly budget limit.");
|
|
2232
|
+
process.exit(1);
|
|
2233
|
+
}
|
|
2234
|
+
const status = checkBudget(dir, {
|
|
2235
|
+
daily_limit_usd: dailyLimit,
|
|
2236
|
+
monthly_limit_usd: monthlyLimit
|
|
2237
|
+
});
|
|
2238
|
+
console.log("\nBudget Status\n");
|
|
2239
|
+
if (status.daily_limit_usd !== null) {
|
|
2240
|
+
const pct = status.daily_pct !== null ? ` (${status.daily_pct.toFixed(1)}%)` : "";
|
|
2241
|
+
console.log(` Daily: $${status.daily_spent_usd.toFixed(6)} / $${status.daily_limit_usd.toFixed(2)}${pct}`);
|
|
2242
|
+
if (status.daily_remaining_usd !== null) {
|
|
2243
|
+
console.log(` Remaining: $${status.daily_remaining_usd.toFixed(6)}`);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
if (status.monthly_limit_usd !== null) {
|
|
2247
|
+
const pct = status.monthly_pct !== null ? ` (${status.monthly_pct.toFixed(1)}%)` : "";
|
|
2248
|
+
console.log(` Monthly: $${status.monthly_spent_usd.toFixed(6)} / $${status.monthly_limit_usd.toFixed(2)}${pct}`);
|
|
2249
|
+
if (status.monthly_remaining_usd !== null) {
|
|
2250
|
+
console.log(` Remaining: $${status.monthly_remaining_usd.toFixed(6)}`);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
if (status.alerts.length > 0) {
|
|
2254
|
+
console.log("\n Alerts:");
|
|
2255
|
+
for (const alert of status.alerts) {
|
|
2256
|
+
console.log(` \u26A0 ${alert}`);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
console.log();
|
|
2260
|
+
if (status.alerts.some((a) => a.includes("exceeded"))) {
|
|
2261
|
+
process.exit(1);
|
|
2262
|
+
}
|
|
2263
|
+
});
|
|
2264
|
+
costsCmd.command("clear").description("Clear all cost records").option("-d, --dir <path>", "Harness directory", ".").option("--model <id>", "Clear only entries for this model").action(async (opts) => {
|
|
2265
|
+
const { clearCosts } = await import("../cost-tracker-RS3W7SVY.js");
|
|
2266
|
+
const dir = resolve(opts.dir);
|
|
2267
|
+
requireHarness(dir);
|
|
2268
|
+
const removed = clearCosts(dir, opts.model);
|
|
2269
|
+
if (opts.model) {
|
|
2270
|
+
console.log(`Cleared ${removed} cost entry(ies) for model "${opts.model}".`);
|
|
2271
|
+
} else {
|
|
2272
|
+
console.log(`Cleared ${removed} total cost entry(ies).`);
|
|
2273
|
+
}
|
|
2274
|
+
});
|
|
2275
|
+
program.command("health").description("Show system health status and metrics").option("-d, --dir <path>", "Harness directory", ".").option("--reset", "Reset health metrics", false).option("--json", "Output as JSON").action(async (opts) => {
|
|
2276
|
+
const { getHealthStatus, resetHealth } = await import("../health-NZ6WNIMV.js");
|
|
2277
|
+
const dir = resolve(opts.dir);
|
|
2278
|
+
requireHarness(dir);
|
|
2279
|
+
if (opts.reset) {
|
|
2280
|
+
resetHealth(dir);
|
|
2281
|
+
console.log("Health metrics reset.");
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
const health = getHealthStatus(dir);
|
|
2285
|
+
if (opts.json) {
|
|
2286
|
+
console.log(JSON.stringify(health, null, 2));
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
const statusIcon = health.status === "healthy" ? "OK" : health.status === "degraded" ? "WARN" : "FAIL";
|
|
2290
|
+
console.log(`
|
|
2291
|
+
Health: ${statusIcon} (${health.status})
|
|
2292
|
+
`);
|
|
2293
|
+
for (const check of health.checks) {
|
|
2294
|
+
const icon = check.status === "pass" ? "pass" : check.status === "warn" ? "WARN" : "FAIL";
|
|
2295
|
+
console.log(` [${icon}] ${check.name}: ${check.message}`);
|
|
2296
|
+
}
|
|
2297
|
+
console.log(`
|
|
2298
|
+
Metrics:`);
|
|
2299
|
+
console.log(` Total runs: ${health.metrics.totalRuns}`);
|
|
2300
|
+
console.log(` Successes: ${health.metrics.totalSuccesses}`);
|
|
2301
|
+
console.log(` Failures: ${health.metrics.totalFailures}`);
|
|
2302
|
+
console.log(` Consecutive: ${health.metrics.consecutiveFailures} failure(s)`);
|
|
2303
|
+
if (health.metrics.bootedAt) {
|
|
2304
|
+
console.log(` Booted at: ${health.metrics.bootedAt}`);
|
|
2305
|
+
}
|
|
2306
|
+
if (health.metrics.lastSuccessfulRun) {
|
|
2307
|
+
console.log(` Last success: ${health.metrics.lastSuccessfulRun}`);
|
|
2308
|
+
}
|
|
2309
|
+
if (health.metrics.lastFailedRun) {
|
|
2310
|
+
console.log(` Last failure: ${health.metrics.lastFailedRun}`);
|
|
2311
|
+
}
|
|
2312
|
+
if (health.metrics.lastError) {
|
|
2313
|
+
console.log(` Last error: ${health.metrics.lastError.slice(0, 120)}`);
|
|
2314
|
+
}
|
|
2315
|
+
if (health.costToday > 0 || health.costThisMonth > 0) {
|
|
2316
|
+
console.log(`
|
|
2317
|
+
Spending:`);
|
|
2318
|
+
console.log(` Today: $${health.costToday.toFixed(6)}`);
|
|
2319
|
+
console.log(` Month: $${health.costThisMonth.toFixed(6)}`);
|
|
2320
|
+
}
|
|
2321
|
+
console.log();
|
|
2322
|
+
});
|
|
2323
|
+
var rateLimitCmd = program.command("ratelimit").description("View and manage rate limit state");
|
|
2324
|
+
rateLimitCmd.command("status").description("Show current rate limit usage for a key").argument("<key>", "Rate limit key (e.g., tool:github, model:claude)").option("-d, --dir <path>", "Harness directory", ".").option("--window <ms>", "Window size in ms", "3600000").action(async (key, opts) => {
|
|
2325
|
+
const { getUsage } = await import("../rate-limiter-RLRVM325.js");
|
|
2326
|
+
const dir = resolve(opts.dir);
|
|
2327
|
+
requireHarness(dir);
|
|
2328
|
+
const windowMs = parseInt(opts.window, 10) || 36e5;
|
|
2329
|
+
const usage = getUsage(dir, key, windowMs);
|
|
2330
|
+
const windowLabel = windowMs >= 36e5 ? `${windowMs / 36e5}h` : windowMs >= 6e4 ? `${windowMs / 6e4}m` : `${windowMs}ms`;
|
|
2331
|
+
console.log(`
|
|
2332
|
+
Rate limit: ${key} (${windowLabel} window)
|
|
2333
|
+
`);
|
|
2334
|
+
console.log(` Requests: ${usage.count}`);
|
|
2335
|
+
if (usage.oldest !== null) {
|
|
2336
|
+
console.log(` Oldest: ${new Date(usage.oldest).toISOString()}`);
|
|
2337
|
+
}
|
|
2338
|
+
if (usage.newest !== null) {
|
|
2339
|
+
console.log(` Newest: ${new Date(usage.newest).toISOString()}`);
|
|
2340
|
+
}
|
|
2341
|
+
console.log();
|
|
2342
|
+
});
|
|
2343
|
+
rateLimitCmd.command("clear").description("Clear rate limit events").option("-d, --dir <path>", "Harness directory", ".").option("--key <key>", "Clear only this key (clears all if omitted)").action(async (opts) => {
|
|
2344
|
+
const { clearRateLimits } = await import("../rate-limiter-RLRVM325.js");
|
|
2345
|
+
const dir = resolve(opts.dir);
|
|
2346
|
+
requireHarness(dir);
|
|
2347
|
+
const removed = clearRateLimits(dir, opts.key);
|
|
2348
|
+
if (opts.key) {
|
|
2349
|
+
console.log(`Cleared ${removed} event(s) for key "${opts.key}".`);
|
|
2350
|
+
} else {
|
|
2351
|
+
console.log(`Cleared ${removed} total event(s).`);
|
|
2352
|
+
}
|
|
2353
|
+
});
|
|
2354
|
+
var mcpCmd = program.command("mcp").description("Manage MCP (Model Context Protocol) server connections");
|
|
2355
|
+
mcpCmd.command("list").description("List configured MCP servers and their status").option("-d, --dir <path>", "Harness directory", ".").action(async (opts) => {
|
|
2356
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
2357
|
+
const { validateMcpConfig } = await import("../mcp-WTQJJZAO.js");
|
|
2358
|
+
const dir = resolve(opts.dir);
|
|
2359
|
+
requireHarness(dir);
|
|
2360
|
+
const config = loadConfig(dir);
|
|
2361
|
+
const servers = config.mcp?.servers ?? {};
|
|
2362
|
+
const entries = Object.entries(servers);
|
|
2363
|
+
if (entries.length === 0) {
|
|
2364
|
+
console.log("\nNo MCP servers configured.");
|
|
2365
|
+
console.log("Add servers to config.yaml under mcp.servers:\n");
|
|
2366
|
+
console.log(" mcp:");
|
|
2367
|
+
console.log(" servers:");
|
|
2368
|
+
console.log(" my-server:");
|
|
2369
|
+
console.log(" transport: stdio");
|
|
2370
|
+
console.log(" command: npx");
|
|
2371
|
+
console.log(' args: ["-y", "@my/mcp-server"]');
|
|
2372
|
+
console.log();
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
const validationErrors = validateMcpConfig(config);
|
|
2376
|
+
const errorMap = new Map(validationErrors.map((e) => [e.server, e.error]));
|
|
2377
|
+
console.log(`
|
|
2378
|
+
${entries.length} MCP server(s) configured:
|
|
2379
|
+
`);
|
|
2380
|
+
for (const [name, serverConfig] of entries) {
|
|
2381
|
+
const enabled = serverConfig.enabled !== false;
|
|
2382
|
+
const status = !enabled ? "disabled" : errorMap.has(name) ? "invalid" : "configured";
|
|
2383
|
+
const icon = status === "configured" ? "+" : status === "disabled" ? "-" : "!";
|
|
2384
|
+
console.log(` [${icon}] ${name} (${serverConfig.transport})`);
|
|
2385
|
+
if (serverConfig.transport === "stdio" && serverConfig.command) {
|
|
2386
|
+
const args = serverConfig.args?.join(" ") ?? "";
|
|
2387
|
+
console.log(` Command: ${serverConfig.command} ${args}`.trimEnd());
|
|
2388
|
+
} else if (serverConfig.url) {
|
|
2389
|
+
console.log(` URL: ${serverConfig.url}`);
|
|
2390
|
+
}
|
|
2391
|
+
if (errorMap.has(name)) {
|
|
2392
|
+
console.log(` Error: ${errorMap.get(name)}`);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
console.log();
|
|
2396
|
+
});
|
|
2397
|
+
mcpCmd.command("test").description("Test MCP server connections and list available tools").option("-d, --dir <path>", "Harness directory", ".").option("-s, --server <name>", "Test only a specific server").action(async (opts) => {
|
|
2398
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
2399
|
+
const { createMcpManager } = await import("../mcp-WTQJJZAO.js");
|
|
2400
|
+
const dir = resolve(opts.dir);
|
|
2401
|
+
loadEnvFromDir(dir);
|
|
2402
|
+
requireHarness(dir);
|
|
2403
|
+
const config = loadConfig(dir);
|
|
2404
|
+
const servers = config.mcp?.servers ?? {};
|
|
2405
|
+
if (Object.keys(servers).length === 0) {
|
|
2406
|
+
console.log("\nNo MCP servers configured. Run `harness mcp list` for setup instructions.\n");
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
let testConfig = config;
|
|
2410
|
+
if (opts.server) {
|
|
2411
|
+
if (!servers[opts.server]) {
|
|
2412
|
+
console.error(`Error: MCP server "${opts.server}" not found in config.`);
|
|
2413
|
+
console.error(`Available: ${Object.keys(servers).join(", ")}`);
|
|
2414
|
+
process.exit(1);
|
|
2415
|
+
}
|
|
2416
|
+
testConfig = {
|
|
2417
|
+
...config,
|
|
2418
|
+
mcp: { servers: { [opts.server]: servers[opts.server] } }
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
console.log(`
|
|
2422
|
+
Testing MCP server connections...
|
|
2423
|
+
`);
|
|
2424
|
+
const manager = createMcpManager(testConfig);
|
|
2425
|
+
try {
|
|
2426
|
+
await manager.connect();
|
|
2427
|
+
const summaries = manager.getSummaries();
|
|
2428
|
+
for (const summary of summaries) {
|
|
2429
|
+
if (!summary.enabled) {
|
|
2430
|
+
console.log(` [-] ${summary.name}: disabled`);
|
|
2431
|
+
continue;
|
|
2432
|
+
}
|
|
2433
|
+
if (summary.connected) {
|
|
2434
|
+
console.log(` [OK] ${summary.name}: connected, ${summary.toolCount} tool(s)`);
|
|
2435
|
+
if (summary.toolNames.length > 0) {
|
|
2436
|
+
for (const toolName of summary.toolNames) {
|
|
2437
|
+
console.log(` - ${toolName}`);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
} else {
|
|
2441
|
+
console.log(` [FAIL] ${summary.name}: ${summary.error ?? "unknown error"}`);
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
const totalTools = summaries.reduce((sum, s) => sum + s.toolCount, 0);
|
|
2445
|
+
const connectedCount = summaries.filter((s) => s.connected).length;
|
|
2446
|
+
console.log(`
|
|
2447
|
+
${connectedCount}/${summaries.length} server(s) connected, ${totalTools} total tool(s)
|
|
2448
|
+
`);
|
|
2449
|
+
} catch (err) {
|
|
2450
|
+
console.error(`Error: ${formatError(err)}`);
|
|
2451
|
+
process.exit(1);
|
|
2452
|
+
} finally {
|
|
2453
|
+
await manager.close();
|
|
2454
|
+
}
|
|
2455
|
+
});
|
|
2456
|
+
mcpCmd.command("discover").description("Scan for MCP servers from other tools (Claude Desktop, Cursor, VS Code, etc.)").option("--json", "Output raw JSON", false).action(async (opts) => {
|
|
2457
|
+
const { discoverMcpServers, discoveredServersToYaml, getScannedTools } = await import("../mcp-discovery-WPAQFL6S.js");
|
|
2458
|
+
const discovery = discoverMcpServers();
|
|
2459
|
+
if (opts.json) {
|
|
2460
|
+
console.log(JSON.stringify(discovery, null, 2));
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
const tools = getScannedTools();
|
|
2464
|
+
console.log(`
|
|
2465
|
+
Scanned ${tools.length} tools: ${tools.join(", ")}
|
|
2466
|
+
`);
|
|
2467
|
+
if (discovery.sourcesFound === 0) {
|
|
2468
|
+
console.log("No tool configs found on this machine.\n");
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
console.log(`Found config files from ${discovery.sourcesFound} tool(s):
|
|
2472
|
+
`);
|
|
2473
|
+
for (const source of discovery.sources) {
|
|
2474
|
+
if (!source.found) continue;
|
|
2475
|
+
const status = source.error ? `error: ${source.error}` : `${source.servers.length} server(s)`;
|
|
2476
|
+
console.log(` ${source.tool}: ${status}`);
|
|
2477
|
+
for (const server of source.servers) {
|
|
2478
|
+
console.log(` - ${server.name} (${server.transport}${server.command ? `: ${server.command}` : ""}${server.url ? `: ${server.url}` : ""})`);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
if (discovery.totalServers > 0) {
|
|
2482
|
+
console.log(`
|
|
2483
|
+
${discovery.totalServers} unique server(s) after dedup:
|
|
2484
|
+
`);
|
|
2485
|
+
console.log(discoveredServersToYaml(discovery.servers));
|
|
2486
|
+
console.log("\nAdd the above to your config.yaml to use these servers.\n");
|
|
2487
|
+
} else {
|
|
2488
|
+
console.log("\nNo MCP servers found in any tool configs.\n");
|
|
2489
|
+
}
|
|
2490
|
+
});
|
|
2491
|
+
mcpCmd.command("search <query>").description("Search the MCP registry for available servers").option("-n, --limit <number>", "Max results", "10").option("--json", "Output raw JSON", false).action(async (query, opts) => {
|
|
2492
|
+
const { searchRegistry, formatRegistryServer } = await import("../mcp-installer-6O2XXD3V.js");
|
|
2493
|
+
try {
|
|
2494
|
+
const result = await searchRegistry(query, { limit: parseInt(opts.limit, 10) });
|
|
2495
|
+
if (opts.json) {
|
|
2496
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2497
|
+
return;
|
|
2498
|
+
}
|
|
2499
|
+
if (result.servers.length === 0) {
|
|
2500
|
+
console.log(`
|
|
2501
|
+
No servers found for "${query}".
|
|
2502
|
+
`);
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
console.log(`
|
|
2506
|
+
${result.servers.length} server(s) found for "${query}":
|
|
2507
|
+
`);
|
|
2508
|
+
for (const entry of result.servers) {
|
|
2509
|
+
console.log(formatRegistryServer(entry));
|
|
2510
|
+
console.log();
|
|
2511
|
+
}
|
|
2512
|
+
} catch (err) {
|
|
2513
|
+
console.error(`Error: ${formatError(err)}`);
|
|
2514
|
+
process.exit(1);
|
|
2515
|
+
}
|
|
2516
|
+
});
|
|
2517
|
+
mcpCmd.command("install <query>").description("Install an MCP server from the registry into config.yaml").option("-d, --dir <path>", "Harness directory", ".").option("-n, --name <name>", "Custom name for the server in config").option("--force", "Overwrite if server already exists", false).option("--skip-test", "Skip connection testing", false).option("--skip-docs", "Skip tool doc generation", false).option("--json", "Output raw JSON", false).action(async (query, opts) => {
|
|
2518
|
+
const { installMcpServer } = await import("../mcp-installer-6O2XXD3V.js");
|
|
2519
|
+
const dir = resolve(opts.dir);
|
|
2520
|
+
loadEnvFromDir(dir);
|
|
2521
|
+
requireHarness(dir);
|
|
2522
|
+
try {
|
|
2523
|
+
console.log(`
|
|
2524
|
+
Searching for "${query}" in MCP registry...`);
|
|
2525
|
+
const result = await installMcpServer(query, {
|
|
2526
|
+
dir,
|
|
2527
|
+
name: opts.name,
|
|
2528
|
+
force: opts.force,
|
|
2529
|
+
skipTest: opts.skipTest,
|
|
2530
|
+
skipDocs: opts.skipDocs
|
|
2531
|
+
});
|
|
2532
|
+
if (opts.json) {
|
|
2533
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
if (!result.installed) {
|
|
2537
|
+
console.error(`
|
|
2538
|
+
Failed: ${result.error}`);
|
|
2539
|
+
process.exit(1);
|
|
2540
|
+
}
|
|
2541
|
+
console.log(`
|
|
2542
|
+
Installed MCP server: ${result.name}`);
|
|
2543
|
+
if (result.server?.registryName) {
|
|
2544
|
+
console.log(` registry: ${result.server.registryName}`);
|
|
2545
|
+
}
|
|
2546
|
+
if (result.server?.description) {
|
|
2547
|
+
const desc = result.server.description.length > 80 ? result.server.description.slice(0, 77) + "..." : result.server.description;
|
|
2548
|
+
console.log(` description: ${desc}`);
|
|
2549
|
+
}
|
|
2550
|
+
console.log(` transport: ${result.server?.config.transport ?? "unknown"}`);
|
|
2551
|
+
console.log(` -> Added to config.yaml`);
|
|
2552
|
+
if (result.pendingEnvVars.length > 0) {
|
|
2553
|
+
console.log(`
|
|
2554
|
+
Required environment variables:`);
|
|
2555
|
+
for (const ev of result.pendingEnvVars) {
|
|
2556
|
+
const desc = ev.description ? ` \u2014 ${ev.description}` : "";
|
|
2557
|
+
console.log(` ${ev.name}${desc}`);
|
|
2558
|
+
}
|
|
2559
|
+
console.log(` -> Set these in .env or config.yaml env section`);
|
|
2560
|
+
}
|
|
2561
|
+
if (result.connectionTest) {
|
|
2562
|
+
if (result.connectionTest.connected) {
|
|
2563
|
+
console.log(`
|
|
2564
|
+
[OK] Connected: ${result.connectionTest.toolCount} tool(s)`);
|
|
2565
|
+
for (const toolName of result.connectionTest.toolNames) {
|
|
2566
|
+
console.log(` - ${toolName}`);
|
|
2567
|
+
}
|
|
2568
|
+
} else {
|
|
2569
|
+
console.log(`
|
|
2570
|
+
[WARN] Connection test failed: ${result.connectionTest.error}`);
|
|
2571
|
+
if (result.pendingEnvVars.length > 0) {
|
|
2572
|
+
console.log(` (This is expected if required env vars are not yet set)`);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
if (result.generatedDocs.length > 0) {
|
|
2577
|
+
console.log(`
|
|
2578
|
+
Generated tool docs:`);
|
|
2579
|
+
for (const doc of result.generatedDocs) {
|
|
2580
|
+
console.log(` ${doc}`);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
console.log();
|
|
2584
|
+
} catch (err) {
|
|
2585
|
+
console.error(`Error: ${formatError(err)}`);
|
|
2586
|
+
process.exit(1);
|
|
2587
|
+
}
|
|
2588
|
+
});
|
|
2589
|
+
var discoverCmd = program.command("discover").description("Discover environment variables, project context, and MCP servers");
|
|
2590
|
+
discoverCmd.command("env").description("Scan .env files for API keys and suggest MCP servers").option("-d, --dir <path>", "Directory to scan", ".").option("--json", "Output raw JSON", false).action(async (opts) => {
|
|
2591
|
+
const { discoverEnvKeys } = await import("../env-discovery-2BLVMAIM.js");
|
|
2592
|
+
const dir = resolve(opts.dir);
|
|
2593
|
+
const result = discoverEnvKeys({ dir });
|
|
2594
|
+
if (opts.json) {
|
|
2595
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
console.log(`
|
|
2599
|
+
Scanned ${result.filesScanned.length} file(s)
|
|
2600
|
+
`);
|
|
2601
|
+
if (result.keys.length === 0) {
|
|
2602
|
+
console.log("No API keys detected in .env files.\n");
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
console.log(`${result.keys.length} API key(s) detected:
|
|
2606
|
+
`);
|
|
2607
|
+
for (const key of result.keys) {
|
|
2608
|
+
const status = key.hasValue ? "[set]" : "[empty]";
|
|
2609
|
+
const sug = key.suggestion ? ` \u2192 ${key.suggestion}` : "";
|
|
2610
|
+
console.log(` ${status} ${key.name} (${key.source})${sug}`);
|
|
2611
|
+
}
|
|
2612
|
+
if (result.suggestions.length > 0) {
|
|
2613
|
+
console.log(`
|
|
2614
|
+
Suggested MCP servers:
|
|
2615
|
+
`);
|
|
2616
|
+
for (const sug of result.suggestions) {
|
|
2617
|
+
console.log(` ${sug.message}`);
|
|
2618
|
+
console.log(` Install: harness mcp install "${sug.serverQuery}"`);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
console.log();
|
|
2622
|
+
});
|
|
2623
|
+
discoverCmd.command("project").description("Scan project files to detect tech stack and suggest rules/skills").option("-d, --dir <path>", "Project directory to scan", ".").option("--json", "Output raw JSON", false).action(async (opts) => {
|
|
2624
|
+
const { discoverProjectContext } = await import("../project-discovery-C4UMD7JI.js");
|
|
2625
|
+
const dir = resolve(opts.dir);
|
|
2626
|
+
const result = discoverProjectContext({ dir });
|
|
2627
|
+
if (opts.json) {
|
|
2628
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
if (result.signals.length === 0) {
|
|
2632
|
+
console.log("\nNo project signals detected.\n");
|
|
2633
|
+
return;
|
|
2634
|
+
}
|
|
2635
|
+
console.log(`
|
|
2636
|
+
Detected ${result.signals.length} signal(s):
|
|
2637
|
+
`);
|
|
2638
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
2639
|
+
for (const signal of result.signals) {
|
|
2640
|
+
const list = byCategory.get(signal.category) ?? [];
|
|
2641
|
+
list.push(signal);
|
|
2642
|
+
byCategory.set(signal.category, list);
|
|
2643
|
+
}
|
|
2644
|
+
for (const [category, signals] of byCategory) {
|
|
2645
|
+
console.log(` ${category}: ${signals.map((s) => s.name).join(", ")}`);
|
|
2646
|
+
}
|
|
2647
|
+
if (result.suggestions.length > 0) {
|
|
2648
|
+
console.log(`
|
|
2649
|
+
Suggestions:
|
|
2650
|
+
`);
|
|
2651
|
+
for (const sug of result.suggestions) {
|
|
2652
|
+
if (sug.type === "mcp-server") {
|
|
2653
|
+
console.log(` [mcp] ${sug.message}`);
|
|
2654
|
+
console.log(` Install: harness mcp install "${sug.target}"`);
|
|
2655
|
+
} else {
|
|
2656
|
+
console.log(` [${sug.type}] ${sug.message}`);
|
|
2657
|
+
console.log(` Create: ${sug.target}`);
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
console.log();
|
|
2662
|
+
});
|
|
2663
|
+
var generateCmd = program.command("generate").description("Auto-generate harness files");
|
|
2664
|
+
generateCmd.command("system").description("Regenerate SYSTEM.md from actual directory structure").option("-d, --dir <path>", "Harness directory", ".").action(async (opts) => {
|
|
2665
|
+
const { generateSystemMd } = await import("../scaffold-A3VRRCBV.js");
|
|
2666
|
+
const dir = resolve(opts.dir);
|
|
2667
|
+
requireHarness(dir);
|
|
2668
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
2669
|
+
const config = loadConfig(dir);
|
|
2670
|
+
const content = generateSystemMd(dir, config.agent.name);
|
|
2671
|
+
const { writeFileSync } = await import("fs");
|
|
2672
|
+
writeFileSync(join(dir, "SYSTEM.md"), content, "utf-8");
|
|
2673
|
+
console.log(`
|
|
2674
|
+
\u2713 SYSTEM.md regenerated from directory structure
|
|
2675
|
+
`);
|
|
2676
|
+
});
|
|
2677
|
+
program.command("dashboard").description("Show a unified dashboard of health, costs, sessions, workflows, and storage").option("-d, --dir <path>", "Harness directory", ".").option("--json", "Output raw JSON snapshot", false).option("--watch", "Refresh every N seconds", false).option("--interval <seconds>", "Watch refresh interval in seconds", "5").action(async (opts) => {
|
|
2678
|
+
const { collectSnapshot, formatDashboard } = await import("../telemetry-UC6PBXC7.js");
|
|
2679
|
+
const dir = resolve(opts.dir);
|
|
2680
|
+
requireHarness(dir);
|
|
2681
|
+
if (opts.json) {
|
|
2682
|
+
const snapshot = collectSnapshot(dir);
|
|
2683
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
const showDashboard = () => {
|
|
2687
|
+
const snapshot = collectSnapshot(dir);
|
|
2688
|
+
const output = formatDashboard(snapshot);
|
|
2689
|
+
if (opts.watch) {
|
|
2690
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
2691
|
+
console.log(`
|
|
2692
|
+
Agent Harness Dashboard (refreshing every ${opts.interval}s \u2014 Ctrl+C to stop)
|
|
2693
|
+
`);
|
|
2694
|
+
console.log(` ${snapshot.timestamp}
|
|
2695
|
+
`);
|
|
2696
|
+
} else {
|
|
2697
|
+
console.log(`
|
|
2698
|
+
Agent Harness Dashboard
|
|
2699
|
+
`);
|
|
2700
|
+
}
|
|
2701
|
+
console.log(output);
|
|
2702
|
+
};
|
|
2703
|
+
showDashboard();
|
|
2704
|
+
if (opts.watch) {
|
|
2705
|
+
const intervalMs = (parseInt(opts.interval, 10) || 5) * 1e3;
|
|
2706
|
+
const timer = setInterval(showDashboard, intervalMs);
|
|
2707
|
+
const cleanup = () => {
|
|
2708
|
+
clearInterval(timer);
|
|
2709
|
+
process.exit(0);
|
|
2710
|
+
};
|
|
2711
|
+
process.on("SIGINT", cleanup);
|
|
2712
|
+
process.on("SIGTERM", cleanup);
|
|
2713
|
+
}
|
|
2714
|
+
});
|
|
2715
|
+
var intelligenceCmd = program.command("intelligence").description("Intelligence and learning analysis tools");
|
|
2716
|
+
intelligenceCmd.command("promote").description("Auto-promote instinct candidates that appear 3+ times across journals").option("-d, --dir <dir>", "Harness directory", ".").option("--threshold <n>", "Minimum occurrences to promote", "3").option("--install", "Install promoted instincts as .md files").option("--json", "Output as JSON").action(async (opts) => {
|
|
2717
|
+
const dir = resolve(opts.dir);
|
|
2718
|
+
loadEnvFromDir(dir);
|
|
2719
|
+
const { autoPromoteInstincts } = await import("../intelligence-HJOCA4SJ.js");
|
|
2720
|
+
const result = autoPromoteInstincts(dir, {
|
|
2721
|
+
threshold: parseInt(opts.threshold, 10),
|
|
2722
|
+
install: opts.install ?? false
|
|
2723
|
+
});
|
|
2724
|
+
if (opts.json) {
|
|
2725
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2726
|
+
} else {
|
|
2727
|
+
console.log(`Scanned ${result.journalsScanned} journals`);
|
|
2728
|
+
if (result.patterns.length === 0) {
|
|
2729
|
+
console.log("No patterns meeting threshold found.");
|
|
2730
|
+
} else {
|
|
2731
|
+
console.log(`
|
|
2732
|
+
Patterns (${result.patterns.length}):`);
|
|
2733
|
+
for (const p of result.patterns) {
|
|
2734
|
+
console.log(` [${p.count}x] ${p.behavior}`);
|
|
2735
|
+
console.log(` Dates: ${p.journalDates.join(", ")}`);
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
if (result.promoted.length > 0) {
|
|
2739
|
+
console.log(`
|
|
2740
|
+
Promoted: ${result.promoted.join(", ")}`);
|
|
2741
|
+
}
|
|
2742
|
+
if (result.skipped.length > 0) {
|
|
2743
|
+
console.log(`Skipped (already exists): ${result.skipped.join(", ")}`);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
});
|
|
2747
|
+
intelligenceCmd.command("dead").description("Detect dead primitives (unreferenced and old)").option("-d, --dir <dir>", "Harness directory", ".").option("--threshold <days>", "Days since modification to consider dead", "30").option("--json", "Output as JSON").action(async (opts) => {
|
|
2748
|
+
const dir = resolve(opts.dir);
|
|
2749
|
+
loadEnvFromDir(dir);
|
|
2750
|
+
const { detectDeadPrimitives } = await import("../intelligence-HJOCA4SJ.js");
|
|
2751
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
2752
|
+
let config;
|
|
2753
|
+
try {
|
|
2754
|
+
config = loadConfig(dir);
|
|
2755
|
+
} catch (err) {
|
|
2756
|
+
if (process.env.DEBUG) console.error(`Config load skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
2757
|
+
}
|
|
2758
|
+
const result = detectDeadPrimitives(dir, config, {
|
|
2759
|
+
thresholdDays: parseInt(opts.threshold, 10)
|
|
2760
|
+
});
|
|
2761
|
+
if (opts.json) {
|
|
2762
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2763
|
+
} else {
|
|
2764
|
+
console.log(`Scanned ${result.totalScanned} primitives (threshold: ${result.thresholdDays} days)`);
|
|
2765
|
+
if (result.dead.length === 0) {
|
|
2766
|
+
console.log("No dead primitives found.");
|
|
2767
|
+
} else {
|
|
2768
|
+
console.log(`
|
|
2769
|
+
Dead primitives (${result.dead.length}):`);
|
|
2770
|
+
for (const d of result.dead) {
|
|
2771
|
+
console.log(` ${d.id} (${d.directory}) \u2014 ${d.daysSinceModified}d since modified`);
|
|
2772
|
+
console.log(` ${d.reason}`);
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
});
|
|
2777
|
+
intelligenceCmd.command("contradictions").description("Detect contradictions between rules and instincts").option("-d, --dir <dir>", "Harness directory", ".").option("--json", "Output as JSON").action(async (opts) => {
|
|
2778
|
+
const dir = resolve(opts.dir);
|
|
2779
|
+
loadEnvFromDir(dir);
|
|
2780
|
+
const { detectContradictions } = await import("../intelligence-HJOCA4SJ.js");
|
|
2781
|
+
const result = detectContradictions(dir);
|
|
2782
|
+
if (opts.json) {
|
|
2783
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2784
|
+
} else {
|
|
2785
|
+
console.log(`Checked ${result.rulesChecked} rules, ${result.instinctsChecked} instincts`);
|
|
2786
|
+
if (result.contradictions.length === 0) {
|
|
2787
|
+
console.log("No contradictions detected.");
|
|
2788
|
+
} else {
|
|
2789
|
+
console.log(`
|
|
2790
|
+
Contradictions (${result.contradictions.length}):`);
|
|
2791
|
+
for (const c of result.contradictions) {
|
|
2792
|
+
console.log(` [${c.severity}] ${c.primitiveA.id} (${c.primitiveA.type}) vs ${c.primitiveB.id} (${c.primitiveB.type})`);
|
|
2793
|
+
console.log(` ${c.reason}`);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
});
|
|
2798
|
+
intelligenceCmd.command("enrich").description("Enrich sessions with topics, token counts, and related primitives").option("-d, --dir <dir>", "Harness directory", ".").option("--from <date>", "Start date (YYYY-MM-DD)").option("--to <date>", "End date (YYYY-MM-DD)").option("--json", "Output as JSON").action(async (opts) => {
|
|
2799
|
+
const dir = resolve(opts.dir);
|
|
2800
|
+
loadEnvFromDir(dir);
|
|
2801
|
+
const { enrichSessions } = await import("../intelligence-HJOCA4SJ.js");
|
|
2802
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
2803
|
+
let config;
|
|
2804
|
+
try {
|
|
2805
|
+
config = loadConfig(dir);
|
|
2806
|
+
} catch (err) {
|
|
2807
|
+
if (process.env.DEBUG) console.error(`Config load skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
2808
|
+
}
|
|
2809
|
+
const result = enrichSessions(dir, config, {
|
|
2810
|
+
from: opts.from,
|
|
2811
|
+
to: opts.to
|
|
2812
|
+
});
|
|
2813
|
+
if (opts.json) {
|
|
2814
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2815
|
+
} else {
|
|
2816
|
+
console.log(`Scanned ${result.sessionsScanned} sessions, enriched ${result.enriched.length}`);
|
|
2817
|
+
for (const s of result.enriched) {
|
|
2818
|
+
console.log(` ${s.sessionId}: ${s.topics.join(", ") || "(no topics)"} \u2014 ${s.tokenCount} tokens, ${s.toolsUsed.length} tools`);
|
|
2819
|
+
if (s.primitivesReferenced.length > 0) {
|
|
2820
|
+
console.log(` Referenced: ${s.primitivesReferenced.join(", ")}`);
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
});
|
|
2825
|
+
intelligenceCmd.command("suggest").description("Suggest new skills/playbooks for frequent uncovered topics").option("-d, --dir <dir>", "Harness directory", ".").option("--min-frequency <n>", "Minimum topic frequency", "3").option("--json", "Output as JSON").action(async (opts) => {
|
|
2826
|
+
const dir = resolve(opts.dir);
|
|
2827
|
+
loadEnvFromDir(dir);
|
|
2828
|
+
const { suggestCapabilities } = await import("../intelligence-HJOCA4SJ.js");
|
|
2829
|
+
const { loadConfig } = await import("../config-WVMRUOCA.js");
|
|
2830
|
+
let config;
|
|
2831
|
+
try {
|
|
2832
|
+
config = loadConfig(dir);
|
|
2833
|
+
} catch (err) {
|
|
2834
|
+
if (process.env.DEBUG) console.error(`Config load skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
2835
|
+
}
|
|
2836
|
+
const result = suggestCapabilities(dir, config, {
|
|
2837
|
+
minFrequency: parseInt(opts.minFrequency, 10)
|
|
2838
|
+
});
|
|
2839
|
+
if (opts.json) {
|
|
2840
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2841
|
+
} else {
|
|
2842
|
+
console.log(`Analyzed ${result.topicsAnalyzed} topics from ${result.sessionsScanned} sessions`);
|
|
2843
|
+
if (result.suggestions.length === 0) {
|
|
2844
|
+
console.log("No capability suggestions at this time.");
|
|
2845
|
+
} else {
|
|
2846
|
+
console.log(`
|
|
2847
|
+
Suggestions (${result.suggestions.length}):`);
|
|
2848
|
+
for (const s of result.suggestions) {
|
|
2849
|
+
console.log(` [${s.suggestedType}] "${s.topic}" \u2014 ${s.frequency} occurrences`);
|
|
2850
|
+
console.log(` ${s.suggestion}`);
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
});
|
|
2855
|
+
intelligenceCmd.command("failures").description("Analyze recent failure patterns and suggest recovery strategies").option("-d, --dir <dir>", "Harness directory", ".").option("--days <n>", "Days to look back", "7").option("--json", "Output as JSON").action(async (opts) => {
|
|
2856
|
+
const dir = resolve(opts.dir);
|
|
2857
|
+
loadEnvFromDir(dir);
|
|
2858
|
+
const { analyzeFailures } = await import("../intelligence-HJOCA4SJ.js");
|
|
2859
|
+
const result = analyzeFailures(dir, { days: parseInt(opts.days, 10) });
|
|
2860
|
+
if (opts.json) {
|
|
2861
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2862
|
+
} else {
|
|
2863
|
+
console.log(`Health: ${result.healthImplication}`);
|
|
2864
|
+
console.log(`Recent failures: ${result.recentFailures.length}`);
|
|
2865
|
+
if (result.mostCommonMode) {
|
|
2866
|
+
console.log(`Most common failure: ${result.mostCommonMode}`);
|
|
2867
|
+
}
|
|
2868
|
+
if (Object.keys(result.modeFrequency).length > 0) {
|
|
2869
|
+
console.log("\nFailure frequency:");
|
|
2870
|
+
for (const [mode, count] of Object.entries(result.modeFrequency)) {
|
|
2871
|
+
console.log(` ${mode}: ${count}`);
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
if (result.suggestedRecovery.length > 0) {
|
|
2875
|
+
console.log("\nSuggested recovery:");
|
|
2876
|
+
for (const s of result.suggestedRecovery) {
|
|
2877
|
+
console.log(` - ${s}`);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
});
|
|
2882
|
+
intelligenceCmd.command("classify <error>").description("Classify an error message into a failure mode").action(async (errorMsg) => {
|
|
2883
|
+
const { classifyFailure, getRecoveryStrategies, FAILURE_TAXONOMY } = await import("../intelligence-HJOCA4SJ.js");
|
|
2884
|
+
const mode = classifyFailure(errorMsg);
|
|
2885
|
+
const info = FAILURE_TAXONOMY.modes[mode];
|
|
2886
|
+
const strategies = getRecoveryStrategies(mode);
|
|
2887
|
+
console.log(`Mode: ${mode}`);
|
|
2888
|
+
console.log(`Severity: ${info.severity}`);
|
|
2889
|
+
console.log(`Description: ${info.description}`);
|
|
2890
|
+
console.log(`Auto-recoverable: ${info.autoRecoverable}`);
|
|
2891
|
+
console.log("\nRecovery strategies:");
|
|
2892
|
+
for (const s of strategies) {
|
|
2893
|
+
console.log(` - ${s}`);
|
|
2894
|
+
}
|
|
2895
|
+
});
|
|
2896
|
+
var gateCmd = program.command("gate").description("Run verification gates");
|
|
2897
|
+
gateCmd.command("run [name]").description("Run a verification gate (or all gates if no name)").option("-d, --dir <dir>", "Harness directory", ".").option("--json", "Output as JSON").action(async (name, opts) => {
|
|
2898
|
+
const dir = resolve(opts.dir);
|
|
2899
|
+
loadEnvFromDir(dir);
|
|
2900
|
+
const { runGate, runAllGates } = await import("../intelligence-HJOCA4SJ.js");
|
|
2901
|
+
if (name) {
|
|
2902
|
+
const result = runGate(name, dir);
|
|
2903
|
+
if (opts.json) {
|
|
2904
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2905
|
+
} else {
|
|
2906
|
+
console.log(`Gate: ${result.gateName} \u2014 ${result.passed ? "PASSED" : "FAILED"}`);
|
|
2907
|
+
console.log(result.summary);
|
|
2908
|
+
for (const c of result.checks) {
|
|
2909
|
+
const icon = c.status === "pass" ? "[OK]" : c.status === "fail" ? "[FAIL]" : c.status === "warn" ? "[WARN]" : "[SKIP]";
|
|
2910
|
+
console.log(` ${icon} ${c.name}: ${c.message}`);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
} else {
|
|
2914
|
+
const results = runAllGates(dir);
|
|
2915
|
+
if (opts.json) {
|
|
2916
|
+
console.log(JSON.stringify(results, null, 2));
|
|
2917
|
+
} else {
|
|
2918
|
+
for (const result of results) {
|
|
2919
|
+
const icon = result.passed ? "[OK]" : "[FAIL]";
|
|
2920
|
+
console.log(`${icon} ${result.summary}`);
|
|
2921
|
+
for (const c of result.checks) {
|
|
2922
|
+
const cIcon = c.status === "pass" ? "[OK]" : c.status === "fail" ? "[FAIL]" : c.status === "warn" ? "[WARN]" : "[SKIP]";
|
|
2923
|
+
console.log(` ${cIcon} ${c.name}: ${c.message}`);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
gateCmd.command("list").description("List available verification gates").action(async () => {
|
|
2930
|
+
const { listGates } = await import("../intelligence-HJOCA4SJ.js");
|
|
2931
|
+
const gates = listGates();
|
|
2932
|
+
for (const g of gates) {
|
|
2933
|
+
console.log(` ${g.name}: ${g.description}`);
|
|
2934
|
+
}
|
|
2935
|
+
});
|
|
2936
|
+
program.command("check-rules").description("Check an action against loaded rules").argument("<action>", 'Action to check (e.g., "deploy", "run", "tool_call")').option("-d, --dir <path>", "Harness directory", process.cwd()).option("--description <text>", "Description of the action").option("--tags <tags>", "Comma-separated tags").option("--tool <name>", "Tool name (for tool_call actions)").option("--json", "Output as JSON").action(async (action, opts) => {
|
|
2937
|
+
const dir = resolve(opts.dir);
|
|
2938
|
+
loadEnvFromDir(dir);
|
|
2939
|
+
const { enforceRules } = await import("../rule-engine-YGQ3RYZM.js");
|
|
2940
|
+
const tags = opts.tags ? opts.tags.split(",").map((t) => t.trim()) : void 0;
|
|
2941
|
+
const result = enforceRules(dir, {
|
|
2942
|
+
action,
|
|
2943
|
+
description: opts.description,
|
|
2944
|
+
tags,
|
|
2945
|
+
toolName: opts.tool
|
|
2946
|
+
});
|
|
2947
|
+
if (opts.json) {
|
|
2948
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2949
|
+
} else {
|
|
2950
|
+
console.log(result.allowed ? "[OK] " + result.summary : "[BLOCKED] " + result.summary);
|
|
2951
|
+
for (const v of result.violations) {
|
|
2952
|
+
console.log(` [${v.severity.toUpperCase()}] ${v.directive} (rule: ${v.ruleId})`);
|
|
2953
|
+
}
|
|
2954
|
+
for (const w of result.warnings) {
|
|
2955
|
+
console.log(` [WARN] ${w.directive} (rule: ${w.ruleId})`);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
});
|
|
2959
|
+
program.command("list-rules").description("List all parsed rules from the harness").option("-d, --dir <path>", "Harness directory", process.cwd()).option("--json", "Output as JSON").action(async (opts) => {
|
|
2960
|
+
const dir = resolve(opts.dir);
|
|
2961
|
+
loadEnvFromDir(dir);
|
|
2962
|
+
const { loadRules } = await import("../rule-engine-YGQ3RYZM.js");
|
|
2963
|
+
const rules = loadRules(dir);
|
|
2964
|
+
if (opts.json) {
|
|
2965
|
+
console.log(JSON.stringify(rules, null, 2));
|
|
2966
|
+
} else {
|
|
2967
|
+
if (rules.length === 0) {
|
|
2968
|
+
console.log("No enforceable rules found.");
|
|
2969
|
+
} else {
|
|
2970
|
+
console.log(`${rules.length} rule(s) loaded:
|
|
2971
|
+
`);
|
|
2972
|
+
for (const rule of rules) {
|
|
2973
|
+
const icon = rule.action === "deny" ? "[DENY]" : rule.action === "warn" ? "[WARN]" : rule.action === "require_approval" ? "[APPROVAL]" : "[ALLOW]";
|
|
2974
|
+
console.log(` ${icon} ${rule.directive} (from: ${rule.ruleId})`);
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
});
|
|
2979
|
+
program.command("playbook-gates").description("Extract and check verification gates from playbooks/workflows").argument("[playbook-id]", "Specific playbook/workflow ID").option("-d, --dir <path>", "Harness directory", process.cwd()).option("--json", "Output as JSON").action(async (playbookId, opts) => {
|
|
2980
|
+
const dir = resolve(opts.dir);
|
|
2981
|
+
loadEnvFromDir(dir);
|
|
2982
|
+
const { loadGates, getGatesForPlaybook } = await import("../verification-gate-FYXUX6LH.js");
|
|
2983
|
+
if (playbookId) {
|
|
2984
|
+
const gates = getGatesForPlaybook(dir, playbookId);
|
|
2985
|
+
if (opts.json) {
|
|
2986
|
+
console.log(JSON.stringify(gates, null, 2));
|
|
2987
|
+
} else if (gates.length === 0) {
|
|
2988
|
+
console.log(`No verification gates found for "${playbookId}".`);
|
|
2989
|
+
} else {
|
|
2990
|
+
console.log(`${gates.length} gate(s) for "${playbookId}":
|
|
2991
|
+
`);
|
|
2992
|
+
for (const gate of gates) {
|
|
2993
|
+
console.log(` Gate: ${gate.stage} (${gate.id})`);
|
|
2994
|
+
for (const c of gate.criteria) {
|
|
2995
|
+
const icon = c.manual ? "[MANUAL]" : "[AUTO]";
|
|
2996
|
+
console.log(` ${icon} ${c.description}`);
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
} else {
|
|
3001
|
+
const { gates, sources } = loadGates(dir);
|
|
3002
|
+
if (opts.json) {
|
|
3003
|
+
console.log(JSON.stringify({ gates, sources }, null, 2));
|
|
3004
|
+
} else if (gates.length === 0) {
|
|
3005
|
+
console.log("No verification gates found in playbooks or workflows.");
|
|
3006
|
+
} else {
|
|
3007
|
+
console.log(`${gates.length} gate(s) from ${sources.length} source(s):
|
|
3008
|
+
`);
|
|
3009
|
+
for (const gate of gates) {
|
|
3010
|
+
console.log(` [${gate.sourceId}] ${gate.stage} \u2014 ${gate.criteria.length} criterion(s)`);
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
});
|
|
3015
|
+
var stateCmd = program.command("state-merge").description("Mixed-ownership state merging");
|
|
3016
|
+
stateCmd.command("apply").description("Apply a state change with ownership tracking").option("-d, --dir <dir>", "Harness directory", ".").option("--author <owner>", "Change author: human, agent, infrastructure", "human").option("--mode <mode>", "Set agent mode").option("--goals <goals>", "Set goals (comma-separated)").option("--strategy <strategy>", "Merge strategy: human-wins, agent-wins, latest-wins, union", "human-wins").option("--json", "Output as JSON").action(async (opts) => {
|
|
3017
|
+
const dir = resolve(opts.dir);
|
|
3018
|
+
loadEnvFromDir(dir);
|
|
3019
|
+
const { mergeState } = await import("../state-merge-NKO5FRBA.js");
|
|
3020
|
+
const changes = {};
|
|
3021
|
+
if (opts.mode) changes.mode = opts.mode;
|
|
3022
|
+
if (opts.goals) changes.goals = opts.goals.split(",").map((g) => g.trim());
|
|
3023
|
+
if (Object.keys(changes).length === 0) {
|
|
3024
|
+
console.log("No changes specified. Use --mode or --goals.");
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
const result = mergeState(dir, {
|
|
3028
|
+
author: opts.author,
|
|
3029
|
+
changes
|
|
3030
|
+
}, opts.strategy);
|
|
3031
|
+
if (opts.json) {
|
|
3032
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3033
|
+
} else {
|
|
3034
|
+
console.log(`State merged (strategy: ${opts.strategy}).`);
|
|
3035
|
+
if (result.hadConflicts) {
|
|
3036
|
+
console.log(` ${result.conflicts.length} conflict(s) resolved:`);
|
|
3037
|
+
for (const c of result.conflicts) {
|
|
3038
|
+
console.log(` ${c.field}: resolved to ${c.resolvedTo}`);
|
|
3039
|
+
}
|
|
3040
|
+
} else {
|
|
3041
|
+
console.log(" No conflicts.");
|
|
3042
|
+
}
|
|
3043
|
+
console.log(` Mode: ${result.state.mode}`);
|
|
3044
|
+
console.log(` Goals: ${result.state.goals.join(", ") || "(none)"}`);
|
|
3045
|
+
}
|
|
3046
|
+
});
|
|
3047
|
+
stateCmd.command("ownership").description("Show current state ownership").option("-d, --dir <dir>", "Harness directory", ".").option("--json", "Output as JSON").action(async (opts) => {
|
|
3048
|
+
const dir = resolve(opts.dir);
|
|
3049
|
+
loadEnvFromDir(dir);
|
|
3050
|
+
const { loadOwnership } = await import("../state-merge-NKO5FRBA.js");
|
|
3051
|
+
const ownership = loadOwnership(dir);
|
|
3052
|
+
if (opts.json) {
|
|
3053
|
+
console.log(JSON.stringify(ownership, null, 2));
|
|
3054
|
+
} else {
|
|
3055
|
+
console.log("State field ownership:");
|
|
3056
|
+
for (const [field, owner] of Object.entries(ownership)) {
|
|
3057
|
+
console.log(` ${field}: ${owner}`);
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
});
|
|
3061
|
+
var emoCmd = program.command("emotional").description("Operational disposition tracking");
|
|
3062
|
+
emoCmd.command("status").description("Show current emotional/disposition state").option("-d, --dir <dir>", "Harness directory", ".").option("--json", "Output as JSON").action(async (opts) => {
|
|
3063
|
+
const dir = resolve(opts.dir);
|
|
3064
|
+
loadEnvFromDir(dir);
|
|
3065
|
+
const { loadEmotionalState, summarizeEmotionalState } = await import("../emotional-state-VQVRA6ED.js");
|
|
3066
|
+
const state = loadEmotionalState(dir);
|
|
3067
|
+
if (opts.json) {
|
|
3068
|
+
console.log(JSON.stringify(state, null, 2));
|
|
3069
|
+
} else {
|
|
3070
|
+
console.log("Operational Disposition:");
|
|
3071
|
+
console.log(` Confidence: ${state.confidence}/100`);
|
|
3072
|
+
console.log(` Engagement: ${state.engagement}/100`);
|
|
3073
|
+
console.log(` Frustration: ${state.frustration}/100`);
|
|
3074
|
+
console.log(` Curiosity: ${state.curiosity}/100`);
|
|
3075
|
+
console.log(` Urgency: ${state.urgency}/100`);
|
|
3076
|
+
console.log(` Updated: ${state.updatedAt}`);
|
|
3077
|
+
console.log(`
|
|
3078
|
+
${summarizeEmotionalState(state)}`);
|
|
3079
|
+
}
|
|
3080
|
+
});
|
|
3081
|
+
emoCmd.command("signal").description("Apply an emotional signal").argument("<dimension>", "Dimension: confidence, engagement, frustration, curiosity, urgency").argument("<delta>", "Delta value (positive or negative integer)").option("-d, --dir <dir>", "Harness directory", ".").option("-r, --reason <reason>", "Reason for the signal").option("--json", "Output as JSON").action(async (dimension, delta, opts) => {
|
|
3082
|
+
const dir = resolve(opts.dir);
|
|
3083
|
+
loadEnvFromDir(dir);
|
|
3084
|
+
const { applySignals } = await import("../emotional-state-VQVRA6ED.js");
|
|
3085
|
+
const state = applySignals(dir, [{
|
|
3086
|
+
dimension,
|
|
3087
|
+
delta: parseInt(delta, 10),
|
|
3088
|
+
reason: opts.reason
|
|
3089
|
+
}]);
|
|
3090
|
+
if (opts.json) {
|
|
3091
|
+
console.log(JSON.stringify(state, null, 2));
|
|
3092
|
+
} else {
|
|
3093
|
+
console.log(`Applied signal: ${dimension} ${parseInt(delta, 10) >= 0 ? "+" : ""}${delta}`);
|
|
3094
|
+
console.log(` New ${dimension}: ${state[dimension]}/100`);
|
|
3095
|
+
}
|
|
3096
|
+
});
|
|
3097
|
+
emoCmd.command("trends").description("Show emotional dimension trends").option("-d, --dir <dir>", "Harness directory", ".").option("--days <days>", "Days to analyze", "7").option("--json", "Output as JSON").action(async (opts) => {
|
|
3098
|
+
const dir = resolve(opts.dir);
|
|
3099
|
+
loadEnvFromDir(dir);
|
|
3100
|
+
const { getEmotionalTrends } = await import("../emotional-state-VQVRA6ED.js");
|
|
3101
|
+
const trends = getEmotionalTrends(dir, { days: parseInt(opts.days, 10) });
|
|
3102
|
+
if (opts.json) {
|
|
3103
|
+
console.log(JSON.stringify(trends, null, 2));
|
|
3104
|
+
} else {
|
|
3105
|
+
console.log(`Emotional trends (last ${opts.days} days):
|
|
3106
|
+
`);
|
|
3107
|
+
for (const t of trends) {
|
|
3108
|
+
const arrow = t.trend === "rising" ? "\u2191" : t.trend === "falling" ? "\u2193" : "\u2192";
|
|
3109
|
+
console.log(` ${t.dimension}: avg ${t.average.toFixed(0)}/100 ${arrow} (${t.values.length} data points)`);
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
});
|
|
3113
|
+
emoCmd.command("reset").description("Reset emotional state to defaults").option("-d, --dir <dir>", "Harness directory", ".").action(async (opts) => {
|
|
3114
|
+
const dir = resolve(opts.dir);
|
|
3115
|
+
loadEnvFromDir(dir);
|
|
3116
|
+
const { resetEmotionalState } = await import("../emotional-state-VQVRA6ED.js");
|
|
3117
|
+
resetEmotionalState(dir);
|
|
3118
|
+
console.log("Emotional state reset to defaults.");
|
|
3119
|
+
});
|
|
3120
|
+
program.command("check-action").description("Check if an action is allowed by harness rules (agent-framework guardrails)").argument("<action>", "Action description to check").option("-d, --dir <dir>", "Harness directory", ".").option("--tags <tags>", "Filter by rule tags (comma-separated)").option("--json", "Output as JSON").action(async (action, opts) => {
|
|
3121
|
+
const dir = resolve(opts.dir);
|
|
3122
|
+
loadEnvFromDir(dir);
|
|
3123
|
+
const { checkAction } = await import("../agent-framework-K4GUIICH.js");
|
|
3124
|
+
const tags = opts.tags ? opts.tags.split(",").map((t) => t.trim()) : void 0;
|
|
3125
|
+
const result = checkAction(dir, action, { ruleTags: tags });
|
|
3126
|
+
if (opts.json) {
|
|
3127
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3128
|
+
} else if (result.allowed) {
|
|
3129
|
+
console.log("[OK] Action allowed.");
|
|
3130
|
+
} else {
|
|
3131
|
+
console.log(`[BLOCKED] ${result.reason}`);
|
|
3132
|
+
}
|
|
3133
|
+
});
|
|
3134
|
+
program.command("serve").description("Start the harness HTTP API server for webhooks and integrations").option("-d, --dir <dir>", "Harness directory", ".").option("-p, --port <port>", "Port to listen on", "8080").option("--api-key <key>", "API key for LLM provider").option("--webhook-secret <secret>", "Secret for authenticating webhook management API").option("--no-cors", "Disable CORS").action(async (opts) => {
|
|
3135
|
+
const dir = resolve(opts.dir);
|
|
3136
|
+
loadEnvFromDir(dir);
|
|
3137
|
+
const { startServe } = await import("../serve-DTQ3HENY.js");
|
|
3138
|
+
const port = parseInt(opts.port, 10);
|
|
3139
|
+
const result = startServe({
|
|
3140
|
+
harnessDir: dir,
|
|
3141
|
+
port,
|
|
3142
|
+
apiKey: opts.apiKey,
|
|
3143
|
+
webhookSecret: opts.webhookSecret,
|
|
3144
|
+
corsEnabled: opts.cors !== false
|
|
3145
|
+
});
|
|
3146
|
+
console.log(`Harness API server listening on http://localhost:${result.port}`);
|
|
3147
|
+
console.log("Endpoints:");
|
|
3148
|
+
console.log(" GET /api/health \u2014 health check");
|
|
3149
|
+
console.log(" GET /api/info \u2014 agent info");
|
|
3150
|
+
console.log(" POST /api/run \u2014 execute a prompt");
|
|
3151
|
+
console.log(" GET /api/webhooks \u2014 list registered webhooks");
|
|
3152
|
+
console.log(" POST /api/webhooks \u2014 register a webhook");
|
|
3153
|
+
console.log(" DEL /api/webhooks/:id \u2014 delete a webhook");
|
|
3154
|
+
console.log(" PATCH /api/webhooks/:id \u2014 toggle webhook active/inactive");
|
|
3155
|
+
console.log(" POST /api/webhooks/:id/test \u2014 test a webhook");
|
|
3156
|
+
console.log(" + all dashboard endpoints from harness dev");
|
|
3157
|
+
console.log("\nPress Ctrl+C to stop.");
|
|
3158
|
+
process.on("SIGINT", () => {
|
|
3159
|
+
console.log("\nShutting down...");
|
|
3160
|
+
result.stop();
|
|
3161
|
+
process.exit(0);
|
|
3162
|
+
});
|
|
3163
|
+
process.on("SIGTERM", () => {
|
|
3164
|
+
result.stop();
|
|
3165
|
+
process.exit(0);
|
|
3166
|
+
});
|
|
3167
|
+
await new Promise(() => {
|
|
3168
|
+
});
|
|
3169
|
+
});
|
|
3170
|
+
var sourcesCmd = program.command("sources").description("Manage content sources (skills, agents, rules, MCP servers)");
|
|
3171
|
+
sourcesCmd.command("list").description("List all configured content sources").option("-d, --dir <dir>", "Harness directory", ".").option("--type <type>", "Filter by content type (skills, agents, rules, hooks, mcp, etc.)").option("--json", "Output as JSON").action(async (opts) => {
|
|
3172
|
+
const dir = resolve(opts.dir);
|
|
3173
|
+
loadEnvFromDir(dir);
|
|
3174
|
+
const { loadAllSources, getSourcesForType } = await import("../sources-RW5DT56F.js");
|
|
3175
|
+
const sources = opts.type ? getSourcesForType(dir, opts.type) : loadAllSources(dir);
|
|
3176
|
+
if (opts.json) {
|
|
3177
|
+
console.log(JSON.stringify(sources, null, 2));
|
|
3178
|
+
} else if (sources.length === 0) {
|
|
3179
|
+
console.log("No sources configured.");
|
|
3180
|
+
} else {
|
|
3181
|
+
console.log(`${sources.length} source(s):
|
|
3182
|
+
`);
|
|
3183
|
+
for (const s of sources) {
|
|
3184
|
+
const types = s.content.join(", ");
|
|
3185
|
+
const stats = s.stats ? ` (${Object.entries(s.stats).map(([k, v]) => `${v} ${k}`).join(", ")})` : "";
|
|
3186
|
+
console.log(` [${s.type}] ${s.name}${stats}`);
|
|
3187
|
+
console.log(` ${s.url}`);
|
|
3188
|
+
console.log(` Content: ${types}`);
|
|
3189
|
+
if (s.description) console.log(` ${s.description}`);
|
|
3190
|
+
console.log();
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
});
|
|
3194
|
+
sourcesCmd.command("add").description("Add a new content source").argument("<url>", "Source URL (GitHub repo, registry API, or endpoint)").option("-d, --dir <dir>", "Harness directory", ".").option("-n, --name <name>", "Source display name").option("-t, --type <type>", "Source type: github, registry, api", "github").option("-c, --content <types>", "Content types (comma-separated: skills,agents,rules,hooks,mcp)").option("--description <desc>", "Source description").option("--json", "Output as JSON").action(async (url, opts) => {
|
|
3195
|
+
const dir = resolve(opts.dir);
|
|
3196
|
+
loadEnvFromDir(dir);
|
|
3197
|
+
const { addSource } = await import("../sources-RW5DT56F.js");
|
|
3198
|
+
const name = opts.name ?? url.replace(/https?:\/\//, "").replace(/github\.com\//, "").replace(/\/$/, "");
|
|
3199
|
+
const content = opts.content ? opts.content.split(",").map((c) => c.trim()) : ["skills"];
|
|
3200
|
+
const result = addSource(dir, {
|
|
3201
|
+
name,
|
|
3202
|
+
url,
|
|
3203
|
+
type: opts.type,
|
|
3204
|
+
content,
|
|
3205
|
+
description: opts.description
|
|
3206
|
+
});
|
|
3207
|
+
if (opts.json) {
|
|
3208
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3209
|
+
} else if (result) {
|
|
3210
|
+
console.log(`Added source: ${result.name} (${result.url})`);
|
|
3211
|
+
} else {
|
|
3212
|
+
console.log("Source with that name already exists.");
|
|
3213
|
+
}
|
|
3214
|
+
});
|
|
3215
|
+
sourcesCmd.command("remove").description("Remove a content source").argument("<name>", "Source name to remove").option("-d, --dir <dir>", "Harness directory", ".").action(async (name, opts) => {
|
|
3216
|
+
const dir = resolve(opts.dir);
|
|
3217
|
+
loadEnvFromDir(dir);
|
|
3218
|
+
const { removeSource } = await import("../sources-RW5DT56F.js");
|
|
3219
|
+
const removed = removeSource(dir, name);
|
|
3220
|
+
if (removed) {
|
|
3221
|
+
console.log(`Removed source: ${name}`);
|
|
3222
|
+
} else {
|
|
3223
|
+
console.log(`Source not found: ${name}`);
|
|
3224
|
+
}
|
|
3225
|
+
});
|
|
3226
|
+
sourcesCmd.command("summary").description("Show content available by type across all sources").option("-d, --dir <dir>", "Harness directory", ".").option("--json", "Output as JSON").action(async (opts) => {
|
|
3227
|
+
const dir = resolve(opts.dir);
|
|
3228
|
+
loadEnvFromDir(dir);
|
|
3229
|
+
const { getSourcesSummary } = await import("../sources-RW5DT56F.js");
|
|
3230
|
+
const summary = getSourcesSummary(dir);
|
|
3231
|
+
if (opts.json) {
|
|
3232
|
+
const json = {};
|
|
3233
|
+
for (const [type, sources] of Object.entries(summary)) {
|
|
3234
|
+
json[type] = sources.length;
|
|
3235
|
+
}
|
|
3236
|
+
console.log(JSON.stringify(json, null, 2));
|
|
3237
|
+
} else {
|
|
3238
|
+
console.log("Content sources by type:\n");
|
|
3239
|
+
for (const [type, sources] of Object.entries(summary)) {
|
|
3240
|
+
if (sources.length > 0) {
|
|
3241
|
+
console.log(` ${type}: ${sources.length} source(s)`);
|
|
3242
|
+
for (const s of sources) {
|
|
3243
|
+
console.log(` - ${s.name}`);
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
});
|
|
3249
|
+
discoverCmd.command("search").description("Search all content sources for skills, agents, rules, hooks, MCP servers").argument("<query>", "Search query").option("-d, --dir <dir>", "Harness directory", ".").option("-t, --type <type>", "Filter by content type (skills, agents, rules, hooks, mcp, etc.)").option("-n, --max <n>", "Maximum results", "20").option("--remote", "Also search remote sources (GitHub API, registries)").option("--json", "Output as JSON").action(async (query, opts) => {
|
|
3250
|
+
const dir = resolve(opts.dir);
|
|
3251
|
+
loadEnvFromDir(dir);
|
|
3252
|
+
const maxResults = parseInt(opts.max, 10);
|
|
3253
|
+
const type = opts.type;
|
|
3254
|
+
if (opts.remote) {
|
|
3255
|
+
const { discoverRemote } = await import("../sources-RW5DT56F.js");
|
|
3256
|
+
const results = await discoverRemote(dir, query, {
|
|
3257
|
+
type,
|
|
3258
|
+
maxResults
|
|
3259
|
+
});
|
|
3260
|
+
if (opts.json) {
|
|
3261
|
+
console.log(JSON.stringify(results, null, 2));
|
|
3262
|
+
} else if (results.length === 0) {
|
|
3263
|
+
console.log("No results found.");
|
|
3264
|
+
} else {
|
|
3265
|
+
console.log(`${results.length} result(s):
|
|
3266
|
+
`);
|
|
3267
|
+
for (const r of results) {
|
|
3268
|
+
console.log(` [${r.type}] ${r.name} (score: ${r.score.toFixed(2)})`);
|
|
3269
|
+
console.log(` Source: ${r.source.name}`);
|
|
3270
|
+
console.log(` ${r.url}`);
|
|
3271
|
+
if (r.description) console.log(` ${r.description}`);
|
|
3272
|
+
console.log();
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
} else {
|
|
3276
|
+
const { discoverSources } = await import("../sources-RW5DT56F.js");
|
|
3277
|
+
const results = discoverSources(dir, query, {
|
|
3278
|
+
type,
|
|
3279
|
+
maxResults
|
|
3280
|
+
});
|
|
3281
|
+
if (opts.json) {
|
|
3282
|
+
console.log(JSON.stringify(results, null, 2));
|
|
3283
|
+
} else if (results.length === 0) {
|
|
3284
|
+
console.log("No results found. Try --remote to search GitHub and registries.");
|
|
3285
|
+
} else {
|
|
3286
|
+
console.log(`${results.length} result(s):
|
|
3287
|
+
`);
|
|
3288
|
+
for (const r of results) {
|
|
3289
|
+
console.log(` [${r.type}] ${r.name} (score: ${r.score.toFixed(2)})`);
|
|
3290
|
+
console.log(` Source: ${r.source.name}`);
|
|
3291
|
+
console.log(` ${r.url}`);
|
|
3292
|
+
if (r.description) console.log(` ${r.description}`);
|
|
3293
|
+
console.log();
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
});
|
|
3298
|
+
program.command("browse").description("Browse available community content \u2014 starter packs, sources, and installed primitives").option("-d, --dir <dir>", "Harness directory", ".").option("--type <type>", "Filter by content type (packs, sources, installed)").option("--json", "Output as JSON").action(async (opts) => {
|
|
3299
|
+
const dir = resolve(opts.dir);
|
|
3300
|
+
loadEnvFromDir(dir);
|
|
3301
|
+
const { listStarterPacks } = await import("../starter-packs-76YUVHEU.js");
|
|
3302
|
+
const { loadAllSources, getSourcesSummary } = await import("../sources-RW5DT56F.js");
|
|
3303
|
+
const { listInstalledBundles } = await import("../primitive-registry-I6VTIR4W.js");
|
|
3304
|
+
const filter = opts.type;
|
|
3305
|
+
const sections = [];
|
|
3306
|
+
if (!filter || filter === "packs") {
|
|
3307
|
+
const packs = listStarterPacks();
|
|
3308
|
+
sections.push({
|
|
3309
|
+
title: "Starter Packs",
|
|
3310
|
+
type: "packs",
|
|
3311
|
+
items: packs.map((p) => ({
|
|
3312
|
+
name: p.name,
|
|
3313
|
+
description: p.description,
|
|
3314
|
+
files: p.fileCount,
|
|
3315
|
+
tags: p.tags,
|
|
3316
|
+
install: `harness install pack:${p.name}`
|
|
3317
|
+
}))
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
if (!filter || filter === "sources") {
|
|
3321
|
+
const sources = loadAllSources(dir);
|
|
3322
|
+
const summary = getSourcesSummary(dir);
|
|
3323
|
+
const typeEntries = Object.entries(summary).filter(([, s]) => s.length > 0).map(([type, s]) => `${type}: ${s.length}`);
|
|
3324
|
+
sections.push({
|
|
3325
|
+
title: "Community Sources",
|
|
3326
|
+
type: "sources",
|
|
3327
|
+
items: sources.map((s) => ({
|
|
3328
|
+
name: s.name,
|
|
3329
|
+
url: s.url,
|
|
3330
|
+
type: s.type,
|
|
3331
|
+
content: s.content,
|
|
3332
|
+
stats: s.stats
|
|
3333
|
+
})),
|
|
3334
|
+
...typeEntries.length > 0 ? { summary: typeEntries } : {}
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
if (!filter || filter === "installed") {
|
|
3338
|
+
const installed = listInstalledBundles(dir);
|
|
3339
|
+
sections.push({
|
|
3340
|
+
title: "Installed Bundles",
|
|
3341
|
+
type: "installed",
|
|
3342
|
+
items: installed.map((b) => ({
|
|
3343
|
+
name: b.name,
|
|
3344
|
+
description: b.description,
|
|
3345
|
+
version: b.version,
|
|
3346
|
+
types: b.types,
|
|
3347
|
+
files: b.fileCount
|
|
3348
|
+
}))
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
if (opts.json) {
|
|
3352
|
+
const output = {};
|
|
3353
|
+
for (const section of sections) {
|
|
3354
|
+
output[section.type] = section.items;
|
|
3355
|
+
}
|
|
3356
|
+
console.log(JSON.stringify(output, null, 2));
|
|
3357
|
+
return;
|
|
3358
|
+
}
|
|
3359
|
+
console.log("=== Agent Harness Content Browser ===\n");
|
|
3360
|
+
for (const section of sections) {
|
|
3361
|
+
console.log(`\u2500\u2500 ${section.title} \u2500\u2500
|
|
3362
|
+
`);
|
|
3363
|
+
if (section.items.length === 0) {
|
|
3364
|
+
console.log(" (none)\n");
|
|
3365
|
+
continue;
|
|
3366
|
+
}
|
|
3367
|
+
for (const item of section.items) {
|
|
3368
|
+
if (section.type === "packs") {
|
|
3369
|
+
console.log(` pack:${item.name}`);
|
|
3370
|
+
console.log(` ${item.description}`);
|
|
3371
|
+
console.log(` Files: ${item.files} | Tags: ${item.tags.join(", ")}`);
|
|
3372
|
+
console.log(` Install: ${item.install}`);
|
|
3373
|
+
console.log();
|
|
3374
|
+
} else if (section.type === "sources") {
|
|
3375
|
+
const stats = item.stats;
|
|
3376
|
+
const statsStr = stats ? ` (${Object.entries(stats).map(([k, v]) => `${v} ${k}`).join(", ")})` : "";
|
|
3377
|
+
console.log(` [${item.type}] ${item.name}${statsStr}`);
|
|
3378
|
+
console.log(` ${item.url}`);
|
|
3379
|
+
console.log(` Content: ${item.content.join(", ")}`);
|
|
3380
|
+
console.log();
|
|
3381
|
+
} else if (section.type === "installed") {
|
|
3382
|
+
console.log(` ${item.name} v${item.version}`);
|
|
3383
|
+
if (item.description) console.log(` ${item.description}`);
|
|
3384
|
+
console.log(` Types: ${item.types.join(", ")} | Files: ${item.files}`);
|
|
3385
|
+
console.log();
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
console.log("\u2500\u2500 Quick Start \u2500\u2500\n");
|
|
3390
|
+
console.log(" Install a pack: harness install pack:<name> -d <dir>");
|
|
3391
|
+
console.log(" Search sources: harness discover search <query> -d <dir>");
|
|
3392
|
+
console.log(" Install anything: harness install <url-or-name> -d <dir>");
|
|
3393
|
+
console.log(" Add a source: harness sources add <url> -d <dir>");
|
|
3394
|
+
console.log();
|
|
3395
|
+
});
|
|
3396
|
+
var semanticCmd = program.command("semantic").description("Semantic search over indexed primitives");
|
|
3397
|
+
semanticCmd.command("index").description("Index all primitives for semantic search (requires an embed function at runtime \u2014 shows stats only from CLI)").option("-d, --dir <dir>", "Harness directory", ".").option("--json", "Output as JSON").action(async (opts) => {
|
|
3398
|
+
const dir = resolve(opts.dir);
|
|
3399
|
+
loadEnvFromDir(dir);
|
|
3400
|
+
const { getEmbeddingStats } = await import("../semantic-search-2DTOO5UX.js");
|
|
3401
|
+
const stats = getEmbeddingStats(dir);
|
|
3402
|
+
if (opts.json) {
|
|
3403
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
3404
|
+
} else {
|
|
3405
|
+
console.log("Embedding index stats:");
|
|
3406
|
+
console.log(` Indexed: ${stats.indexed} primitives`);
|
|
3407
|
+
console.log(` Model: ${stats.modelId ?? "(none)"}`);
|
|
3408
|
+
console.log(` Dimensions: ${stats.dimensions}`);
|
|
3409
|
+
console.log(` Last indexed: ${stats.lastIndexedAt ?? "(never)"}`);
|
|
3410
|
+
console.log(` Store size: ${(stats.storeSize / 1024).toFixed(1)} KB`);
|
|
3411
|
+
}
|
|
3412
|
+
});
|
|
3413
|
+
semanticCmd.command("stats").description("Show embedding store statistics").option("-d, --dir <dir>", "Harness directory", ".").option("--json", "Output as JSON").action(async (opts) => {
|
|
3414
|
+
const dir = resolve(opts.dir);
|
|
3415
|
+
loadEnvFromDir(dir);
|
|
3416
|
+
const { getEmbeddingStats, detectStalePrimitives, loadEmbeddingStore } = await import("../semantic-search-2DTOO5UX.js");
|
|
3417
|
+
const stats = getEmbeddingStats(dir);
|
|
3418
|
+
const store = loadEmbeddingStore(dir);
|
|
3419
|
+
const stale = detectStalePrimitives(dir, store, stats.modelId ?? "");
|
|
3420
|
+
if (opts.json) {
|
|
3421
|
+
console.log(JSON.stringify({ ...stats, stale: stale.length }, null, 2));
|
|
3422
|
+
} else {
|
|
3423
|
+
console.log("Semantic search stats:");
|
|
3424
|
+
console.log(` Indexed: ${stats.indexed} primitives`);
|
|
3425
|
+
console.log(` Stale: ${stale.length} primitive(s) need re-indexing`);
|
|
3426
|
+
console.log(` Model: ${stats.modelId ?? "(none)"}`);
|
|
3427
|
+
console.log(` Dimensions: ${stats.dimensions}`);
|
|
3428
|
+
console.log(` Last indexed: ${stats.lastIndexedAt ?? "(never)"}`);
|
|
3429
|
+
console.log(` Store size: ${(stats.storeSize / 1024).toFixed(1)} KB`);
|
|
3430
|
+
}
|
|
3431
|
+
});
|
|
3432
|
+
program.command("install").description("Install a primitive from any source (file, URL, or name)").argument("<source>", "File path, HTTPS URL, or source name to install").option("-d, --dir <dir>", "Harness directory", ".").option("-t, --type <type>", "Override detected type (skill, rule, agent, playbook, workflow, tool)").option("--id <id>", "Override generated ID").option("--force", "Force install despite validation warnings").option("--skip-fix", "Skip auto-fix (no frontmatter/L0/L1 generation)").option("--tags <tags...>", "Additional tags to add").option("--json", "Output as JSON").action(async (source, opts) => {
|
|
3433
|
+
const dir = resolve(opts.dir);
|
|
3434
|
+
loadEnvFromDir(dir);
|
|
3435
|
+
const { isPackReference, parsePackName, getStarterPack, listStarterPacks } = await import("../starter-packs-76YUVHEU.js");
|
|
3436
|
+
if (isPackReference(source)) {
|
|
3437
|
+
const packName = parsePackName(source);
|
|
3438
|
+
if (packName === "list") {
|
|
3439
|
+
const packs = listStarterPacks();
|
|
3440
|
+
console.log("\nAvailable starter packs:\n");
|
|
3441
|
+
for (const p of packs) {
|
|
3442
|
+
console.log(` pack:${p.name}`);
|
|
3443
|
+
console.log(` ${p.description}`);
|
|
3444
|
+
console.log(` Files: ${p.fileCount} | Tags: ${p.tags.join(", ")}
|
|
3445
|
+
`);
|
|
3446
|
+
}
|
|
3447
|
+
console.log(`Install with: harness install pack:<name> -d <harness-dir>`);
|
|
3448
|
+
return;
|
|
3449
|
+
}
|
|
3450
|
+
const bundle = getStarterPack(packName);
|
|
3451
|
+
if (!bundle) {
|
|
3452
|
+
const available = listStarterPacks().map((p) => `pack:${p.name}`).join(", ");
|
|
3453
|
+
console.error(`Unknown starter pack: "${packName}"
|
|
3454
|
+
Available packs: ${available}`);
|
|
3455
|
+
process.exitCode = 1;
|
|
3456
|
+
return;
|
|
3457
|
+
}
|
|
3458
|
+
const { installBundle } = await import("../primitive-registry-I6VTIR4W.js");
|
|
3459
|
+
const bundleResult = installBundle(dir, bundle, {
|
|
3460
|
+
overwrite: opts.force,
|
|
3461
|
+
force: opts.force
|
|
3462
|
+
});
|
|
3463
|
+
if (opts.json) {
|
|
3464
|
+
console.log(JSON.stringify(bundleResult, null, 2));
|
|
3465
|
+
} else if (bundleResult.installed) {
|
|
3466
|
+
console.log(`
|
|
3467
|
+
Installed pack: ${packName}`);
|
|
3468
|
+
console.log(` Files: ${bundleResult.files.length}`);
|
|
3469
|
+
for (const f of bundleResult.files) {
|
|
3470
|
+
console.log(` + ${f}`);
|
|
3471
|
+
}
|
|
3472
|
+
if (bundleResult.skipped.length > 0) {
|
|
3473
|
+
console.log(` Skipped (already exist): ${bundleResult.skipped.length}`);
|
|
3474
|
+
for (const f of bundleResult.skipped) {
|
|
3475
|
+
console.log(` ~ ${f}`);
|
|
3476
|
+
}
|
|
3477
|
+
console.log(` Use --force to overwrite existing files.`);
|
|
3478
|
+
}
|
|
3479
|
+
console.log(`
|
|
3480
|
+
Customize the workflows in your workflows/ directory.`);
|
|
3481
|
+
} else {
|
|
3482
|
+
console.error(`Failed to install pack: ${packName}`);
|
|
3483
|
+
for (const err of bundleResult.errors) {
|
|
3484
|
+
console.error(` ${err}`);
|
|
3485
|
+
}
|
|
3486
|
+
process.exitCode = 1;
|
|
3487
|
+
}
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
const { universalInstall } = await import("../universal-installer-QGS4SJGX.js");
|
|
3491
|
+
const result = await universalInstall(dir, source, {
|
|
3492
|
+
type: opts.type,
|
|
3493
|
+
id: opts.id,
|
|
3494
|
+
force: opts.force,
|
|
3495
|
+
skipFix: opts.skipFix,
|
|
3496
|
+
tags: opts.tags
|
|
3497
|
+
});
|
|
3498
|
+
if (opts.json) {
|
|
3499
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3500
|
+
} else {
|
|
3501
|
+
if (result.installed) {
|
|
3502
|
+
console.log(`Installed: ${result.destination}`);
|
|
3503
|
+
console.log(` Format: ${result.format.format} (${(result.format.confidence * 100).toFixed(0)}% confidence)`);
|
|
3504
|
+
if (result.format.primitiveType) {
|
|
3505
|
+
console.log(` Type: ${result.format.primitiveType}`);
|
|
3506
|
+
}
|
|
3507
|
+
if (result.fixes.length > 0) {
|
|
3508
|
+
console.log(` Fixes:`);
|
|
3509
|
+
for (const fix of result.fixes) {
|
|
3510
|
+
console.log(` - ${fix}`);
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
if (result.suggestedDependencies.length > 0) {
|
|
3514
|
+
console.log(` Dependencies to consider:`);
|
|
3515
|
+
for (const dep of result.suggestedDependencies) {
|
|
3516
|
+
console.log(` - ${dep}`);
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
} else {
|
|
3520
|
+
console.error(`Failed to install: ${source}`);
|
|
3521
|
+
for (const err of result.errors) {
|
|
3522
|
+
console.error(` ${err}`);
|
|
3523
|
+
}
|
|
3524
|
+
if (result.fixes.length > 0) {
|
|
3525
|
+
console.log(` Attempted fixes:`);
|
|
3526
|
+
for (const fix of result.fixes) {
|
|
3527
|
+
console.log(` - ${fix}`);
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
process.exitCode = 1;
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
});
|
|
3534
|
+
var versionCmd = program.command("version").description("Git-backed primitive versioning");
|
|
3535
|
+
versionCmd.command("init").description("Initialize git versioning for the harness").option("-d, --dir <dir>", "Harness directory", ".").action(async (opts) => {
|
|
3536
|
+
const dir = resolve(opts.dir);
|
|
3537
|
+
const { initVersioning, isGitRepo } = await import("../versioning-Z3XNE2Q2.js");
|
|
3538
|
+
if (isGitRepo(dir)) {
|
|
3539
|
+
console.log("Versioning already initialized.");
|
|
3540
|
+
} else {
|
|
3541
|
+
const ok = initVersioning(dir);
|
|
3542
|
+
console.log(ok ? "Versioning initialized." : "Failed to initialize versioning.");
|
|
3543
|
+
}
|
|
3544
|
+
});
|
|
3545
|
+
versionCmd.command("snapshot").description("Take a versioned snapshot of the current harness state").argument("<message>", "Commit message").option("-d, --dir <dir>", "Harness directory", ".").option("-t, --tag <tag>", "Tag this version").option("--json", "Output as JSON").action(async (message, opts) => {
|
|
3546
|
+
const dir = resolve(opts.dir);
|
|
3547
|
+
const { snapshot } = await import("../versioning-Z3XNE2Q2.js");
|
|
3548
|
+
const result = snapshot(dir, message, { tag: opts.tag });
|
|
3549
|
+
if (opts.json) {
|
|
3550
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3551
|
+
} else if (result.success && result.files.length > 0) {
|
|
3552
|
+
console.log(`Snapshot ${result.hash.slice(0, 7)}: ${result.files.length} file(s) committed.`);
|
|
3553
|
+
for (const f of result.files) {
|
|
3554
|
+
console.log(` ${f}`);
|
|
3555
|
+
}
|
|
3556
|
+
if (opts.tag) {
|
|
3557
|
+
console.log(` Tagged: ${opts.tag}`);
|
|
3558
|
+
}
|
|
3559
|
+
} else if (result.error) {
|
|
3560
|
+
console.log(result.error);
|
|
3561
|
+
}
|
|
3562
|
+
});
|
|
3563
|
+
versionCmd.command("log").description("Show version history").option("-d, --dir <dir>", "Harness directory", ".").option("-n, --limit <n>", "Max entries", "20").option("-f, --file <path>", "Filter by file path").option("--json", "Output as JSON").action(async (opts) => {
|
|
3564
|
+
const dir = resolve(opts.dir);
|
|
3565
|
+
const { getVersionLog } = await import("../versioning-Z3XNE2Q2.js");
|
|
3566
|
+
const log = getVersionLog(dir, {
|
|
3567
|
+
limit: parseInt(opts.limit, 10),
|
|
3568
|
+
file: opts.file
|
|
3569
|
+
});
|
|
3570
|
+
if (opts.json) {
|
|
3571
|
+
console.log(JSON.stringify(log, null, 2));
|
|
3572
|
+
} else if (log.entries.length === 0) {
|
|
3573
|
+
console.log("No version history. Run `harness version init` first.");
|
|
3574
|
+
} else {
|
|
3575
|
+
console.log(`Version history (${log.entries.length} entries):
|
|
3576
|
+
`);
|
|
3577
|
+
for (const entry of log.entries) {
|
|
3578
|
+
const tag = entry.tag ? ` [${entry.tag}]` : "";
|
|
3579
|
+
console.log(` ${entry.hash} ${entry.message}${tag}`);
|
|
3580
|
+
console.log(` ${entry.timestamp} \u2014 ${entry.filesChanged.length} file(s)`);
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
});
|
|
3584
|
+
versionCmd.command("diff").description("Show changes between versions").argument("<from>", "Source commit hash or tag").argument("[to]", "Target commit hash or tag (default: HEAD)").option("-d, --dir <dir>", "Harness directory", ".").option("--json", "Output as JSON").action(async (from, to, opts) => {
|
|
3585
|
+
const dir = resolve(opts.dir);
|
|
3586
|
+
const { getVersionDiff } = await import("../versioning-Z3XNE2Q2.js");
|
|
3587
|
+
const diff = getVersionDiff(dir, from, to);
|
|
3588
|
+
if (opts.json) {
|
|
3589
|
+
console.log(JSON.stringify(diff, null, 2));
|
|
3590
|
+
} else {
|
|
3591
|
+
console.log(`${diff.summary}
|
|
3592
|
+
`);
|
|
3593
|
+
for (const entry of diff.entries) {
|
|
3594
|
+
const icon = entry.status === "added" ? "+" : entry.status === "deleted" ? "-" : "M";
|
|
3595
|
+
const stats = entry.additions !== void 0 ? ` (+${entry.additions}/-${entry.deletions})` : "";
|
|
3596
|
+
console.log(` [${icon}] ${entry.file}${stats}`);
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
});
|
|
3600
|
+
versionCmd.command("rollback").description("Roll back to a previous version (creates new commit preserving history)").argument("<target>", "Commit hash or tag to roll back to").option("-d, --dir <dir>", "Harness directory", ".").option("--json", "Output as JSON").action(async (target, opts) => {
|
|
3601
|
+
const dir = resolve(opts.dir);
|
|
3602
|
+
const { rollback } = await import("../versioning-Z3XNE2Q2.js");
|
|
3603
|
+
const result = rollback(dir, target);
|
|
3604
|
+
if (opts.json) {
|
|
3605
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3606
|
+
} else if (result.success) {
|
|
3607
|
+
console.log(`Rolled back to ${result.targetHash.slice(0, 7)}.`);
|
|
3608
|
+
console.log(` ${result.restoredFiles.length} file(s) restored.`);
|
|
3609
|
+
} else {
|
|
3610
|
+
console.log(`Rollback failed: ${result.error}`);
|
|
3611
|
+
}
|
|
3612
|
+
});
|
|
3613
|
+
versionCmd.command("tag").description("Tag the current version").argument("<name>", "Tag name (e.g., v1.0.0)").option("-d, --dir <dir>", "Harness directory", ".").option("-m, --message <msg>", "Tag message").action(async (name, opts) => {
|
|
3614
|
+
const dir = resolve(opts.dir);
|
|
3615
|
+
const { tagVersion } = await import("../versioning-Z3XNE2Q2.js");
|
|
3616
|
+
const ok = tagVersion(dir, name, opts.message);
|
|
3617
|
+
console.log(ok ? `Tagged: ${name}` : "Failed to create tag.");
|
|
3618
|
+
});
|
|
3619
|
+
versionCmd.command("tags").description("List all version tags").option("-d, --dir <dir>", "Harness directory", ".").option("--json", "Output as JSON").action(async (opts) => {
|
|
3620
|
+
const dir = resolve(opts.dir);
|
|
3621
|
+
const { listTags } = await import("../versioning-Z3XNE2Q2.js");
|
|
3622
|
+
const tags = listTags(dir);
|
|
3623
|
+
if (opts.json) {
|
|
3624
|
+
console.log(JSON.stringify(tags, null, 2));
|
|
3625
|
+
} else if (tags.length === 0) {
|
|
3626
|
+
console.log("No tags.");
|
|
3627
|
+
} else {
|
|
3628
|
+
for (const t of tags) {
|
|
3629
|
+
console.log(` ${t.tag} \u2192 ${t.hash} (${t.message})`);
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
});
|
|
3633
|
+
versionCmd.command("pending").description("Show uncommitted changes").option("-d, --dir <dir>", "Harness directory", ".").option("--json", "Output as JSON").action(async (opts) => {
|
|
3634
|
+
const dir = resolve(opts.dir);
|
|
3635
|
+
const { getPendingChanges } = await import("../versioning-Z3XNE2Q2.js");
|
|
3636
|
+
const changes = getPendingChanges(dir);
|
|
3637
|
+
if (opts.json) {
|
|
3638
|
+
console.log(JSON.stringify(changes, null, 2));
|
|
3639
|
+
} else if (changes.length === 0) {
|
|
3640
|
+
console.log("No pending changes.");
|
|
3641
|
+
} else {
|
|
3642
|
+
console.log(`${changes.length} pending change(s):
|
|
3643
|
+
`);
|
|
3644
|
+
for (const c of changes) {
|
|
3645
|
+
const icon = c.status === "added" ? "+" : c.status === "deleted" ? "-" : "M";
|
|
3646
|
+
console.log(` [${icon}] ${c.file}`);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
});
|
|
3650
|
+
versionCmd.command("show").description("Show file content at a specific version").argument("<file>", "File path relative to harness").argument("<hash>", "Commit hash or tag").option("-d, --dir <dir>", "Harness directory", ".").action(async (file, hash, opts) => {
|
|
3651
|
+
const dir = resolve(opts.dir);
|
|
3652
|
+
const { getFileAtVersion } = await import("../versioning-Z3XNE2Q2.js");
|
|
3653
|
+
const content = getFileAtVersion(dir, file, hash);
|
|
3654
|
+
if (content === null) {
|
|
3655
|
+
console.log(`File not found at version ${hash}.`);
|
|
3656
|
+
} else {
|
|
3657
|
+
console.log(content);
|
|
3658
|
+
}
|
|
3659
|
+
});
|
|
3660
|
+
program.parse();
|
|
3661
|
+
//# sourceMappingURL=index.js.map
|