@holdpoint/cli 0.1.0-alpha.15 → 0.1.0-alpha.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder-ui/assets/{index-CaEfVl3b.js → index-DezgTDRo.js} +4 -4
- package/dist/builder-ui/assets/{index-CaEfVl3b.js.map → index-DezgTDRo.js.map} +1 -1
- package/dist/builder-ui/index.html +1 -1
- package/dist/index.js +831 -99
- package/dist/index.js.map +1 -1
- package/dist/templates/HOLDPOINT_PREREQUISITES.md +9 -0
- package/package.json +11 -8
package/dist/index.js
CHANGED
|
@@ -5,9 +5,9 @@ import { Command } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
7
|
import { execSync } from "child_process";
|
|
8
|
-
import { existsSync as
|
|
9
|
-
import { join, dirname } from "path";
|
|
10
|
-
import { fileURLToPath } from "url";
|
|
8
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
9
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
10
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
import ora from "ora";
|
|
13
13
|
import { buildConfigJson, buildEngine } from "@holdpoint/engine-copilot";
|
|
@@ -58,34 +58,52 @@ function detectStack() {
|
|
|
58
58
|
return "unknown";
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
// src/
|
|
61
|
+
// src/templates.ts
|
|
62
|
+
import { copyFileSync, existsSync as existsSync2, writeFileSync } from "fs";
|
|
63
|
+
import { join, dirname } from "path";
|
|
64
|
+
import { fileURLToPath } from "url";
|
|
62
65
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
63
|
-
function
|
|
64
|
-
const name = stack === "unknown" ? "_base" : stack;
|
|
66
|
+
function getBundledTemplatePath(filename) {
|
|
65
67
|
const candidates = [
|
|
66
|
-
join(__dirname, "templates",
|
|
68
|
+
join(__dirname, "templates", filename),
|
|
67
69
|
// dist/templates/ (published package)
|
|
68
|
-
join(__dirname, "../../../templates",
|
|
70
|
+
join(__dirname, "../../../templates", filename),
|
|
69
71
|
// monorepo dev fallback
|
|
70
|
-
join(process.cwd(), "templates",
|
|
72
|
+
join(process.cwd(), "templates", filename)
|
|
71
73
|
// cwd fallback
|
|
72
74
|
];
|
|
73
|
-
for (const
|
|
74
|
-
if (existsSync2(
|
|
75
|
+
for (const candidate of candidates) {
|
|
76
|
+
if (existsSync2(candidate)) return candidate;
|
|
75
77
|
}
|
|
76
78
|
return "";
|
|
77
79
|
}
|
|
78
|
-
function
|
|
80
|
+
function ensureBundledFile(outputPath, templateFilename, fallbackContent) {
|
|
81
|
+
if (existsSync2(outputPath)) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const templatePath = getBundledTemplatePath(templateFilename);
|
|
85
|
+
if (templatePath) {
|
|
86
|
+
copyFileSync(templatePath, outputPath);
|
|
87
|
+
} else {
|
|
88
|
+
writeFileSync(outputPath, fallbackContent, "utf8");
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/commands/init.ts
|
|
94
|
+
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
95
|
+
function getTemplatePath(stack) {
|
|
96
|
+
const name = stack === "unknown" ? "_base" : stack;
|
|
79
97
|
const candidates = [
|
|
80
|
-
|
|
98
|
+
join2(__dirname2, "templates", `${name}.yaml`),
|
|
81
99
|
// dist/templates/ (published package)
|
|
82
|
-
|
|
100
|
+
join2(__dirname2, "../../../templates", `${name}.yaml`),
|
|
83
101
|
// monorepo dev fallback
|
|
84
|
-
|
|
102
|
+
join2(process.cwd(), "templates", `${name}.yaml`)
|
|
85
103
|
// cwd fallback
|
|
86
104
|
];
|
|
87
105
|
for (const p of candidates) {
|
|
88
|
-
if (
|
|
106
|
+
if (existsSync3(p)) return p;
|
|
89
107
|
}
|
|
90
108
|
return "";
|
|
91
109
|
}
|
|
@@ -102,6 +120,21 @@ checks:
|
|
|
102
120
|
label: "JSDoc on changed public functions"
|
|
103
121
|
prompt: "Ensure all changed public functions and exports have JSDoc comments."
|
|
104
122
|
`;
|
|
123
|
+
var MINIMAL_MASTER_PROMPT = `# Holdpoint
|
|
124
|
+
|
|
125
|
+
Run \`holdpoint check\` before marking any task complete.
|
|
126
|
+
See \`checks.yaml\` for the full list of checks.
|
|
127
|
+
`;
|
|
128
|
+
var MINIMAL_PREREQUISITES = `# Holdpoint prerequisites
|
|
129
|
+
|
|
130
|
+
Holdpoint installed repo-local adapters for one or more AI coding agents. Before relying on them locally, review these setup notes:
|
|
131
|
+
|
|
132
|
+
- **GitHub Copilot CLI** \u2014 Holdpoint's \`.github/extensions/holdpoint/extension.mjs\` uses the Copilot CLI **EXTENSIONS** feature. Today that feature is gated behind experimental mode. In Copilot CLI, run \`/experimental on\` so **EXTENSIONS** appears in the enabled feature set before using Holdpoint locally.
|
|
133
|
+
- **OpenAI Codex** \u2014 project-level hooks require trust approval. Run \`codex trust\` in the Codex TUI or review the hook with \`/hooks\`.
|
|
134
|
+
- **General** \u2014 Holdpoint expects Node.js 18+ and a git repository so \`holdpoint init\`, \`holdpoint update\`, and \`holdpoint check\` can run normally.
|
|
135
|
+
|
|
136
|
+
Docs: https://holdpoint.dev/docs
|
|
137
|
+
`;
|
|
105
138
|
async function initCommand(options) {
|
|
106
139
|
const spinner = ora("Initialising Holdpoint\u2026").start();
|
|
107
140
|
const stack = options.stack ?? detectStack();
|
|
@@ -110,7 +143,7 @@ async function initCommand(options) {
|
|
|
110
143
|
spinner.text = `Stack: ${chalk.cyan(stack)} \u2014 installing for: ${chalk.cyan(agents.join(", "))}`;
|
|
111
144
|
const pm = detectPackageManager();
|
|
112
145
|
let yamlContent = MINIMAL_CHECKS_YAML;
|
|
113
|
-
if (!
|
|
146
|
+
if (!existsSync3("checks.yaml")) {
|
|
114
147
|
const templatePath = getTemplatePath(stack);
|
|
115
148
|
if (templatePath) {
|
|
116
149
|
yamlContent = readFileSync2(templatePath, "utf8");
|
|
@@ -118,31 +151,31 @@ async function initCommand(options) {
|
|
|
118
151
|
if (pm !== "pnpm") {
|
|
119
152
|
yamlContent = yamlContent.replace(/\bpnpm\b/g, pm);
|
|
120
153
|
}
|
|
121
|
-
|
|
154
|
+
writeFileSync2("checks.yaml", yamlContent, "utf8");
|
|
122
155
|
} else {
|
|
123
156
|
yamlContent = readFileSync2("checks.yaml", "utf8");
|
|
124
157
|
}
|
|
125
158
|
const config = parseHoldpointYaml(yamlContent);
|
|
126
159
|
const generatedDir = ".github/holdpoint/generated";
|
|
127
160
|
mkdirSync(generatedDir, { recursive: true });
|
|
128
|
-
|
|
161
|
+
writeFileSync2(`${generatedDir}/checks.immutable.json`, buildConfigJson(config), "utf8");
|
|
129
162
|
if (agents.includes("copilot")) {
|
|
130
163
|
const extDir = ".github/extensions/holdpoint";
|
|
131
164
|
mkdirSync(extDir, { recursive: true });
|
|
132
|
-
|
|
165
|
+
writeFileSync2(join2(extDir, "extension.mjs"), buildEngine(config), "utf8");
|
|
133
166
|
}
|
|
134
167
|
if (agents.includes("claude")) {
|
|
135
168
|
mkdirSync(".claude", { recursive: true });
|
|
136
169
|
const settingsPath = ".claude/settings.json";
|
|
137
170
|
let existing = {};
|
|
138
|
-
if (
|
|
171
|
+
if (existsSync3(settingsPath)) {
|
|
139
172
|
try {
|
|
140
173
|
existing = JSON.parse(readFileSync2(settingsPath, "utf8"));
|
|
141
174
|
} catch {
|
|
142
175
|
}
|
|
143
176
|
}
|
|
144
177
|
const holdpointHooks = JSON.parse(buildClaudeEngineJson(config));
|
|
145
|
-
|
|
178
|
+
writeFileSync2(
|
|
146
179
|
settingsPath,
|
|
147
180
|
JSON.stringify({ ...existing, hooks: holdpointHooks.hooks }, null, 2),
|
|
148
181
|
"utf8"
|
|
@@ -151,36 +184,30 @@ async function initCommand(options) {
|
|
|
151
184
|
if (agents.includes("cursor")) {
|
|
152
185
|
const cursorRules = buildCursorEngine(config);
|
|
153
186
|
const cursorPath = ".cursorrules";
|
|
154
|
-
if (
|
|
187
|
+
if (existsSync3(cursorPath)) {
|
|
155
188
|
const existing = readFileSync2(cursorPath, "utf8");
|
|
156
189
|
if (!existing.includes("Holdpoint Rules")) {
|
|
157
|
-
|
|
190
|
+
writeFileSync2(cursorPath, existing + "\n" + cursorRules, "utf8");
|
|
158
191
|
}
|
|
159
192
|
} else {
|
|
160
|
-
|
|
193
|
+
writeFileSync2(cursorPath, cursorRules, "utf8");
|
|
161
194
|
}
|
|
162
195
|
}
|
|
163
196
|
if (agents.includes("codex")) {
|
|
164
197
|
mkdirSync(".codex", { recursive: true });
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
198
|
+
writeFileSync2(".codex/hooks.json", buildCodexHooksJson(config), "utf8");
|
|
199
|
+
writeFileSync2(".codex/holdpoint-check.mjs", buildCodexCheckScript(config), "utf8");
|
|
200
|
+
writeFileSync2(".codex/config.toml", buildCodexConfigToml(), "utf8");
|
|
168
201
|
const agentsMdPath = "AGENTS.md";
|
|
169
|
-
const existing =
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
if (!existsSync2("MASTER_PROMPT.md")) {
|
|
173
|
-
const guidePath = getMasterPromptPath();
|
|
174
|
-
if (guidePath) {
|
|
175
|
-
copyFileSync(guidePath, "MASTER_PROMPT.md");
|
|
176
|
-
} else {
|
|
177
|
-
writeFileSync(
|
|
178
|
-
"MASTER_PROMPT.md",
|
|
179
|
-
"# Holdpoint\n\nRun `holdpoint check` before marking any task complete.\nSee `checks.yaml` for the full list of checks.\n",
|
|
180
|
-
"utf8"
|
|
181
|
-
);
|
|
182
|
-
}
|
|
202
|
+
const existing = existsSync3(agentsMdPath) ? readFileSync2(agentsMdPath, "utf8") : "";
|
|
203
|
+
writeFileSync2(agentsMdPath, spliceAgentsMd(existing, config), "utf8");
|
|
183
204
|
}
|
|
205
|
+
ensureBundledFile("MASTER_PROMPT.md", "MASTER_PROMPT.md", MINIMAL_MASTER_PROMPT);
|
|
206
|
+
ensureBundledFile(
|
|
207
|
+
"HOLDPOINT_PREREQUISITES.md",
|
|
208
|
+
"HOLDPOINT_PREREQUISITES.md",
|
|
209
|
+
MINIMAL_PREREQUISITES
|
|
210
|
+
);
|
|
184
211
|
spinner.text = "Installing holdpoint as a devDependency\u2026";
|
|
185
212
|
const installCmds = {
|
|
186
213
|
pnpm: "pnpm add -D holdpoint@alpha",
|
|
@@ -200,8 +227,13 @@ async function initCommand(options) {
|
|
|
200
227
|
console.log(`
|
|
201
228
|
${chalk.cyan("Next steps:")}
|
|
202
229
|
1. Edit ${chalk.yellow("checks.yaml")} to customise your eval checkpoints
|
|
203
|
-
2.
|
|
204
|
-
3.
|
|
230
|
+
2. Review ${chalk.yellow("HOLDPOINT_PREREQUISITES.md")} for agent setup notes
|
|
231
|
+
3. Commit ${chalk.yellow("checks.yaml")}, ${chalk.yellow("HOLDPOINT_PREREQUISITES.md")}, and the generated engine files
|
|
232
|
+
4. Run ${chalk.yellow("holdpoint check")} at any time to validate
|
|
233
|
+
|
|
234
|
+
${chalk.bgYellow.black(" Copilot local use ")} Run ${chalk.yellow("/experimental on")} in GitHub Copilot CLI so the
|
|
235
|
+
${chalk.yellow("EXTENSIONS")} feature is enabled before using Holdpoint locally.
|
|
236
|
+
See ${chalk.yellow("HOLDPOINT_PREREQUISITES.md")} for the full handoff notes.
|
|
205
237
|
|
|
206
238
|
Visual builder: ${chalk.yellow("holdpoint builder")} (opens localhost:4321)
|
|
207
239
|
Stack: ${chalk.cyan(stack)} Agents: ${chalk.cyan(agents.join(", "))}
|
|
@@ -209,13 +241,16 @@ ${chalk.cyan("Next steps:")}
|
|
|
209
241
|
}
|
|
210
242
|
|
|
211
243
|
// src/commands/check.ts
|
|
212
|
-
import { existsSync as
|
|
213
|
-
import { join as
|
|
244
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
245
|
+
import { join as join3 } from "path";
|
|
214
246
|
import chalk2 from "chalk";
|
|
215
247
|
import ora2 from "ora";
|
|
216
248
|
import { parseHoldpointYaml as parseHoldpointYaml2, matchesWhen } from "@holdpoint/yaml-core";
|
|
217
249
|
import { runDeterministicChecks } from "@holdpoint/yaml-core/runner";
|
|
218
250
|
import { execSync as execSync2 } from "child_process";
|
|
251
|
+
import { randomUUID } from "crypto";
|
|
252
|
+
import { identifyProject } from "@holdpoint/live-daemon";
|
|
253
|
+
import { BridgeClient } from "@holdpoint/sdk";
|
|
219
254
|
var COMMIT_CACHE_PATH = ".holdpoint/checked-commits.json";
|
|
220
255
|
var COMMIT_CACHE_MAX = 100;
|
|
221
256
|
var CHECK_REPORTS_PATH = ".holdpoint/check-reports.json";
|
|
@@ -276,16 +311,16 @@ function recordCommitCache(sha) {
|
|
|
276
311
|
try {
|
|
277
312
|
const existing = readCommitCache();
|
|
278
313
|
const updated = [sha, ...[...existing].filter((s) => s !== sha)].slice(0, COMMIT_CACHE_MAX);
|
|
279
|
-
mkdirSync2(
|
|
280
|
-
|
|
314
|
+
mkdirSync2(join3(COMMIT_CACHE_PATH, ".."), { recursive: true });
|
|
315
|
+
writeFileSync3(COMMIT_CACHE_PATH, JSON.stringify({ verified: updated }, null, 2) + "\n", "utf8");
|
|
281
316
|
} catch {
|
|
282
317
|
}
|
|
283
318
|
}
|
|
284
319
|
function recordCheckReport(run) {
|
|
285
320
|
try {
|
|
286
|
-
mkdirSync2(
|
|
321
|
+
mkdirSync2(join3(CHECK_REPORTS_PATH, ".."), { recursive: true });
|
|
287
322
|
let existing = { runs: [] };
|
|
288
|
-
if (
|
|
323
|
+
if (existsSync4(CHECK_REPORTS_PATH)) {
|
|
289
324
|
try {
|
|
290
325
|
existing = JSON.parse(readFileSync3(CHECK_REPORTS_PATH, "utf8"));
|
|
291
326
|
if (!Array.isArray(existing.runs)) existing.runs = [];
|
|
@@ -296,12 +331,12 @@ function recordCheckReport(run) {
|
|
|
296
331
|
const updated = {
|
|
297
332
|
runs: [run, ...existing.runs].slice(0, CHECK_REPORTS_MAX)
|
|
298
333
|
};
|
|
299
|
-
|
|
334
|
+
writeFileSync3(CHECK_REPORTS_PATH, JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
300
335
|
} catch {
|
|
301
336
|
}
|
|
302
337
|
}
|
|
303
338
|
async function checkCommand(options) {
|
|
304
|
-
if (!
|
|
339
|
+
if (!existsSync4("checks.yaml")) {
|
|
305
340
|
console.error(chalk2.red("No checks.yaml found. Run `holdpoint init` first."));
|
|
306
341
|
process.exit(1);
|
|
307
342
|
}
|
|
@@ -420,6 +455,30 @@ ${chalk2.cyan("Agent prompts to act on:")}`);
|
|
|
420
455
|
}
|
|
421
456
|
};
|
|
422
457
|
recordCheckReport(run);
|
|
458
|
+
const project = identifyProject(process.cwd());
|
|
459
|
+
const bridge = new BridgeClient();
|
|
460
|
+
const liveEvents = reportResults.filter(
|
|
461
|
+
(result) => result.kind === "cmd"
|
|
462
|
+
).map((result, index) => ({
|
|
463
|
+
v: 1,
|
|
464
|
+
id: randomUUID(),
|
|
465
|
+
ts: Date.now() + index,
|
|
466
|
+
engine: "holdpoint",
|
|
467
|
+
session_id: "check-runner",
|
|
468
|
+
project_hash: project.hash,
|
|
469
|
+
cwd: process.cwd(),
|
|
470
|
+
type: "check_run",
|
|
471
|
+
payload: {
|
|
472
|
+
check_id: result.id,
|
|
473
|
+
label: result.label,
|
|
474
|
+
status: result.status,
|
|
475
|
+
duration_ms: 0,
|
|
476
|
+
...result.output ? { output: result.output } : {}
|
|
477
|
+
}
|
|
478
|
+
}));
|
|
479
|
+
if (liveEvents.length > 0) {
|
|
480
|
+
await bridge.sendEvents(liveEvents);
|
|
481
|
+
}
|
|
423
482
|
if (failed.length > 0) {
|
|
424
483
|
process.exit(1);
|
|
425
484
|
}
|
|
@@ -441,11 +500,11 @@ function printResult(result) {
|
|
|
441
500
|
}
|
|
442
501
|
|
|
443
502
|
// src/commands/validate.ts
|
|
444
|
-
import { existsSync as
|
|
503
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
445
504
|
import chalk3 from "chalk";
|
|
446
505
|
import { parseHoldpointYaml as parseHoldpointYaml3, validateConfig } from "@holdpoint/yaml-core";
|
|
447
506
|
async function validateCommand() {
|
|
448
|
-
if (!
|
|
507
|
+
if (!existsSync5("checks.yaml")) {
|
|
449
508
|
console.error(chalk3.red("No checks.yaml found. Run `holdpoint init` first."));
|
|
450
509
|
process.exit(1);
|
|
451
510
|
}
|
|
@@ -475,7 +534,7 @@ async function validateCommand() {
|
|
|
475
534
|
}
|
|
476
535
|
|
|
477
536
|
// src/commands/update.ts
|
|
478
|
-
import { existsSync as
|
|
537
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
479
538
|
import chalk4 from "chalk";
|
|
480
539
|
import ora3 from "ora";
|
|
481
540
|
import { parseHoldpointYaml as parseHoldpointYaml4 } from "@holdpoint/yaml-core";
|
|
@@ -488,8 +547,18 @@ import {
|
|
|
488
547
|
buildCheckScript as buildCodexCheckScript2,
|
|
489
548
|
spliceAgentsMd as spliceAgentsMd2
|
|
490
549
|
} from "@holdpoint/engine-codex";
|
|
550
|
+
var MINIMAL_PREREQUISITES2 = `# Holdpoint prerequisites
|
|
551
|
+
|
|
552
|
+
Holdpoint installed repo-local adapters for one or more AI coding agents. Before relying on them locally, review these setup notes:
|
|
553
|
+
|
|
554
|
+
- **GitHub Copilot CLI** \u2014 Holdpoint's \`.github/extensions/holdpoint/extension.mjs\` uses the Copilot CLI **EXTENSIONS** feature. Today that feature is gated behind experimental mode. In Copilot CLI, run \`/experimental on\` so **EXTENSIONS** appears in the enabled feature set before using Holdpoint locally.
|
|
555
|
+
- **OpenAI Codex** \u2014 project-level hooks require trust approval. Run \`codex trust\` in the Codex TUI or review the hook with \`/hooks\`.
|
|
556
|
+
- **General** \u2014 Holdpoint expects Node.js 18+ and a git repository so \`holdpoint init\`, \`holdpoint update\`, and \`holdpoint check\` can run normally.
|
|
557
|
+
|
|
558
|
+
Docs: https://holdpoint.dev/docs
|
|
559
|
+
`;
|
|
491
560
|
async function updateCommand() {
|
|
492
|
-
if (!
|
|
561
|
+
if (!existsSync6("checks.yaml")) {
|
|
493
562
|
console.error(chalk4.red("No checks.yaml found. Run `holdpoint init` first."));
|
|
494
563
|
process.exit(1);
|
|
495
564
|
}
|
|
@@ -499,24 +568,24 @@ async function updateCommand() {
|
|
|
499
568
|
const agents = detected.length > 0 ? detected : ["copilot", "claude", "cursor", "codex"];
|
|
500
569
|
const generatedDir = ".github/holdpoint/generated";
|
|
501
570
|
mkdirSync3(generatedDir, { recursive: true });
|
|
502
|
-
|
|
571
|
+
writeFileSync4(`${generatedDir}/checks.immutable.json`, buildConfigJson2(config), "utf8");
|
|
503
572
|
if (agents.includes("copilot")) {
|
|
504
573
|
const extDir = ".github/extensions/holdpoint";
|
|
505
574
|
mkdirSync3(extDir, { recursive: true });
|
|
506
|
-
|
|
575
|
+
writeFileSync4(`${extDir}/extension.mjs`, buildEngine2(config), "utf8");
|
|
507
576
|
}
|
|
508
577
|
if (agents.includes("claude")) {
|
|
509
578
|
mkdirSync3(".claude", { recursive: true });
|
|
510
579
|
const settingsPath = ".claude/settings.json";
|
|
511
580
|
let existing = {};
|
|
512
|
-
if (
|
|
581
|
+
if (existsSync6(settingsPath)) {
|
|
513
582
|
try {
|
|
514
583
|
existing = JSON.parse(readFileSync5(settingsPath, "utf8"));
|
|
515
584
|
} catch {
|
|
516
585
|
}
|
|
517
586
|
}
|
|
518
587
|
const hooks = JSON.parse(buildClaudeEngineJson2(config));
|
|
519
|
-
|
|
588
|
+
writeFileSync4(
|
|
520
589
|
settingsPath,
|
|
521
590
|
JSON.stringify({ ...existing, hooks: hooks.hooks }, null, 2) + "\n"
|
|
522
591
|
);
|
|
@@ -524,47 +593,59 @@ async function updateCommand() {
|
|
|
524
593
|
if (agents.includes("cursor")) {
|
|
525
594
|
const cursorRules = buildCursorEngine2(config);
|
|
526
595
|
const cursorPath = ".cursorrules";
|
|
527
|
-
if (
|
|
596
|
+
if (existsSync6(cursorPath)) {
|
|
528
597
|
const content = readFileSync5(cursorPath, "utf8");
|
|
529
598
|
const start = content.indexOf("# \u2500\u2500\u2500 Holdpoint Rules");
|
|
530
599
|
const end = content.indexOf("# \u2500\u2500\u2500 End Holdpoint Rules \u2500\u2500\u2500");
|
|
531
600
|
if (start !== -1 && end !== -1) {
|
|
532
601
|
const afterEnd = content.indexOf("\n", end);
|
|
533
602
|
const updated = content.slice(0, start) + cursorRules + content.slice(afterEnd === -1 ? end : afterEnd + 1);
|
|
534
|
-
|
|
603
|
+
writeFileSync4(cursorPath, updated);
|
|
535
604
|
} else {
|
|
536
|
-
|
|
605
|
+
writeFileSync4(cursorPath, content + "\n" + cursorRules);
|
|
537
606
|
}
|
|
538
607
|
}
|
|
539
608
|
}
|
|
540
609
|
if (agents.includes("codex")) {
|
|
541
610
|
mkdirSync3(".codex", { recursive: true });
|
|
542
|
-
|
|
543
|
-
|
|
611
|
+
writeFileSync4(".codex/hooks.json", buildCodexHooksJson2(config), "utf8");
|
|
612
|
+
writeFileSync4(".codex/holdpoint-check.mjs", buildCodexCheckScript2(config), "utf8");
|
|
544
613
|
const configTomlPath = ".codex/config.toml";
|
|
545
|
-
if (!
|
|
546
|
-
|
|
614
|
+
if (!existsSync6(configTomlPath)) {
|
|
615
|
+
writeFileSync4(configTomlPath, buildCodexConfigToml2(), "utf8");
|
|
547
616
|
} else {
|
|
548
617
|
const existing2 = readFileSync5(configTomlPath, "utf8");
|
|
549
618
|
if (!existing2.includes("[features]")) {
|
|
550
|
-
|
|
619
|
+
writeFileSync4(configTomlPath, existing2.trimEnd() + "\n\n" + buildCodexConfigToml2(), "utf8");
|
|
551
620
|
}
|
|
552
621
|
}
|
|
553
622
|
const agentsMdPath = "AGENTS.md";
|
|
554
|
-
const existing =
|
|
555
|
-
|
|
623
|
+
const existing = existsSync6(agentsMdPath) ? readFileSync5(agentsMdPath, "utf8") : "";
|
|
624
|
+
writeFileSync4(agentsMdPath, spliceAgentsMd2(existing, config), "utf8");
|
|
556
625
|
}
|
|
626
|
+
const wrotePrerequisites = ensureBundledFile(
|
|
627
|
+
"HOLDPOINT_PREREQUISITES.md",
|
|
628
|
+
"HOLDPOINT_PREREQUISITES.md",
|
|
629
|
+
MINIMAL_PREREQUISITES2
|
|
630
|
+
);
|
|
557
631
|
spinner.succeed(chalk4.green("Engine files updated from current checks.yaml"));
|
|
632
|
+
if (wrotePrerequisites) {
|
|
633
|
+
console.log(
|
|
634
|
+
chalk4.cyan(
|
|
635
|
+
"Created HOLDPOINT_PREREQUISITES.md with Copilot experimental-mode and other agent setup notes."
|
|
636
|
+
)
|
|
637
|
+
);
|
|
638
|
+
}
|
|
558
639
|
}
|
|
559
640
|
|
|
560
641
|
// src/commands/build.ts
|
|
561
642
|
import { createServer } from "http";
|
|
562
|
-
import { createReadStream, existsSync as
|
|
563
|
-
import { join as
|
|
564
|
-
import { fileURLToPath as
|
|
643
|
+
import { createReadStream, existsSync as existsSync7 } from "fs";
|
|
644
|
+
import { join as join4, extname, dirname as dirname3 } from "path";
|
|
645
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
565
646
|
import { execSync as execSync3 } from "child_process";
|
|
566
647
|
import chalk5 from "chalk";
|
|
567
|
-
var
|
|
648
|
+
var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
|
|
568
649
|
var MIME = {
|
|
569
650
|
".html": "text/html; charset=utf-8",
|
|
570
651
|
".js": "text/javascript",
|
|
@@ -586,8 +667,8 @@ function serveFile(res, filePath) {
|
|
|
586
667
|
function handleRequest(req, res, uiDir) {
|
|
587
668
|
const url = (req.url ?? "/").split("?")[0] ?? "/";
|
|
588
669
|
if (url === "/__holdpoint/initial-yaml") {
|
|
589
|
-
const checksPath =
|
|
590
|
-
if (
|
|
670
|
+
const checksPath = join4(process.cwd(), "checks.yaml");
|
|
671
|
+
if (existsSync7(checksPath)) {
|
|
591
672
|
res.writeHead(200, { "Content-Type": "text/yaml; charset=utf-8" });
|
|
592
673
|
createReadStream(checksPath).pipe(res);
|
|
593
674
|
} else {
|
|
@@ -597,8 +678,8 @@ function handleRequest(req, res, uiDir) {
|
|
|
597
678
|
return;
|
|
598
679
|
}
|
|
599
680
|
if (url === "/__holdpoint/initial-reports") {
|
|
600
|
-
const reportsPath =
|
|
601
|
-
if (
|
|
681
|
+
const reportsPath = join4(process.cwd(), ".holdpoint", "check-reports.json");
|
|
682
|
+
if (existsSync7(reportsPath)) {
|
|
602
683
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
603
684
|
createReadStream(reportsPath).pipe(res);
|
|
604
685
|
} else {
|
|
@@ -607,21 +688,21 @@ function handleRequest(req, res, uiDir) {
|
|
|
607
688
|
}
|
|
608
689
|
return;
|
|
609
690
|
}
|
|
610
|
-
const candidate =
|
|
611
|
-
const filePath =
|
|
691
|
+
const candidate = join4(uiDir, url === "/" ? "index.html" : url);
|
|
692
|
+
const filePath = existsSync7(candidate) ? candidate : join4(uiDir, "index.html");
|
|
612
693
|
serveFile(res, filePath);
|
|
613
694
|
}
|
|
614
695
|
async function buildCommand() {
|
|
615
696
|
const port = 4321;
|
|
616
|
-
const uiDir =
|
|
617
|
-
if (!
|
|
697
|
+
const uiDir = join4(__dirname3, "builder-ui");
|
|
698
|
+
if (!existsSync7(uiDir)) {
|
|
618
699
|
console.error(chalk5.red("\u2717 Builder UI not found.\n"));
|
|
619
700
|
console.log(chalk5.dim(" This is unexpected for a published build of @holdpoint/cli."));
|
|
620
701
|
console.log(chalk5.dim(" If you installed from source, rebuild with: pnpm turbo build\n"));
|
|
621
702
|
process.exit(1);
|
|
622
703
|
}
|
|
623
704
|
const server = createServer((req, res) => handleRequest(req, res, uiDir));
|
|
624
|
-
await new Promise((
|
|
705
|
+
await new Promise((resolve2, reject) => {
|
|
625
706
|
server.listen(port, () => {
|
|
626
707
|
console.log(
|
|
627
708
|
`
|
|
@@ -638,21 +719,21 @@ ${chalk5.green("\u2713")} Holdpoint builder running at ${chalk5.cyan(`http://loc
|
|
|
638
719
|
server.on("error", reject);
|
|
639
720
|
process.on("SIGINT", () => {
|
|
640
721
|
console.log(chalk5.dim("\n Stopping builder\u2026"));
|
|
641
|
-
server.close(() =>
|
|
722
|
+
server.close(() => resolve2());
|
|
642
723
|
});
|
|
643
724
|
});
|
|
644
725
|
}
|
|
645
726
|
|
|
646
727
|
// src/commands/evolve.ts
|
|
647
|
-
import { existsSync as
|
|
728
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
648
729
|
import { execSync as execSync6 } from "child_process";
|
|
649
730
|
import chalk6 from "chalk";
|
|
650
731
|
import ora4 from "ora";
|
|
651
732
|
import { parseHoldpointYaml as parseHoldpointYaml5, generateYaml } from "@holdpoint/yaml-core";
|
|
652
733
|
|
|
653
734
|
// src/evolve/scanner.ts
|
|
654
|
-
import { existsSync as
|
|
655
|
-
import { join as
|
|
735
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync } from "fs";
|
|
736
|
+
import { join as join5 } from "path";
|
|
656
737
|
import { execSync as execSync4 } from "child_process";
|
|
657
738
|
function tryReadJson(path) {
|
|
658
739
|
try {
|
|
@@ -669,17 +750,17 @@ function tryReadText(path) {
|
|
|
669
750
|
}
|
|
670
751
|
}
|
|
671
752
|
function scanProject(cwd = process.cwd()) {
|
|
672
|
-
const exists = (p) =>
|
|
753
|
+
const exists = (p) => existsSync8(join5(cwd, p));
|
|
673
754
|
const packageManager = exists("pnpm-lock.yaml") ? "pnpm" : exists("yarn.lock") ? "yarn" : exists("bun.lockb") ? "bun" : "npm";
|
|
674
|
-
const pkg = tryReadJson(
|
|
755
|
+
const pkg = tryReadJson(join5(cwd, "package.json"));
|
|
675
756
|
const scripts = pkg?.scripts ?? {};
|
|
676
757
|
const deps = /* @__PURE__ */ new Set([
|
|
677
758
|
...Object.keys(pkg?.dependencies ?? {}),
|
|
678
759
|
...Object.keys(pkg?.devDependencies ?? {})
|
|
679
760
|
]);
|
|
680
|
-
const pyprojectText = tryReadText(
|
|
681
|
-
const requirementsText = tryReadText(
|
|
682
|
-
const pipfileText = tryReadText(
|
|
761
|
+
const pyprojectText = tryReadText(join5(cwd, "pyproject.toml"));
|
|
762
|
+
const requirementsText = tryReadText(join5(cwd, "requirements.txt"));
|
|
763
|
+
const pipfileText = tryReadText(join5(cwd, "Pipfile"));
|
|
683
764
|
const allPyText = pyprojectText + requirementsText + pipfileText;
|
|
684
765
|
const hasPytest = exists("pytest.ini") || exists("setup.cfg") || allPyText.includes("pytest") || allPyText.includes("[tool.pytest");
|
|
685
766
|
const hasRuff = allPyText.includes("ruff") || deps.has("ruff");
|
|
@@ -732,8 +813,8 @@ function scanProject(cwd = process.cwd()) {
|
|
|
732
813
|
|
|
733
814
|
// src/evolve/dead-checker.ts
|
|
734
815
|
import { execSync as execSync5 } from "child_process";
|
|
735
|
-
import { readdirSync as readdirSync2, existsSync as
|
|
736
|
-
import { join as
|
|
816
|
+
import { readdirSync as readdirSync2, existsSync as existsSync9 } from "fs";
|
|
817
|
+
import { join as join6 } from "path";
|
|
737
818
|
var NAMED_SCOPES = /* @__PURE__ */ new Set([
|
|
738
819
|
"frontend",
|
|
739
820
|
"backend",
|
|
@@ -778,7 +859,7 @@ function walkDir(dir, root, depth, maxDepth) {
|
|
|
778
859
|
const results = [];
|
|
779
860
|
for (const entry of entries) {
|
|
780
861
|
if (WALK_IGNORED.has(entry) || entry.startsWith(".")) continue;
|
|
781
|
-
const full =
|
|
862
|
+
const full = join6(dir, entry);
|
|
782
863
|
const rel = full.slice(root.length + 1);
|
|
783
864
|
results.push(rel);
|
|
784
865
|
const children = walkDir(full, root, depth + 1, maxDepth);
|
|
@@ -825,7 +906,7 @@ function detectStaleChecks(config, repoFiles) {
|
|
|
825
906
|
if (matches.length === 0) {
|
|
826
907
|
const label = patternAlias ? `Pattern '${patternAlias}' (= '${regexStr}')` : `Regex '${regexStr}'`;
|
|
827
908
|
const suggestedConditionPath = extractPathFromRegex(regexStr);
|
|
828
|
-
const pathGone = !suggestedConditionPath || !
|
|
909
|
+
const pathGone = !suggestedConditionPath || !existsSync9(join6(process.cwd(), suggestedConditionPath));
|
|
829
910
|
if (pathGone) {
|
|
830
911
|
stale.push({
|
|
831
912
|
check,
|
|
@@ -1026,7 +1107,7 @@ function withHeader(header, newYaml) {
|
|
|
1026
1107
|
return header + "\n\n" + newYaml;
|
|
1027
1108
|
}
|
|
1028
1109
|
async function evolveCommand(options) {
|
|
1029
|
-
if (!
|
|
1110
|
+
if (!existsSync10("checks.yaml")) {
|
|
1030
1111
|
console.error(chalk6.red("No checks.yaml found. Run `holdpoint init` first."));
|
|
1031
1112
|
process.exit(1);
|
|
1032
1113
|
}
|
|
@@ -1148,7 +1229,7 @@ async function evolveCommand(options) {
|
|
|
1148
1229
|
};
|
|
1149
1230
|
const header = extractHeader(yamlContent);
|
|
1150
1231
|
const newYaml = withHeader(header, generateYaml(updatedConfig));
|
|
1151
|
-
|
|
1232
|
+
writeFileSync5("checks.yaml", newYaml, "utf8");
|
|
1152
1233
|
applySpinner.text = "Running holdpoint update\u2026";
|
|
1153
1234
|
try {
|
|
1154
1235
|
execSync6("npx @holdpoint/cli@alpha update", { stdio: "pipe" });
|
|
@@ -1173,9 +1254,652 @@ function printAppliedSummary(added, wrapped) {
|
|
|
1173
1254
|
);
|
|
1174
1255
|
}
|
|
1175
1256
|
|
|
1257
|
+
// src/commands/live.ts
|
|
1258
|
+
import { existsSync as existsSync11 } from "fs";
|
|
1259
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
1260
|
+
import chalk7 from "chalk";
|
|
1261
|
+
import { identifyProject as identifyProject2 } from "@holdpoint/live-daemon";
|
|
1262
|
+
|
|
1263
|
+
// src/lib/ensure-daemon.ts
|
|
1264
|
+
import { spawn } from "child_process";
|
|
1265
|
+
import { readHealthyDaemonLock } from "@holdpoint/live-daemon";
|
|
1266
|
+
function sleep(ms) {
|
|
1267
|
+
return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
1268
|
+
}
|
|
1269
|
+
async function ensureDaemon(timeoutMs = 5e3) {
|
|
1270
|
+
const existing = await readHealthyDaemonLock();
|
|
1271
|
+
if (existing) {
|
|
1272
|
+
return { info: existing, started: false };
|
|
1273
|
+
}
|
|
1274
|
+
const cliEntry = process.argv[1];
|
|
1275
|
+
if (!cliEntry) {
|
|
1276
|
+
throw new Error("Cannot determine the current holdpoint CLI entrypoint");
|
|
1277
|
+
}
|
|
1278
|
+
const child = spawn(process.execPath, [cliEntry, "daemon-serve"], {
|
|
1279
|
+
stdio: "ignore",
|
|
1280
|
+
env: process.env,
|
|
1281
|
+
cwd: process.cwd()
|
|
1282
|
+
});
|
|
1283
|
+
child.unref();
|
|
1284
|
+
const deadline = Date.now() + timeoutMs;
|
|
1285
|
+
while (Date.now() < deadline) {
|
|
1286
|
+
const lock = await readHealthyDaemonLock();
|
|
1287
|
+
if (lock) {
|
|
1288
|
+
return { info: lock, started: true };
|
|
1289
|
+
}
|
|
1290
|
+
await sleep(100);
|
|
1291
|
+
}
|
|
1292
|
+
throw new Error("Daemon unavailable + cannot spawn");
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// src/lib/open-browser.ts
|
|
1296
|
+
import { execSync as execSync7 } from "child_process";
|
|
1297
|
+
function openBrowser(url) {
|
|
1298
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1299
|
+
try {
|
|
1300
|
+
execSync7(`${openCmd} ${JSON.stringify(url)}`, { stdio: "ignore" });
|
|
1301
|
+
} catch {
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// src/commands/live.ts
|
|
1306
|
+
function findChecksYaml(startDir) {
|
|
1307
|
+
let current = startDir;
|
|
1308
|
+
for (; ; ) {
|
|
1309
|
+
const candidate = join7(current, "checks.yaml");
|
|
1310
|
+
if (existsSync11(candidate)) {
|
|
1311
|
+
return candidate;
|
|
1312
|
+
}
|
|
1313
|
+
const parent = dirname4(current);
|
|
1314
|
+
if (parent === current) {
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
current = parent;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
function tryResolveCurrentProject() {
|
|
1321
|
+
const checksYaml = findChecksYaml(process.cwd());
|
|
1322
|
+
if (checksYaml) {
|
|
1323
|
+
return identifyProject2(dirname4(checksYaml));
|
|
1324
|
+
}
|
|
1325
|
+
try {
|
|
1326
|
+
return identifyProject2(process.cwd());
|
|
1327
|
+
} catch {
|
|
1328
|
+
return null;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
async function liveCommand(options = {}) {
|
|
1332
|
+
const { info, started } = await ensureDaemon();
|
|
1333
|
+
const baseUrl = new URL(`/__holdpoint/live-auth`, `http://127.0.0.1:${info.port}`);
|
|
1334
|
+
baseUrl.searchParams.set("token", info.token);
|
|
1335
|
+
const currentProject = options.project ? null : tryResolveCurrentProject();
|
|
1336
|
+
if (options.project) {
|
|
1337
|
+
baseUrl.searchParams.set("project", options.project);
|
|
1338
|
+
} else if (currentProject) {
|
|
1339
|
+
baseUrl.searchParams.set("project", currentProject.hash);
|
|
1340
|
+
baseUrl.searchParams.set("name", currentProject.name);
|
|
1341
|
+
baseUrl.searchParams.set("root", currentProject.root);
|
|
1342
|
+
}
|
|
1343
|
+
openBrowser(baseUrl.toString());
|
|
1344
|
+
console.log(
|
|
1345
|
+
chalk7.green(
|
|
1346
|
+
started ? "\u2713 Started Holdpoint Live and opened the browser" : "\u2713 Opened Holdpoint Live"
|
|
1347
|
+
)
|
|
1348
|
+
);
|
|
1349
|
+
console.log(` url: ${chalk7.cyan(baseUrl.toString())}`);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// src/commands/daemon.ts
|
|
1353
|
+
import chalk8 from "chalk";
|
|
1354
|
+
import {
|
|
1355
|
+
DaemonAlreadyRunningError,
|
|
1356
|
+
isProcessAlive,
|
|
1357
|
+
readDaemonLock,
|
|
1358
|
+
readHealthyDaemonLock as readHealthyDaemonLock2,
|
|
1359
|
+
removeDaemonLock,
|
|
1360
|
+
startDaemonProcess
|
|
1361
|
+
} from "@holdpoint/live-daemon";
|
|
1362
|
+
|
|
1363
|
+
// src/version.ts
|
|
1364
|
+
var CLI_VERSION = "0.1.0-alpha.15";
|
|
1365
|
+
|
|
1366
|
+
// src/commands/daemon.ts
|
|
1367
|
+
function formatUptime(lock) {
|
|
1368
|
+
const seconds = Math.max(0, Math.floor((Date.now() - lock.started_at) / 1e3));
|
|
1369
|
+
const minutes = Math.floor(seconds / 60);
|
|
1370
|
+
const remainingSeconds = seconds % 60;
|
|
1371
|
+
return minutes > 0 ? `${minutes}m ${remainingSeconds}s` : `${remainingSeconds}s`;
|
|
1372
|
+
}
|
|
1373
|
+
async function fetchSessionCount(lock) {
|
|
1374
|
+
try {
|
|
1375
|
+
const response = await fetch(`http://127.0.0.1:${lock.port}/v1/sessions`, {
|
|
1376
|
+
headers: {
|
|
1377
|
+
authorization: `Bearer ${lock.token}`
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
if (!response.ok) return null;
|
|
1381
|
+
const parsed = await response.json();
|
|
1382
|
+
return Array.isArray(parsed.sessions) ? parsed.sessions.length : null;
|
|
1383
|
+
} catch {
|
|
1384
|
+
return null;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
function sleep2(ms) {
|
|
1388
|
+
return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
1389
|
+
}
|
|
1390
|
+
async function daemonStartCommand() {
|
|
1391
|
+
const { info, started } = await ensureDaemon();
|
|
1392
|
+
const sessionCount = await fetchSessionCount(info);
|
|
1393
|
+
const headline = started ? "Started Holdpoint Live daemon" : "Reused existing Holdpoint Live daemon";
|
|
1394
|
+
console.log(chalk8.green(`\u2713 ${headline}`));
|
|
1395
|
+
console.log(` pid: ${chalk8.cyan(String(info.pid))}`);
|
|
1396
|
+
console.log(` port: ${chalk8.cyan(String(info.port))}`);
|
|
1397
|
+
console.log(` uptime: ${chalk8.cyan(formatUptime(info))}`);
|
|
1398
|
+
if (sessionCount !== null) {
|
|
1399
|
+
console.log(` sessions: ${chalk8.cyan(String(sessionCount))}`);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
async function daemonStatusCommand() {
|
|
1403
|
+
const lock = await readHealthyDaemonLock2();
|
|
1404
|
+
if (!lock) {
|
|
1405
|
+
console.log(chalk8.yellow("Holdpoint Live daemon is not running."));
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
const sessionCount = await fetchSessionCount(lock);
|
|
1409
|
+
console.log(chalk8.green("\u2713 Holdpoint Live daemon is running"));
|
|
1410
|
+
console.log(` pid: ${chalk8.cyan(String(lock.pid))}`);
|
|
1411
|
+
console.log(` port: ${chalk8.cyan(String(lock.port))}`);
|
|
1412
|
+
console.log(` uptime: ${chalk8.cyan(formatUptime(lock))}`);
|
|
1413
|
+
if (sessionCount !== null) {
|
|
1414
|
+
console.log(` sessions: ${chalk8.cyan(String(sessionCount))}`);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
async function daemonStopCommand() {
|
|
1418
|
+
const lock = readDaemonLock();
|
|
1419
|
+
if (!lock) {
|
|
1420
|
+
console.log(chalk8.yellow("Holdpoint Live daemon is not running."));
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
if (!isProcessAlive(lock.pid)) {
|
|
1424
|
+
removeDaemonLock(void 0, lock.token);
|
|
1425
|
+
console.log(chalk8.yellow("Removed stale Holdpoint Live lockfile."));
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
process.kill(lock.pid, "SIGTERM");
|
|
1429
|
+
const deadline = Date.now() + 5e3;
|
|
1430
|
+
while (Date.now() < deadline) {
|
|
1431
|
+
if (!isProcessAlive(lock.pid)) {
|
|
1432
|
+
removeDaemonLock(void 0, lock.token);
|
|
1433
|
+
console.log(chalk8.green(`\u2713 Stopped Holdpoint Live daemon (${lock.pid})`));
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
await sleep2(100);
|
|
1437
|
+
}
|
|
1438
|
+
process.kill(lock.pid, "SIGKILL");
|
|
1439
|
+
await sleep2(100);
|
|
1440
|
+
removeDaemonLock(void 0, lock.token);
|
|
1441
|
+
console.log(chalk8.green(`\u2713 Force-stopped Holdpoint Live daemon (${lock.pid})`));
|
|
1442
|
+
}
|
|
1443
|
+
async function daemonServeCommand(options) {
|
|
1444
|
+
try {
|
|
1445
|
+
const daemon2 = await startDaemonProcess({
|
|
1446
|
+
version: CLI_VERSION,
|
|
1447
|
+
...options.port ? { port: Number(options.port) } : {}
|
|
1448
|
+
});
|
|
1449
|
+
await daemon2.closed;
|
|
1450
|
+
} catch (error) {
|
|
1451
|
+
if (error instanceof DaemonAlreadyRunningError) {
|
|
1452
|
+
process.exit(0);
|
|
1453
|
+
}
|
|
1454
|
+
throw error;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/commands/engines.ts
|
|
1459
|
+
import chalk9 from "chalk";
|
|
1460
|
+
|
|
1461
|
+
// src/engines.ts
|
|
1462
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
1463
|
+
import { dirname as dirname5, join as join8, resolve, sep } from "path";
|
|
1464
|
+
import { createRequire } from "module";
|
|
1465
|
+
import { fileURLToPath as fileURLToPath4, pathToFileURL } from "url";
|
|
1466
|
+
import { parseEventV1 } from "@holdpoint/live-protocol";
|
|
1467
|
+
var require2 = createRequire(import.meta.url);
|
|
1468
|
+
var CLI_SRC_DIR = dirname5(fileURLToPath4(import.meta.url));
|
|
1469
|
+
var MONOREPO_ROOT = resolve(CLI_SRC_DIR, "../../..");
|
|
1470
|
+
var BUILTIN_LIVE_ENGINE_PACKAGES = ["@holdpoint/engine-claude"];
|
|
1471
|
+
var HOLDPOINT_ENGINE_KEYWORD = "holdpoint-engine";
|
|
1472
|
+
function isObject(value) {
|
|
1473
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
1474
|
+
}
|
|
1475
|
+
function isExternalLiveEnginePackageName(packageName) {
|
|
1476
|
+
return /^holdpoint-engine-[a-z0-9-]+$/.test(packageName) || /^@[a-z0-9_.-]+\/holdpoint-engine-[a-z0-9-]+$/.test(packageName);
|
|
1477
|
+
}
|
|
1478
|
+
function readJsonFile(path) {
|
|
1479
|
+
if (!existsSync12(path)) {
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1482
|
+
try {
|
|
1483
|
+
const parsed = JSON.parse(readFileSync8(path, "utf8"));
|
|
1484
|
+
return isObject(parsed) ? parsed : null;
|
|
1485
|
+
} catch {
|
|
1486
|
+
return null;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
function findNearestPackageRoot(startDir) {
|
|
1490
|
+
let current = resolve(startDir);
|
|
1491
|
+
while (true) {
|
|
1492
|
+
if (existsSync12(join8(current, "package.json"))) {
|
|
1493
|
+
return current;
|
|
1494
|
+
}
|
|
1495
|
+
const parent = dirname5(current);
|
|
1496
|
+
if (parent === current) {
|
|
1497
|
+
return resolve(startDir);
|
|
1498
|
+
}
|
|
1499
|
+
current = parent;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
function findPackageRootFromFile(path) {
|
|
1503
|
+
let current = dirname5(path);
|
|
1504
|
+
while (true) {
|
|
1505
|
+
if (existsSync12(join8(current, "package.json"))) {
|
|
1506
|
+
return current;
|
|
1507
|
+
}
|
|
1508
|
+
const parent = dirname5(current);
|
|
1509
|
+
if (parent === current) {
|
|
1510
|
+
return null;
|
|
1511
|
+
}
|
|
1512
|
+
current = parent;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
function getDependencyEnginePackageNames(projectRoot) {
|
|
1516
|
+
const packageJson = readJsonFile(join8(projectRoot, "package.json"));
|
|
1517
|
+
if (!packageJson) {
|
|
1518
|
+
return [];
|
|
1519
|
+
}
|
|
1520
|
+
const packageNames = /* @__PURE__ */ new Set();
|
|
1521
|
+
for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
1522
|
+
const deps = packageJson[field];
|
|
1523
|
+
if (!isObject(deps)) {
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
for (const packageName of Object.keys(deps)) {
|
|
1527
|
+
if (isExternalLiveEnginePackageName(packageName)) {
|
|
1528
|
+
packageNames.add(packageName);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
return [...packageNames];
|
|
1533
|
+
}
|
|
1534
|
+
function resolvePackageRoot(packageName, projectRoot) {
|
|
1535
|
+
try {
|
|
1536
|
+
const entryPath = require2.resolve(packageName);
|
|
1537
|
+
return findPackageRootFromFile(entryPath);
|
|
1538
|
+
} catch {
|
|
1539
|
+
}
|
|
1540
|
+
try {
|
|
1541
|
+
const entryPath = require2.resolve(packageName, {
|
|
1542
|
+
paths: [projectRoot, process.cwd()]
|
|
1543
|
+
});
|
|
1544
|
+
return findPackageRootFromFile(entryPath);
|
|
1545
|
+
} catch {
|
|
1546
|
+
}
|
|
1547
|
+
try {
|
|
1548
|
+
const packageJsonPath = require2.resolve(`${packageName}/package.json`, {
|
|
1549
|
+
paths: [projectRoot, process.cwd()]
|
|
1550
|
+
});
|
|
1551
|
+
return dirname5(packageJsonPath);
|
|
1552
|
+
} catch {
|
|
1553
|
+
if (packageName.startsWith("@holdpoint/")) {
|
|
1554
|
+
const scopedName = packageName.split("/")[1];
|
|
1555
|
+
if (scopedName) {
|
|
1556
|
+
const packageDir = resolve(MONOREPO_ROOT, "packages", scopedName);
|
|
1557
|
+
if (existsSync12(join8(packageDir, "package.json"))) {
|
|
1558
|
+
return packageDir;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
return null;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
function formatImportError(error) {
|
|
1566
|
+
return error instanceof Error && error.message ? error.message : String(error);
|
|
1567
|
+
}
|
|
1568
|
+
function parseManifest(value) {
|
|
1569
|
+
if (!isObject(value)) {
|
|
1570
|
+
return null;
|
|
1571
|
+
}
|
|
1572
|
+
if (value.manifestVersion !== 1) {
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
if (typeof value.id !== "string" || !/^[a-z0-9-]+$/.test(value.id)) {
|
|
1576
|
+
return null;
|
|
1577
|
+
}
|
|
1578
|
+
if (typeof value.displayName !== "string" || !value.displayName.trim()) {
|
|
1579
|
+
return null;
|
|
1580
|
+
}
|
|
1581
|
+
return {
|
|
1582
|
+
manifestVersion: 1,
|
|
1583
|
+
id: value.id,
|
|
1584
|
+
displayName: value.displayName
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
function parseLiveCapabilities(value) {
|
|
1588
|
+
if (!isObject(value)) {
|
|
1589
|
+
return null;
|
|
1590
|
+
}
|
|
1591
|
+
const capabilities = {};
|
|
1592
|
+
for (const key of [
|
|
1593
|
+
"can_stream",
|
|
1594
|
+
"can_control",
|
|
1595
|
+
"can_modify_context",
|
|
1596
|
+
"can_register_tools",
|
|
1597
|
+
"control_online"
|
|
1598
|
+
]) {
|
|
1599
|
+
const entry = value[key];
|
|
1600
|
+
if (entry === void 0) {
|
|
1601
|
+
continue;
|
|
1602
|
+
}
|
|
1603
|
+
if (typeof entry !== "boolean") {
|
|
1604
|
+
return null;
|
|
1605
|
+
}
|
|
1606
|
+
capabilities[key] = entry;
|
|
1607
|
+
}
|
|
1608
|
+
return capabilities;
|
|
1609
|
+
}
|
|
1610
|
+
function parseLiveAdapter(value, manifest) {
|
|
1611
|
+
if (!isObject(value)) {
|
|
1612
|
+
return null;
|
|
1613
|
+
}
|
|
1614
|
+
if (typeof value.id !== "string" || typeof value.displayName !== "string") {
|
|
1615
|
+
return null;
|
|
1616
|
+
}
|
|
1617
|
+
if (value.id !== manifest.id || value.displayName !== manifest.displayName) {
|
|
1618
|
+
return null;
|
|
1619
|
+
}
|
|
1620
|
+
const capabilities = parseLiveCapabilities(value.capabilities);
|
|
1621
|
+
if (!capabilities) {
|
|
1622
|
+
return null;
|
|
1623
|
+
}
|
|
1624
|
+
const generateBridgeCommand = value.generateBridgeCommand;
|
|
1625
|
+
if (typeof generateBridgeCommand !== "function") {
|
|
1626
|
+
return null;
|
|
1627
|
+
}
|
|
1628
|
+
const translateHookInput = value.translateHookInput;
|
|
1629
|
+
if (typeof translateHookInput !== "function") {
|
|
1630
|
+
return null;
|
|
1631
|
+
}
|
|
1632
|
+
return {
|
|
1633
|
+
id: value.id,
|
|
1634
|
+
displayName: value.displayName,
|
|
1635
|
+
capabilities,
|
|
1636
|
+
generateBridgeCommand: () => {
|
|
1637
|
+
const command = generateBridgeCommand();
|
|
1638
|
+
if (typeof command !== "string") {
|
|
1639
|
+
throw new Error("adapter.generateBridgeCommand() must return a string");
|
|
1640
|
+
}
|
|
1641
|
+
return command;
|
|
1642
|
+
},
|
|
1643
|
+
translateHookInput: (raw, options) => {
|
|
1644
|
+
const event = translateHookInput(raw, options);
|
|
1645
|
+
return event == null ? null : parseEventV1(event);
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
async function importModule(modulePath) {
|
|
1650
|
+
const moduleUrl = pathToFileURL(modulePath).href;
|
|
1651
|
+
return await import(moduleUrl);
|
|
1652
|
+
}
|
|
1653
|
+
function resolvePackageAssetPath(packageRoot, relativePath) {
|
|
1654
|
+
const declaredPath = resolve(packageRoot, relativePath);
|
|
1655
|
+
const sourceFallback = resolve(
|
|
1656
|
+
packageRoot,
|
|
1657
|
+
relativePath.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts")
|
|
1658
|
+
);
|
|
1659
|
+
if (packageRoot.startsWith(resolve(MONOREPO_ROOT, "packages") + sep) && existsSync12(sourceFallback)) {
|
|
1660
|
+
return sourceFallback;
|
|
1661
|
+
}
|
|
1662
|
+
if (existsSync12(declaredPath)) {
|
|
1663
|
+
return declaredPath;
|
|
1664
|
+
}
|
|
1665
|
+
return sourceFallback;
|
|
1666
|
+
}
|
|
1667
|
+
async function resolveCandidate(packageName, source, projectRoot) {
|
|
1668
|
+
const packageRoot = resolvePackageRoot(packageName, projectRoot);
|
|
1669
|
+
if (!packageRoot) {
|
|
1670
|
+
return {
|
|
1671
|
+
packageName,
|
|
1672
|
+
source,
|
|
1673
|
+
status: "ignored",
|
|
1674
|
+
reason: "package could not be resolved from this project"
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
const packageJson = readJsonFile(join8(packageRoot, "package.json"));
|
|
1678
|
+
if (!packageJson) {
|
|
1679
|
+
return {
|
|
1680
|
+
packageName,
|
|
1681
|
+
source,
|
|
1682
|
+
status: "ignored",
|
|
1683
|
+
reason: "package.json could not be read"
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
const keywords = Array.isArray(packageJson.keywords) ? packageJson.keywords : [];
|
|
1687
|
+
if (!keywords.includes(HOLDPOINT_ENGINE_KEYWORD)) {
|
|
1688
|
+
return {
|
|
1689
|
+
packageName,
|
|
1690
|
+
source,
|
|
1691
|
+
status: "ignored",
|
|
1692
|
+
reason: `missing \`${HOLDPOINT_ENGINE_KEYWORD}\` keyword`
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
const metadata = isObject(packageJson.holdpoint) ? packageJson.holdpoint : void 0;
|
|
1696
|
+
if (!metadata?.manifest) {
|
|
1697
|
+
return {
|
|
1698
|
+
packageName,
|
|
1699
|
+
source,
|
|
1700
|
+
status: "ignored",
|
|
1701
|
+
reason: "missing `holdpoint.manifest` package.json field"
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
if (!metadata.adapter) {
|
|
1705
|
+
return {
|
|
1706
|
+
packageName,
|
|
1707
|
+
source,
|
|
1708
|
+
status: "ignored",
|
|
1709
|
+
reason: "missing `holdpoint.adapter` package.json field"
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
const manifestPath = resolvePackageAssetPath(packageRoot, metadata.manifest);
|
|
1713
|
+
const adapterPath = resolvePackageAssetPath(packageRoot, metadata.adapter);
|
|
1714
|
+
if (!existsSync12(manifestPath)) {
|
|
1715
|
+
return {
|
|
1716
|
+
packageName,
|
|
1717
|
+
source,
|
|
1718
|
+
status: "ignored",
|
|
1719
|
+
reason: "manifest file does not exist"
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
if (!existsSync12(adapterPath)) {
|
|
1723
|
+
return {
|
|
1724
|
+
packageName,
|
|
1725
|
+
source,
|
|
1726
|
+
status: "ignored",
|
|
1727
|
+
reason: "adapter file does not exist"
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
try {
|
|
1731
|
+
const manifestModule = await importModule(manifestPath);
|
|
1732
|
+
const manifest = parseManifest(manifestModule.manifest);
|
|
1733
|
+
if (!manifest) {
|
|
1734
|
+
return {
|
|
1735
|
+
packageName,
|
|
1736
|
+
source,
|
|
1737
|
+
status: "ignored",
|
|
1738
|
+
reason: "manifest export is invalid"
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
return {
|
|
1742
|
+
packageName,
|
|
1743
|
+
source,
|
|
1744
|
+
status: "loaded",
|
|
1745
|
+
manifest,
|
|
1746
|
+
packageRoot,
|
|
1747
|
+
adapterPath
|
|
1748
|
+
};
|
|
1749
|
+
} catch (error) {
|
|
1750
|
+
return {
|
|
1751
|
+
packageName,
|
|
1752
|
+
source,
|
|
1753
|
+
status: "ignored",
|
|
1754
|
+
reason: `manifest import failed: ${formatImportError(error)}`
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
async function discoverLiveEnginesDetailed(options) {
|
|
1759
|
+
const projectRoot = findNearestPackageRoot(options?.cwd ?? process.cwd());
|
|
1760
|
+
const dependencyPackages = getDependencyEnginePackageNames(projectRoot);
|
|
1761
|
+
const seenPackages = /* @__PURE__ */ new Set();
|
|
1762
|
+
const results = [];
|
|
1763
|
+
const loadedIds = /* @__PURE__ */ new Set();
|
|
1764
|
+
const candidates = [
|
|
1765
|
+
...BUILTIN_LIVE_ENGINE_PACKAGES.map((packageName) => ({
|
|
1766
|
+
packageName,
|
|
1767
|
+
source: "built-in"
|
|
1768
|
+
})),
|
|
1769
|
+
...dependencyPackages.map((packageName) => ({ packageName, source: "dependency" }))
|
|
1770
|
+
];
|
|
1771
|
+
for (const candidate of candidates) {
|
|
1772
|
+
if (seenPackages.has(candidate.packageName)) {
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
seenPackages.add(candidate.packageName);
|
|
1776
|
+
const result = await resolveCandidate(candidate.packageName, candidate.source, projectRoot);
|
|
1777
|
+
if (result.status === "loaded" && result.manifest) {
|
|
1778
|
+
if (loadedIds.has(result.manifest.id)) {
|
|
1779
|
+
results.push({
|
|
1780
|
+
packageName: result.packageName,
|
|
1781
|
+
source: result.source,
|
|
1782
|
+
status: "ignored",
|
|
1783
|
+
reason: `engine id \`${result.manifest.id}\` collides with an already loaded adapter`,
|
|
1784
|
+
manifest: result.manifest
|
|
1785
|
+
});
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
loadedIds.add(result.manifest.id);
|
|
1789
|
+
}
|
|
1790
|
+
results.push(result);
|
|
1791
|
+
}
|
|
1792
|
+
return results;
|
|
1793
|
+
}
|
|
1794
|
+
async function discoverLiveEngines(options) {
|
|
1795
|
+
const results = await discoverLiveEnginesDetailed(options);
|
|
1796
|
+
return results.map(({ packageName, source, status, reason, manifest }) => ({
|
|
1797
|
+
packageName,
|
|
1798
|
+
source,
|
|
1799
|
+
status,
|
|
1800
|
+
...reason ? { reason } : {},
|
|
1801
|
+
...manifest ? { manifest } : {}
|
|
1802
|
+
}));
|
|
1803
|
+
}
|
|
1804
|
+
async function loadLiveAdapter(engineId, options) {
|
|
1805
|
+
const results = await discoverLiveEnginesDetailed(options);
|
|
1806
|
+
const match = results.find(
|
|
1807
|
+
(result) => result.status === "loaded" && result.manifest?.id === engineId
|
|
1808
|
+
);
|
|
1809
|
+
if (!match?.adapterPath || !match.manifest) {
|
|
1810
|
+
return null;
|
|
1811
|
+
}
|
|
1812
|
+
try {
|
|
1813
|
+
const adapterModule = await importModule(match.adapterPath);
|
|
1814
|
+
return parseLiveAdapter(adapterModule.adapter, match.manifest);
|
|
1815
|
+
} catch {
|
|
1816
|
+
return null;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// src/commands/engines.ts
|
|
1821
|
+
async function enginesCommand(options = {}) {
|
|
1822
|
+
const engines = await discoverLiveEngines();
|
|
1823
|
+
if (options.json) {
|
|
1824
|
+
console.log(JSON.stringify(engines, null, 2));
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
if (engines.length === 0) {
|
|
1828
|
+
console.log("No Holdpoint live adapters were discovered.");
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
for (const engine of engines) {
|
|
1832
|
+
if (engine.status === "loaded" && engine.manifest) {
|
|
1833
|
+
console.log(
|
|
1834
|
+
`${chalk9.green("loaded")} ${chalk9.cyan(engine.manifest.id)} (${engine.manifest.displayName}) from ${chalk9.yellow(engine.packageName)} [${engine.source}]`
|
|
1835
|
+
);
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
console.log(
|
|
1839
|
+
`${chalk9.yellow("ignored")} ${chalk9.yellow(engine.packageName)} [${engine.source}] \u2014 ${engine.reason ?? "unknown reason"}`
|
|
1840
|
+
);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// src/commands/event.ts
|
|
1845
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
1846
|
+
import { parseEventV1 as parseEventV12, parseEventsBatch } from "@holdpoint/live-protocol";
|
|
1847
|
+
import { BridgeClient as BridgeClient2 } from "@holdpoint/sdk";
|
|
1848
|
+
function readStdin() {
|
|
1849
|
+
return readFileSync9(0, "utf8");
|
|
1850
|
+
}
|
|
1851
|
+
async function eventCommand(options) {
|
|
1852
|
+
const stdin = readStdin().trim();
|
|
1853
|
+
if (!stdin) {
|
|
1854
|
+
if (options.fromHook) {
|
|
1855
|
+
process.exit(0);
|
|
1856
|
+
}
|
|
1857
|
+
console.error("No JSON input received on stdin.");
|
|
1858
|
+
process.exit(3);
|
|
1859
|
+
}
|
|
1860
|
+
let raw;
|
|
1861
|
+
try {
|
|
1862
|
+
raw = JSON.parse(stdin);
|
|
1863
|
+
} catch {
|
|
1864
|
+
if (options.fromHook) {
|
|
1865
|
+
process.exit(0);
|
|
1866
|
+
}
|
|
1867
|
+
console.error("Invalid JSON input.");
|
|
1868
|
+
process.exit(3);
|
|
1869
|
+
}
|
|
1870
|
+
const client = new BridgeClient2();
|
|
1871
|
+
try {
|
|
1872
|
+
if (options.fromHook) {
|
|
1873
|
+
if (!options.engine) {
|
|
1874
|
+
process.exit(0);
|
|
1875
|
+
}
|
|
1876
|
+
const adapter = await loadLiveAdapter(options.engine);
|
|
1877
|
+
if (!adapter) {
|
|
1878
|
+
process.exit(0);
|
|
1879
|
+
}
|
|
1880
|
+
const event = adapter.translateHookInput(raw, { cwd: process.cwd() });
|
|
1881
|
+
if (!event) {
|
|
1882
|
+
process.exit(0);
|
|
1883
|
+
}
|
|
1884
|
+
await client.sendEvent(event);
|
|
1885
|
+
process.exit(0);
|
|
1886
|
+
}
|
|
1887
|
+
if (Array.isArray(raw)) {
|
|
1888
|
+
await client.sendEvents(parseEventsBatch(raw));
|
|
1889
|
+
} else {
|
|
1890
|
+
await client.sendEvent(parseEventV12(raw));
|
|
1891
|
+
}
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
console.error(error.message);
|
|
1894
|
+
process.exit(3);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1176
1898
|
// src/index.ts
|
|
1177
1899
|
var program = new Command();
|
|
1178
|
-
program.name("holdpoint").description("Universal eval-guard for AI coding agents (alpha)").version(
|
|
1900
|
+
program.name("holdpoint").description("Universal eval-guard for AI coding agents (alpha)").version(CLI_VERSION).action(() => {
|
|
1901
|
+
void liveCommand();
|
|
1902
|
+
});
|
|
1179
1903
|
program.command("init").description("Initialise Holdpoint in the current project").option("--stack <stack>", "Stack type: typescript | python | nextjs | fullstack").option(
|
|
1180
1904
|
"--agent <agent>",
|
|
1181
1905
|
"Agent to install for: copilot | claude | cursor | codex (default: all four)"
|
|
@@ -1184,6 +1908,14 @@ program.command("check").description("Run task checks from checks.yaml").option(
|
|
|
1184
1908
|
program.command("validate").description("Validate checks.yaml schema and print any errors").action(validateCommand);
|
|
1185
1909
|
program.command("update").description("Regenerate engine files from current checks.yaml (preserves checks.yaml)").action(updateCommand);
|
|
1186
1910
|
program.command("builder").description("Open the visual builder UI on localhost:4321").action(buildCommand);
|
|
1911
|
+
program.command("live").description("Open the Holdpoint Live UI").option("--project <project>", "Open the UI focused on a specific project hash").action(liveCommand);
|
|
1912
|
+
var daemon = program.command("daemon").description("Manage the Holdpoint Live daemon");
|
|
1913
|
+
daemon.command("start").description("Start or connect to the singleton Holdpoint Live daemon").action(daemonStartCommand);
|
|
1914
|
+
daemon.command("status").description("Show Holdpoint Live daemon status").action(daemonStatusCommand);
|
|
1915
|
+
daemon.command("stop").description("Stop the running Holdpoint Live daemon").action(daemonStopCommand);
|
|
1916
|
+
program.command("event").description("Internal: read event JSON from stdin and publish it to Holdpoint Live").option("--engine <engine>", "Engine name when converting native hook payloads").option("--from-hook", "Interpret stdin as an engine-native hook payload").action(eventCommand);
|
|
1917
|
+
program.command("engines").description("List discovered Holdpoint Live adapter packages").option("--json", "Print machine-readable discovery output").action(enginesCommand);
|
|
1918
|
+
program.command("daemon-serve").description("Internal: run the Holdpoint Live daemon in the foreground").option("--port <port>", "Fixed port for the daemon process").action(daemonServeCommand);
|
|
1187
1919
|
program.command("evolve").description("Scan project and propose (or apply) new checks to keep checks.yaml in sync").option("--apply", "Write proposed changes to checks.yaml and regenerate engine files").action(evolveCommand);
|
|
1188
1920
|
program.parse();
|
|
1189
1921
|
//# sourceMappingURL=index.js.map
|