@cockpit-ai/cli 0.1.0
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/index.d.ts +2 -0
- package/dist/index.js +1444 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1444 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import { mkdirSync, existsSync, writeFileSync } from "fs";
|
|
8
|
+
import { join, resolve } from "path";
|
|
9
|
+
import { createInterface } from "readline";
|
|
10
|
+
import { COCKPIT_DIR, CONFIG_FILE } from "@cockpit-ai/core";
|
|
11
|
+
|
|
12
|
+
// src/ui/output.ts
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
var ui = {
|
|
15
|
+
success: (msg) => console.log(chalk.green("\u2713") + " " + msg),
|
|
16
|
+
error: (msg) => console.error(chalk.red("\u2717") + " " + msg),
|
|
17
|
+
warn: (msg) => console.warn(chalk.yellow("!") + " " + msg),
|
|
18
|
+
info: (msg) => console.log(chalk.blue("\u2192") + " " + msg),
|
|
19
|
+
dim: (msg) => console.log(chalk.dim(msg)),
|
|
20
|
+
heading: (msg) => console.log("\n" + chalk.bold(msg)),
|
|
21
|
+
blank: () => console.log()
|
|
22
|
+
};
|
|
23
|
+
function formatKey(key) {
|
|
24
|
+
return chalk.cyan(key);
|
|
25
|
+
}
|
|
26
|
+
function formatValue(value) {
|
|
27
|
+
if (value == null || value === "") return chalk.dim("(none)");
|
|
28
|
+
return chalk.white(value);
|
|
29
|
+
}
|
|
30
|
+
function formatList(items) {
|
|
31
|
+
if (items.length === 0) return chalk.dim("(empty)");
|
|
32
|
+
return items.map((i) => chalk.white(i)).join(", ");
|
|
33
|
+
}
|
|
34
|
+
function printKeyValue(key, value) {
|
|
35
|
+
console.log(` ${formatKey(key.padEnd(20))} ${formatValue(value)}`);
|
|
36
|
+
}
|
|
37
|
+
function printKeyList(key, items) {
|
|
38
|
+
console.log(` ${formatKey(key.padEnd(20))} ${formatList(items)}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/commands/init.ts
|
|
42
|
+
async function prompt(question, defaultValue) {
|
|
43
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
44
|
+
const display = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
|
|
45
|
+
return new Promise((resolve9) => {
|
|
46
|
+
rl.question(display, (answer) => {
|
|
47
|
+
rl.close();
|
|
48
|
+
resolve9(answer.trim() || defaultValue || "");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function workspaceTemplate(name) {
|
|
53
|
+
return `cockpit: "1.0"
|
|
54
|
+
|
|
55
|
+
workspace:
|
|
56
|
+
name: ${name}
|
|
57
|
+
default_adapter: claude-code
|
|
58
|
+
|
|
59
|
+
adapters:
|
|
60
|
+
- claude-code
|
|
61
|
+
|
|
62
|
+
context:
|
|
63
|
+
global:
|
|
64
|
+
- "Follow project coding conventions"
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
function projectTemplate(name) {
|
|
68
|
+
return `cockpit: "1.0"
|
|
69
|
+
|
|
70
|
+
project:
|
|
71
|
+
name: ${name}
|
|
72
|
+
|
|
73
|
+
context:
|
|
74
|
+
global: []
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
async function initCommand(targetPath, options) {
|
|
78
|
+
const dir = resolve(targetPath ?? process.cwd());
|
|
79
|
+
const cockpitDir = join(dir, COCKPIT_DIR);
|
|
80
|
+
const configPath = join(cockpitDir, CONFIG_FILE);
|
|
81
|
+
if (existsSync(configPath)) {
|
|
82
|
+
ui.warn(`Cockpit config already exists at ${configPath}`);
|
|
83
|
+
ui.info("Use 'cockpit status' to view the current configuration.");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const isProject = options.project === true;
|
|
87
|
+
const kind = isProject ? "project" : "workspace";
|
|
88
|
+
ui.heading(`Initializing Cockpit ${kind}`);
|
|
89
|
+
ui.info(`Target directory: ${dir}`);
|
|
90
|
+
ui.blank();
|
|
91
|
+
const defaultName = dir.split("/").at(-1) ?? "my-workspace";
|
|
92
|
+
const name = await prompt(`${kind} name`, defaultName);
|
|
93
|
+
mkdirSync(cockpitDir, { recursive: true });
|
|
94
|
+
const content = isProject ? projectTemplate(name) : workspaceTemplate(name);
|
|
95
|
+
writeFileSync(configPath, content, "utf-8");
|
|
96
|
+
ui.blank();
|
|
97
|
+
ui.success(`Created ${configPath}`);
|
|
98
|
+
ui.blank();
|
|
99
|
+
ui.dim("Next steps:");
|
|
100
|
+
ui.dim(` cockpit status \u2014 view current environment`);
|
|
101
|
+
ui.dim(` cockpit skill list \u2014 browse available skills`);
|
|
102
|
+
ui.dim(` cockpit apply \u2014 apply config to AI tools`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/commands/status.ts
|
|
106
|
+
import { resolve as resolve2 } from "path";
|
|
107
|
+
import chalk2 from "chalk";
|
|
108
|
+
import {
|
|
109
|
+
findConfigPaths,
|
|
110
|
+
resolveConfig
|
|
111
|
+
} from "@cockpit-ai/core";
|
|
112
|
+
async function statusCommand(targetPath) {
|
|
113
|
+
const cwd = resolve2(targetPath ?? process.cwd());
|
|
114
|
+
const paths = findConfigPaths(cwd);
|
|
115
|
+
if (!paths.workspacePath && !paths.projectPath) {
|
|
116
|
+
ui.warn("No Cockpit configuration found.");
|
|
117
|
+
ui.dim(`Searched from: ${cwd}`);
|
|
118
|
+
ui.blank();
|
|
119
|
+
ui.info("Run 'cockpit init' to initialize a workspace.");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const config = resolveConfig(paths);
|
|
123
|
+
ui.heading("Cockpit Status");
|
|
124
|
+
console.log(chalk2.bold(" Workspace"));
|
|
125
|
+
printKeyValue("Name", config.name);
|
|
126
|
+
printKeyValue("Default Adapter", config.defaultAdapter);
|
|
127
|
+
printKeyList("Adapters", config.adapters);
|
|
128
|
+
ui.blank();
|
|
129
|
+
console.log(chalk2.bold(" Config Paths"));
|
|
130
|
+
printKeyValue("Profile", config.profilePath);
|
|
131
|
+
printKeyValue("Workspace", config.workspacePath);
|
|
132
|
+
printKeyValue("Project", config.projectPath);
|
|
133
|
+
ui.blank();
|
|
134
|
+
console.log(chalk2.bold(" Preferences"));
|
|
135
|
+
printKeyValue("Language", config.preferences.language);
|
|
136
|
+
printKeyValue("Default Model", config.preferences.defaultModel);
|
|
137
|
+
ui.blank();
|
|
138
|
+
if (config.context.global.length > 0) {
|
|
139
|
+
console.log(chalk2.bold(" Context Rules"));
|
|
140
|
+
for (const rule of config.context.global) {
|
|
141
|
+
console.log(` ${chalk2.dim("\u2022")} ${rule}`);
|
|
142
|
+
}
|
|
143
|
+
ui.blank();
|
|
144
|
+
}
|
|
145
|
+
if (config.skills.include.length > 0 || config.agents.include.length > 0) {
|
|
146
|
+
console.log(chalk2.bold(" Resources"));
|
|
147
|
+
if (config.skills.include.length > 0) {
|
|
148
|
+
printKeyList("Skill paths", config.skills.include);
|
|
149
|
+
}
|
|
150
|
+
if (config.agents.include.length > 0) {
|
|
151
|
+
printKeyList("Agent paths", config.agents.include);
|
|
152
|
+
}
|
|
153
|
+
ui.blank();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/commands/apply.ts
|
|
158
|
+
import { resolve as resolve3, join as join2 } from "path";
|
|
159
|
+
import ora from "ora";
|
|
160
|
+
import {
|
|
161
|
+
findConfigPaths as findConfigPaths2,
|
|
162
|
+
resolveConfig as resolveConfig2,
|
|
163
|
+
buildResolvedContext,
|
|
164
|
+
COCKPIT_DIR as COCKPIT_DIR2
|
|
165
|
+
} from "@cockpit-ai/core";
|
|
166
|
+
import { getAdapters } from "@cockpit-ai/adapters";
|
|
167
|
+
import { SkillRegistry } from "@cockpit-ai/skills";
|
|
168
|
+
async function applyCommand(options) {
|
|
169
|
+
const cwd = resolve3(process.cwd());
|
|
170
|
+
const paths = findConfigPaths2(cwd);
|
|
171
|
+
if (!paths.workspacePath && !paths.projectPath) {
|
|
172
|
+
ui.error("No Cockpit configuration found.");
|
|
173
|
+
ui.info("Run 'cockpit init' to initialize a workspace.");
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
const config = resolveConfig2(paths);
|
|
177
|
+
const targetAdapterNames = options.adapter ? [options.adapter] : config.adapters;
|
|
178
|
+
const adapters = getAdapters(targetAdapterNames);
|
|
179
|
+
if (adapters.length === 0) {
|
|
180
|
+
ui.warn("No supported adapters found.");
|
|
181
|
+
ui.dim(`Requested: ${targetAdapterNames.join(", ")}`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
ui.heading("Cockpit Apply");
|
|
185
|
+
ui.info(`Workspace: ${config.name}`);
|
|
186
|
+
ui.blank();
|
|
187
|
+
const skillDirs = config.skills.include.map((p) => {
|
|
188
|
+
if (p.startsWith(".")) {
|
|
189
|
+
const root = paths.workspacePath ? resolve3(join2(paths.workspacePath, "..", "..")) : cwd;
|
|
190
|
+
return resolve3(join2(root, p));
|
|
191
|
+
}
|
|
192
|
+
return resolve3(p);
|
|
193
|
+
});
|
|
194
|
+
const workspaceRoot = paths.workspacePath ? resolve3(join2(paths.workspacePath, "..", "..")) : null;
|
|
195
|
+
const projectRoot = paths.projectPath ? resolve3(join2(paths.projectPath, "..", "..")) : null;
|
|
196
|
+
const defaultDirs = [
|
|
197
|
+
workspaceRoot ? join2(workspaceRoot, COCKPIT_DIR2, "skills") : null,
|
|
198
|
+
projectRoot ? join2(projectRoot, COCKPIT_DIR2, "skills") : null
|
|
199
|
+
].filter(Boolean);
|
|
200
|
+
const allSkillDirs = [.../* @__PURE__ */ new Set([...defaultDirs, ...skillDirs])];
|
|
201
|
+
const registry = new SkillRegistry();
|
|
202
|
+
const loadErrors = registry.loadFromDirs(allSkillDirs);
|
|
203
|
+
for (const { file, error } of loadErrors) {
|
|
204
|
+
ui.warn(`Skipped skill ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
205
|
+
}
|
|
206
|
+
const skills = registry.list();
|
|
207
|
+
const context = buildResolvedContext(
|
|
208
|
+
config.context.global,
|
|
209
|
+
config.context.project,
|
|
210
|
+
"cockpit"
|
|
211
|
+
);
|
|
212
|
+
for (const adapter of adapters) {
|
|
213
|
+
const spinner = ora(`Applying to ${adapter.name}\u2026`).start();
|
|
214
|
+
try {
|
|
215
|
+
if (options.clean) {
|
|
216
|
+
await adapter.clean(cwd);
|
|
217
|
+
spinner.info(`Cleaned ${adapter.name}`);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (context.global.length > 0 || context.project.length > 0) {
|
|
221
|
+
await adapter.applyContext(cwd, context);
|
|
222
|
+
}
|
|
223
|
+
for (const skill of skills) {
|
|
224
|
+
await adapter.applySkill(cwd, skill);
|
|
225
|
+
}
|
|
226
|
+
const parts = [];
|
|
227
|
+
if (context.global.length + context.project.length > 0) {
|
|
228
|
+
parts.push(`${context.global.length + context.project.length} context rules`);
|
|
229
|
+
}
|
|
230
|
+
if (skills.length > 0) {
|
|
231
|
+
parts.push(`${skills.length} skill${skills.length === 1 ? "" : "s"}`);
|
|
232
|
+
}
|
|
233
|
+
spinner.succeed(
|
|
234
|
+
`${adapter.name}: ${parts.length > 0 ? parts.join(", ") : "nothing to apply"}`
|
|
235
|
+
);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
spinner.fail(`${adapter.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
ui.blank();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/commands/skill.ts
|
|
244
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, cpSync, rmSync } from "fs";
|
|
245
|
+
import { join as join3, resolve as resolve4, basename, extname } from "path";
|
|
246
|
+
import { findConfigPaths as findConfigPaths3, COCKPIT_DIR as COCKPIT_DIR3 } from "@cockpit-ai/core";
|
|
247
|
+
import { SkillRegistry as SkillRegistry2, defaultSkillTemplate } from "@cockpit-ai/skills";
|
|
248
|
+
import chalk3 from "chalk";
|
|
249
|
+
var SKILLS_SUBDIR = "skills";
|
|
250
|
+
function getSkillsDir(cockpitRoot) {
|
|
251
|
+
return join3(cockpitRoot, COCKPIT_DIR3, SKILLS_SUBDIR);
|
|
252
|
+
}
|
|
253
|
+
function findCockpitRoot(cwd) {
|
|
254
|
+
const paths = findConfigPaths3(cwd);
|
|
255
|
+
if (paths.projectPath) return resolve4(join3(paths.projectPath, "..", ".."));
|
|
256
|
+
if (paths.workspacePath) return resolve4(join3(paths.workspacePath, "..", ".."));
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
function collectAllSkillDirs(cwd) {
|
|
260
|
+
const paths = findConfigPaths3(cwd);
|
|
261
|
+
const dirs = [];
|
|
262
|
+
if (paths.workspacePath) {
|
|
263
|
+
dirs.push(getSkillsDir(resolve4(join3(paths.workspacePath, "..", ".."))));
|
|
264
|
+
}
|
|
265
|
+
if (paths.projectPath) {
|
|
266
|
+
dirs.push(getSkillsDir(resolve4(join3(paths.projectPath, "..", ".."))));
|
|
267
|
+
}
|
|
268
|
+
return dirs;
|
|
269
|
+
}
|
|
270
|
+
async function skillListCommand() {
|
|
271
|
+
const cwd = process.cwd();
|
|
272
|
+
const skillDirs = collectAllSkillDirs(cwd);
|
|
273
|
+
if (skillDirs.length === 0) {
|
|
274
|
+
ui.warn("No Cockpit configuration found.");
|
|
275
|
+
ui.info("Run 'cockpit init' to initialize a workspace.");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const registry = new SkillRegistry2();
|
|
279
|
+
const errors = registry.loadFromDirs(skillDirs);
|
|
280
|
+
for (const { file, error } of errors) {
|
|
281
|
+
ui.warn(`Skipped ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
282
|
+
}
|
|
283
|
+
const skills = registry.list();
|
|
284
|
+
if (skills.length === 0) {
|
|
285
|
+
ui.info("No skills found.");
|
|
286
|
+
ui.dim("Use 'cockpit skill create <name>' to scaffold a new skill.");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
ui.heading(`Skills (${skills.length})`);
|
|
290
|
+
for (const skill of skills) {
|
|
291
|
+
const triggers = skill.trigger.length > 0 ? chalk3.dim(` [${skill.trigger.join(", ")}]`) : "";
|
|
292
|
+
console.log(` ${chalk3.cyan(skill.name)}@${chalk3.dim(skill.version)}${triggers}`);
|
|
293
|
+
if (skill.description) {
|
|
294
|
+
console.log(` ${chalk3.dim(skill.description)}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
ui.blank();
|
|
298
|
+
}
|
|
299
|
+
async function skillCreateCommand(name) {
|
|
300
|
+
const cwd = process.cwd();
|
|
301
|
+
const cockpitRoot = findCockpitRoot(cwd);
|
|
302
|
+
if (!cockpitRoot) {
|
|
303
|
+
ui.error("No Cockpit configuration found.");
|
|
304
|
+
ui.info("Run 'cockpit init' to initialize a workspace.");
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
const skillsDir = getSkillsDir(cockpitRoot);
|
|
308
|
+
mkdirSync2(skillsDir, { recursive: true });
|
|
309
|
+
const fileName = `${name}.yaml`;
|
|
310
|
+
const filePath = join3(skillsDir, fileName);
|
|
311
|
+
if (existsSync2(filePath)) {
|
|
312
|
+
ui.warn(`Skill '${name}' already exists at ${filePath}`);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const content = defaultSkillTemplate(name);
|
|
316
|
+
writeFileSync2(filePath, content, "utf-8");
|
|
317
|
+
ui.success(`Created skill: ${filePath}`);
|
|
318
|
+
ui.dim(`Edit the file, then run 'cockpit apply' to apply it to your AI tools.`);
|
|
319
|
+
}
|
|
320
|
+
async function skillAddCommand(source) {
|
|
321
|
+
const cwd = process.cwd();
|
|
322
|
+
const cockpitRoot = findCockpitRoot(cwd);
|
|
323
|
+
if (!cockpitRoot) {
|
|
324
|
+
ui.error("No Cockpit configuration found.");
|
|
325
|
+
ui.info("Run 'cockpit init' to initialize a workspace.");
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
const sourcePath = resolve4(source);
|
|
329
|
+
if (!existsSync2(sourcePath)) {
|
|
330
|
+
ui.error(`File not found: ${sourcePath}`);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
const ext = extname(sourcePath);
|
|
334
|
+
if (ext !== ".yaml" && ext !== ".yml") {
|
|
335
|
+
ui.error("Only .yaml or .yml skill files are supported.");
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
const skillsDir = getSkillsDir(cockpitRoot);
|
|
339
|
+
mkdirSync2(skillsDir, { recursive: true });
|
|
340
|
+
const destPath = join3(skillsDir, basename(sourcePath));
|
|
341
|
+
cpSync(sourcePath, destPath);
|
|
342
|
+
ui.success(`Added skill from ${sourcePath}`);
|
|
343
|
+
ui.dim(`Run 'cockpit skill list' to see all skills.`);
|
|
344
|
+
}
|
|
345
|
+
async function skillRemoveCommand(name) {
|
|
346
|
+
const cwd = process.cwd();
|
|
347
|
+
const cockpitRoot = findCockpitRoot(cwd);
|
|
348
|
+
if (!cockpitRoot) {
|
|
349
|
+
ui.error("No Cockpit configuration found.");
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
const skillsDir = getSkillsDir(cockpitRoot);
|
|
353
|
+
const candidates = [`${name}.yaml`, `${name}.yml`];
|
|
354
|
+
let removed = false;
|
|
355
|
+
for (const fileName of candidates) {
|
|
356
|
+
const filePath = join3(skillsDir, fileName);
|
|
357
|
+
if (existsSync2(filePath)) {
|
|
358
|
+
rmSync(filePath);
|
|
359
|
+
ui.success(`Removed skill '${name}'`);
|
|
360
|
+
removed = true;
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (!removed) {
|
|
365
|
+
ui.error(`Skill '${name}' not found in ${skillsDir}`);
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/commands/profile.ts
|
|
371
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync } from "fs";
|
|
372
|
+
import { resolve as resolve5 } from "path";
|
|
373
|
+
import { createInterface as createInterface2 } from "readline";
|
|
374
|
+
import { execSync } from "child_process";
|
|
375
|
+
import { stringify as yamlStringify } from "yaml";
|
|
376
|
+
import {
|
|
377
|
+
ProfileConfigSchema,
|
|
378
|
+
WorkspaceConfigSchema,
|
|
379
|
+
getProfilePath,
|
|
380
|
+
getProfileDir,
|
|
381
|
+
tryLoadConfig,
|
|
382
|
+
findConfigPaths as findConfigPaths4,
|
|
383
|
+
resolveConfig as resolveConfig3
|
|
384
|
+
} from "@cockpit-ai/core";
|
|
385
|
+
async function prompt2(question, defaultValue) {
|
|
386
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
387
|
+
const display = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
|
|
388
|
+
return new Promise((resolve9) => {
|
|
389
|
+
rl.question(display, (answer) => {
|
|
390
|
+
rl.close();
|
|
391
|
+
resolve9(answer.trim() || defaultValue || "");
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
function profileTemplate(opts) {
|
|
396
|
+
return `cockpit: "1.0"
|
|
397
|
+
|
|
398
|
+
profile:
|
|
399
|
+
name: ${opts.name}
|
|
400
|
+
sync:
|
|
401
|
+
remote: ""
|
|
402
|
+
auto_sync: false
|
|
403
|
+
|
|
404
|
+
preferences:
|
|
405
|
+
language: ${opts.language}
|
|
406
|
+
default_model: ${opts.defaultModel}
|
|
407
|
+
default_adapter: ${opts.defaultAdapter}
|
|
408
|
+
|
|
409
|
+
context:
|
|
410
|
+
global: []
|
|
411
|
+
`;
|
|
412
|
+
}
|
|
413
|
+
function runGit(args, cwd) {
|
|
414
|
+
return execSync(`git ${args}`, { cwd, stdio: "pipe" }).toString().trim();
|
|
415
|
+
}
|
|
416
|
+
function isGitRepo(dir) {
|
|
417
|
+
try {
|
|
418
|
+
runGit("rev-parse --is-inside-work-tree", dir);
|
|
419
|
+
return true;
|
|
420
|
+
} catch {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function hasCommits(dir) {
|
|
425
|
+
try {
|
|
426
|
+
runGit("rev-parse HEAD", dir);
|
|
427
|
+
return true;
|
|
428
|
+
} catch {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function hasRemote(dir) {
|
|
433
|
+
try {
|
|
434
|
+
const remotes = runGit("remote", dir);
|
|
435
|
+
return remotes.trim().length > 0;
|
|
436
|
+
} catch {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
async function profileShowCommand() {
|
|
441
|
+
const profilePath = getProfilePath();
|
|
442
|
+
const profileDir = getProfileDir();
|
|
443
|
+
ui.heading("Cockpit Profile");
|
|
444
|
+
printKeyValue("Profile dir", profileDir);
|
|
445
|
+
printKeyValue("Profile file", profilePath);
|
|
446
|
+
ui.blank();
|
|
447
|
+
if (!existsSync3(profilePath)) {
|
|
448
|
+
ui.warn("No profile found.");
|
|
449
|
+
ui.info("Run 'cockpit profile create' to set up your profile.");
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const profile = tryLoadConfig(profilePath, ProfileConfigSchema);
|
|
453
|
+
if (!profile) {
|
|
454
|
+
ui.error(`Failed to load profile from ${profilePath}`);
|
|
455
|
+
ui.info("The file may be corrupted. Run 'cockpit profile create' to recreate it.");
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (profile.profile) {
|
|
459
|
+
printKeyValue("Name", profile.profile.name);
|
|
460
|
+
if (profile.profile.sync) {
|
|
461
|
+
const sync = profile.profile.sync;
|
|
462
|
+
printKeyValue("Sync remote", sync.remote ?? "(none)");
|
|
463
|
+
printKeyValue("Auto sync", sync.auto_sync ? "yes" : "no");
|
|
464
|
+
}
|
|
465
|
+
ui.blank();
|
|
466
|
+
}
|
|
467
|
+
if (profile.preferences) {
|
|
468
|
+
ui.dim("Preferences");
|
|
469
|
+
printKeyValue("Language", profile.preferences.language);
|
|
470
|
+
printKeyValue("Default model", profile.preferences.default_model);
|
|
471
|
+
printKeyValue("Default adapter", profile.preferences.default_adapter);
|
|
472
|
+
ui.blank();
|
|
473
|
+
}
|
|
474
|
+
if (profile.context?.global && profile.context.global.length > 0) {
|
|
475
|
+
ui.dim("Global context rules");
|
|
476
|
+
for (const rule of profile.context.global) {
|
|
477
|
+
console.log(` \u2022 ${rule}`);
|
|
478
|
+
}
|
|
479
|
+
ui.blank();
|
|
480
|
+
}
|
|
481
|
+
const profileDirResolved = resolve5(profileDir);
|
|
482
|
+
if (existsSync3(profileDirResolved)) {
|
|
483
|
+
ui.dim("Sync status");
|
|
484
|
+
if (isGitRepo(profileDirResolved)) {
|
|
485
|
+
printKeyValue("Git repo", "yes");
|
|
486
|
+
try {
|
|
487
|
+
const branch = runGit("rev-parse --abbrev-ref HEAD", profileDirResolved);
|
|
488
|
+
printKeyValue("Branch", branch);
|
|
489
|
+
if (hasRemote(profileDirResolved)) {
|
|
490
|
+
const remote = runGit("remote get-url origin", profileDirResolved);
|
|
491
|
+
printKeyValue("Remote origin", remote);
|
|
492
|
+
} else {
|
|
493
|
+
printKeyValue("Remote origin", "(none)");
|
|
494
|
+
}
|
|
495
|
+
} catch {
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
printKeyValue("Git repo", "no");
|
|
499
|
+
ui.dim(" Run 'cockpit profile sync push' to initialize git sync.");
|
|
500
|
+
}
|
|
501
|
+
ui.blank();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
async function profileCreateCommand() {
|
|
505
|
+
const profilePath = getProfilePath();
|
|
506
|
+
const profileDir = getProfileDir();
|
|
507
|
+
ui.heading("Create Cockpit Profile");
|
|
508
|
+
ui.info(`Profile will be saved to: ${profilePath}`);
|
|
509
|
+
ui.blank();
|
|
510
|
+
if (existsSync3(profilePath)) {
|
|
511
|
+
const existing = tryLoadConfig(profilePath, ProfileConfigSchema);
|
|
512
|
+
const existingName = existing?.profile?.name;
|
|
513
|
+
ui.warn(`A profile already exists${existingName ? ` for '${existingName}'` : ""}.`);
|
|
514
|
+
const overwrite = await prompt2("Overwrite? [y/N]", "N");
|
|
515
|
+
if (overwrite.toLowerCase() !== "y") {
|
|
516
|
+
ui.info("Aborted. Existing profile unchanged.");
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
ui.blank();
|
|
520
|
+
}
|
|
521
|
+
const name = await prompt2("Your name", process.env["USER"] ?? "");
|
|
522
|
+
const language = await prompt2("Preferred language code (e.g. en, ko, ja)", "en");
|
|
523
|
+
const defaultModel = await prompt2("Default AI model", "claude-sonnet-4-6");
|
|
524
|
+
const defaultAdapter = await prompt2(
|
|
525
|
+
"Default adapter (claude-code, cursor, copilot, opencode)",
|
|
526
|
+
"claude-code"
|
|
527
|
+
);
|
|
528
|
+
const validAdapters = ["claude-code", "cursor", "copilot", "opencode"];
|
|
529
|
+
if (!validAdapters.includes(defaultAdapter)) {
|
|
530
|
+
ui.error(`Invalid adapter '${defaultAdapter}'. Must be one of: ${validAdapters.join(", ")}`);
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
mkdirSync3(profileDir, { recursive: true });
|
|
534
|
+
const content = profileTemplate({ name, language, defaultModel, defaultAdapter });
|
|
535
|
+
writeFileSync3(profilePath, content, "utf-8");
|
|
536
|
+
ui.blank();
|
|
537
|
+
ui.success(`Profile created at ${profilePath}`);
|
|
538
|
+
ui.blank();
|
|
539
|
+
ui.dim("Next steps:");
|
|
540
|
+
ui.dim(" cockpit profile show \u2014 view your profile");
|
|
541
|
+
ui.dim(" cockpit profile sync push \u2014 sync profile to a remote git repo");
|
|
542
|
+
ui.dim(" cockpit status \u2014 view merged environment config");
|
|
543
|
+
}
|
|
544
|
+
async function profileSyncPushCommand() {
|
|
545
|
+
const profilePath = getProfilePath();
|
|
546
|
+
const profileDir = resolve5(getProfileDir());
|
|
547
|
+
if (!existsSync3(profilePath)) {
|
|
548
|
+
ui.error("No profile found.");
|
|
549
|
+
ui.info("Run 'cockpit profile create' first.");
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
const profile = tryLoadConfig(profilePath, ProfileConfigSchema);
|
|
553
|
+
const remote = profile?.profile?.sync?.remote;
|
|
554
|
+
ui.heading("Profile Sync \u2014 Push");
|
|
555
|
+
mkdirSync3(profileDir, { recursive: true });
|
|
556
|
+
if (!isGitRepo(profileDir)) {
|
|
557
|
+
ui.info("Initializing git repository in profile directory...");
|
|
558
|
+
try {
|
|
559
|
+
runGit("init", profileDir);
|
|
560
|
+
ui.success("Git repository initialized.");
|
|
561
|
+
} catch (err) {
|
|
562
|
+
ui.error(`Failed to initialize git repo: ${err instanceof Error ? err.message : String(err)}`);
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (remote && remote.trim() !== "") {
|
|
567
|
+
try {
|
|
568
|
+
const existingRemotes = runGit("remote", profileDir);
|
|
569
|
+
if (!existingRemotes.split("\n").includes("origin")) {
|
|
570
|
+
runGit(`remote add origin ${remote}`, profileDir);
|
|
571
|
+
ui.success(`Remote 'origin' set to: ${remote}`);
|
|
572
|
+
}
|
|
573
|
+
} catch (err) {
|
|
574
|
+
ui.warn(`Could not configure remote: ${err instanceof Error ? err.message : String(err)}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
try {
|
|
578
|
+
runGit("add .", profileDir);
|
|
579
|
+
} catch (err) {
|
|
580
|
+
ui.error(`Failed to stage files: ${err instanceof Error ? err.message : String(err)}`);
|
|
581
|
+
process.exit(1);
|
|
582
|
+
}
|
|
583
|
+
let hasChanges = false;
|
|
584
|
+
try {
|
|
585
|
+
const status = runGit("status --porcelain", profileDir);
|
|
586
|
+
hasChanges = status.trim().length > 0;
|
|
587
|
+
} catch {
|
|
588
|
+
hasChanges = true;
|
|
589
|
+
}
|
|
590
|
+
if (hasChanges || !hasCommits(profileDir)) {
|
|
591
|
+
try {
|
|
592
|
+
runGit(`commit -m "sync"`, profileDir);
|
|
593
|
+
ui.success("Committed profile changes.");
|
|
594
|
+
} catch (err) {
|
|
595
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
596
|
+
if (!msg.includes("nothing to commit")) {
|
|
597
|
+
ui.error(`Failed to commit: ${msg}`);
|
|
598
|
+
process.exit(1);
|
|
599
|
+
} else {
|
|
600
|
+
ui.info("Nothing new to commit.");
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
} else {
|
|
604
|
+
ui.info("Nothing new to commit.");
|
|
605
|
+
}
|
|
606
|
+
if (remote && remote.trim() !== "") {
|
|
607
|
+
ui.info(`Pushing to remote: ${remote}`);
|
|
608
|
+
try {
|
|
609
|
+
let branch = "main";
|
|
610
|
+
try {
|
|
611
|
+
branch = runGit("rev-parse --abbrev-ref HEAD", profileDir);
|
|
612
|
+
} catch {
|
|
613
|
+
}
|
|
614
|
+
runGit(`push -u origin ${branch}`, profileDir);
|
|
615
|
+
ui.success("Profile pushed to remote.");
|
|
616
|
+
} catch (err) {
|
|
617
|
+
ui.error(`Push failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
618
|
+
ui.dim("Ensure the remote is accessible and you have push permissions.");
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
} else {
|
|
622
|
+
ui.warn("No remote configured in profile.sync.remote \u2014 skipping push.");
|
|
623
|
+
ui.dim("Edit your profile and add a 'sync.remote' URL, then run this command again.");
|
|
624
|
+
}
|
|
625
|
+
ui.blank();
|
|
626
|
+
}
|
|
627
|
+
async function profileSyncPullCommand() {
|
|
628
|
+
const profilePath = getProfilePath();
|
|
629
|
+
const profileDir = resolve5(getProfileDir());
|
|
630
|
+
ui.heading("Profile Sync \u2014 Pull");
|
|
631
|
+
const profile = tryLoadConfig(profilePath, ProfileConfigSchema);
|
|
632
|
+
const remote = profile?.profile?.sync?.remote;
|
|
633
|
+
if (!existsSync3(profileDir) || !isGitRepo(profileDir)) {
|
|
634
|
+
if (remote && remote.trim() !== "") {
|
|
635
|
+
ui.info(`Cloning profile from remote: ${remote}`);
|
|
636
|
+
try {
|
|
637
|
+
const parentDir = resolve5(profileDir, "..");
|
|
638
|
+
const dirName = profileDir.split("/").at(-1) ?? ".cockpit";
|
|
639
|
+
mkdirSync3(parentDir, { recursive: true });
|
|
640
|
+
execSync(`git clone ${remote} ${dirName}`, { cwd: parentDir, stdio: "pipe" });
|
|
641
|
+
ui.success("Profile cloned from remote.");
|
|
642
|
+
} catch (err) {
|
|
643
|
+
ui.error(`Clone failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
644
|
+
ui.dim(`Remote: ${remote}`);
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
ui.error("Profile directory is not a git repository and no remote is configured.");
|
|
649
|
+
ui.info("Run 'cockpit profile create' and configure 'sync.remote' first.");
|
|
650
|
+
process.exit(1);
|
|
651
|
+
}
|
|
652
|
+
} else {
|
|
653
|
+
if (!hasRemote(profileDir)) {
|
|
654
|
+
ui.error("No git remote configured in the profile directory.");
|
|
655
|
+
if (remote && remote.trim() !== "") {
|
|
656
|
+
ui.info("Adding remote from profile config...");
|
|
657
|
+
try {
|
|
658
|
+
runGit(`remote add origin ${remote}`, profileDir);
|
|
659
|
+
ui.success(`Remote 'origin' set to: ${remote}`);
|
|
660
|
+
} catch (err) {
|
|
661
|
+
ui.error(`Failed to add remote: ${err instanceof Error ? err.message : String(err)}`);
|
|
662
|
+
process.exit(1);
|
|
663
|
+
}
|
|
664
|
+
} else {
|
|
665
|
+
ui.dim("Set 'sync.remote' in your profile.yaml and try again.");
|
|
666
|
+
process.exit(1);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
ui.info("Pulling latest changes from remote...");
|
|
670
|
+
try {
|
|
671
|
+
runGit("pull", profileDir);
|
|
672
|
+
ui.success("Profile updated from remote.");
|
|
673
|
+
} catch (err) {
|
|
674
|
+
ui.error(`Pull failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
675
|
+
ui.dim("Check your network connection and remote access permissions.");
|
|
676
|
+
process.exit(1);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
ui.blank();
|
|
680
|
+
ui.dim("Run 'cockpit profile show' to view the updated profile.");
|
|
681
|
+
ui.blank();
|
|
682
|
+
}
|
|
683
|
+
async function profileExportCommand(outputFile) {
|
|
684
|
+
const profilePath = getProfilePath();
|
|
685
|
+
const cwd = process.cwd();
|
|
686
|
+
const outPath = resolve5(outputFile ?? "cockpit-profile-export.yaml");
|
|
687
|
+
ui.heading("Profile Export");
|
|
688
|
+
if (!existsSync3(profilePath)) {
|
|
689
|
+
ui.error("No profile found.");
|
|
690
|
+
ui.info("Run 'cockpit profile create' first.");
|
|
691
|
+
process.exit(1);
|
|
692
|
+
}
|
|
693
|
+
const profile = tryLoadConfig(profilePath, ProfileConfigSchema);
|
|
694
|
+
if (!profile) {
|
|
695
|
+
ui.error(`Failed to load profile from ${profilePath}`);
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
const paths = findConfigPaths4(cwd);
|
|
699
|
+
paths.profilePath = profilePath;
|
|
700
|
+
let workspaceConfig = null;
|
|
701
|
+
if (paths.workspacePath) {
|
|
702
|
+
workspaceConfig = tryLoadConfig(paths.workspacePath, WorkspaceConfigSchema);
|
|
703
|
+
}
|
|
704
|
+
const resolved = resolveConfig3(paths);
|
|
705
|
+
const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
706
|
+
const exportDoc = {
|
|
707
|
+
cockpit: "1.0",
|
|
708
|
+
exported_at: exportedAt
|
|
709
|
+
};
|
|
710
|
+
if (profile.profile) {
|
|
711
|
+
exportDoc["profile"] = profile.profile;
|
|
712
|
+
}
|
|
713
|
+
exportDoc["preferences"] = {
|
|
714
|
+
language: resolved.preferences.language,
|
|
715
|
+
default_model: resolved.preferences.defaultModel,
|
|
716
|
+
default_adapter: resolved.defaultAdapter
|
|
717
|
+
};
|
|
718
|
+
if (resolved.context.global.length > 0 || resolved.context.project.length > 0) {
|
|
719
|
+
const contextExport = {};
|
|
720
|
+
if (resolved.context.global.length > 0) {
|
|
721
|
+
contextExport["global"] = resolved.context.global;
|
|
722
|
+
}
|
|
723
|
+
if (resolved.context.project.length > 0) {
|
|
724
|
+
contextExport["project"] = resolved.context.project;
|
|
725
|
+
}
|
|
726
|
+
exportDoc["context"] = contextExport;
|
|
727
|
+
}
|
|
728
|
+
if (workspaceConfig?.workspace) {
|
|
729
|
+
exportDoc["workspace"] = workspaceConfig.workspace;
|
|
730
|
+
}
|
|
731
|
+
if (resolved.adapters.length > 0) {
|
|
732
|
+
exportDoc["adapters"] = resolved.adapters;
|
|
733
|
+
}
|
|
734
|
+
const header = `# Cockpit Profile Export \u2014 generated by cockpit profile export
|
|
735
|
+
`;
|
|
736
|
+
const body = yamlStringify(exportDoc, { lineWidth: 0 });
|
|
737
|
+
writeFileSync3(outPath, header + body, "utf-8");
|
|
738
|
+
ui.blank();
|
|
739
|
+
ui.success(`Exported to: ${outPath}`);
|
|
740
|
+
ui.blank();
|
|
741
|
+
ui.dim(`Profile: ${profilePath}`);
|
|
742
|
+
if (paths.workspacePath) {
|
|
743
|
+
ui.dim(`Workspace: ${paths.workspacePath}`);
|
|
744
|
+
}
|
|
745
|
+
ui.dim(`Exported at: ${exportedAt}`);
|
|
746
|
+
ui.blank();
|
|
747
|
+
}
|
|
748
|
+
async function profileImportCommand(inputFile) {
|
|
749
|
+
const inputPath = resolve5(inputFile);
|
|
750
|
+
const profilePath = getProfilePath();
|
|
751
|
+
const profileDir = getProfileDir();
|
|
752
|
+
ui.heading("Profile Import");
|
|
753
|
+
ui.info(`Importing from: ${inputPath}`);
|
|
754
|
+
ui.blank();
|
|
755
|
+
if (!existsSync3(inputPath)) {
|
|
756
|
+
ui.error(`Import file not found: ${inputPath}`);
|
|
757
|
+
process.exit(1);
|
|
758
|
+
}
|
|
759
|
+
let rawContent = "";
|
|
760
|
+
try {
|
|
761
|
+
rawContent = readFileSync(inputPath, "utf-8");
|
|
762
|
+
} catch (err) {
|
|
763
|
+
ui.error(`Failed to read file: ${err instanceof Error ? err.message : String(err)}`);
|
|
764
|
+
process.exit(1);
|
|
765
|
+
}
|
|
766
|
+
let parsed = null;
|
|
767
|
+
try {
|
|
768
|
+
const { parse: parseYaml } = await import("yaml");
|
|
769
|
+
parsed = parseYaml(rawContent);
|
|
770
|
+
} catch (err) {
|
|
771
|
+
ui.error(`Invalid YAML in import file: ${err instanceof Error ? err.message : String(err)}`);
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
775
|
+
ui.error("Import file does not contain a valid Cockpit export document.");
|
|
776
|
+
process.exit(1);
|
|
777
|
+
}
|
|
778
|
+
const doc = parsed;
|
|
779
|
+
if (doc["cockpit"] === void 0) {
|
|
780
|
+
ui.warn("Import file does not have a 'cockpit' version field. Proceeding with caution.");
|
|
781
|
+
}
|
|
782
|
+
if (existsSync3(profilePath)) {
|
|
783
|
+
const existing = tryLoadConfig(profilePath, ProfileConfigSchema);
|
|
784
|
+
const existingName = existing?.profile?.name;
|
|
785
|
+
ui.warn(`An existing profile${existingName ? ` for '${existingName}'` : ""} will be overwritten.`);
|
|
786
|
+
const overwrite = await prompt2("Continue? [y/N]", "N");
|
|
787
|
+
if (overwrite.toLowerCase() !== "y") {
|
|
788
|
+
ui.info("Aborted. Existing profile unchanged.");
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
ui.blank();
|
|
792
|
+
}
|
|
793
|
+
const profileDoc = {
|
|
794
|
+
cockpit: doc["cockpit"] ?? "1.0"
|
|
795
|
+
};
|
|
796
|
+
if (doc["profile"] !== void 0) {
|
|
797
|
+
profileDoc["profile"] = doc["profile"];
|
|
798
|
+
}
|
|
799
|
+
if (doc["preferences"] !== void 0) {
|
|
800
|
+
profileDoc["preferences"] = doc["preferences"];
|
|
801
|
+
}
|
|
802
|
+
if (doc["context"] !== void 0) {
|
|
803
|
+
profileDoc["context"] = doc["context"];
|
|
804
|
+
}
|
|
805
|
+
const profileResult = ProfileConfigSchema.safeParse(profileDoc);
|
|
806
|
+
if (!profileResult.success) {
|
|
807
|
+
ui.error("Import file does not contain a valid profile configuration:");
|
|
808
|
+
for (const issue of profileResult.error.issues) {
|
|
809
|
+
ui.dim(` ${issue.path.join(".")}: ${issue.message}`);
|
|
810
|
+
}
|
|
811
|
+
process.exit(1);
|
|
812
|
+
}
|
|
813
|
+
mkdirSync3(profileDir, { recursive: true });
|
|
814
|
+
const profileYaml = yamlStringify(profileDoc, { lineWidth: 0 });
|
|
815
|
+
writeFileSync3(profilePath, profileYaml, "utf-8");
|
|
816
|
+
ui.success(`Profile imported to: ${profilePath}`);
|
|
817
|
+
if (doc["workspace"] !== void 0 && doc["adapters"] !== void 0) {
|
|
818
|
+
ui.blank();
|
|
819
|
+
ui.info("The export file also contains workspace configuration.");
|
|
820
|
+
ui.dim("Workspace settings were NOT imported (they belong in .cockpit/config.yaml).");
|
|
821
|
+
ui.dim("Run 'cockpit init' in your workspace to create a workspace config.");
|
|
822
|
+
}
|
|
823
|
+
ui.blank();
|
|
824
|
+
ui.dim("Run 'cockpit profile show' to verify the imported profile.");
|
|
825
|
+
ui.blank();
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// src/commands/agent.ts
|
|
829
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
830
|
+
import { join as join4, resolve as resolve6 } from "path";
|
|
831
|
+
import { AgentRegistry, setAgentStatus, getAgentStatus, readAgentState } from "@cockpit-ai/agents";
|
|
832
|
+
import { findConfigPaths as findConfigPaths5, COCKPIT_DIR as COCKPIT_DIR4 } from "@cockpit-ai/core";
|
|
833
|
+
import chalk4 from "chalk";
|
|
834
|
+
function collectAgentDirs(cwd) {
|
|
835
|
+
const paths = findConfigPaths5(cwd);
|
|
836
|
+
const dirs = [];
|
|
837
|
+
if (paths.workspacePath) {
|
|
838
|
+
dirs.push(join4(resolve6(join4(paths.workspacePath, "..", "..")), COCKPIT_DIR4, "agents"));
|
|
839
|
+
}
|
|
840
|
+
if (paths.projectPath) {
|
|
841
|
+
dirs.push(join4(resolve6(join4(paths.projectPath, "..", "..")), COCKPIT_DIR4, "agents"));
|
|
842
|
+
}
|
|
843
|
+
return dirs;
|
|
844
|
+
}
|
|
845
|
+
function statusColor(status) {
|
|
846
|
+
switch (status) {
|
|
847
|
+
case "running":
|
|
848
|
+
return chalk4.green(status);
|
|
849
|
+
case "stopped":
|
|
850
|
+
return chalk4.dim(status);
|
|
851
|
+
case "error":
|
|
852
|
+
return chalk4.red(status);
|
|
853
|
+
default:
|
|
854
|
+
return chalk4.cyan(status);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
async function agentListCommand() {
|
|
858
|
+
const cwd = process.cwd();
|
|
859
|
+
const agentDirs = collectAgentDirs(cwd);
|
|
860
|
+
if (agentDirs.length === 0) {
|
|
861
|
+
ui.warn("No Cockpit configuration found.");
|
|
862
|
+
ui.info("Run 'cockpit init' to initialize a workspace.");
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const registry = new AgentRegistry();
|
|
866
|
+
const errors = registry.loadFromDirs(agentDirs);
|
|
867
|
+
for (const { file, error } of errors) {
|
|
868
|
+
ui.warn(`Skipped ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
869
|
+
}
|
|
870
|
+
const agents = registry.list();
|
|
871
|
+
if (agents.length === 0) {
|
|
872
|
+
ui.info("No agents found.");
|
|
873
|
+
ui.dim("Add agent YAML files to a .cockpit/agents/ directory.");
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
ui.heading(`Agents (${agents.length})`);
|
|
877
|
+
for (const agent of agents) {
|
|
878
|
+
const currentStatus = getAgentStatus(agent.name);
|
|
879
|
+
console.log(
|
|
880
|
+
` ${chalk4.cyan(agent.name)} ${chalk4.dim("\xB7")} ${statusColor(currentStatus)}`
|
|
881
|
+
);
|
|
882
|
+
console.log(` ${chalk4.dim("role:")} ${agent.role}`);
|
|
883
|
+
console.log(` ${chalk4.dim("model:")} ${agent.model}`);
|
|
884
|
+
if (agent.skills.length > 0) {
|
|
885
|
+
console.log(` ${chalk4.dim("skills:")} ${agent.skills.join(", ")}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
ui.blank();
|
|
889
|
+
}
|
|
890
|
+
async function agentSpawnCommand(name) {
|
|
891
|
+
const cwd = process.cwd();
|
|
892
|
+
const agentDirs = collectAgentDirs(cwd);
|
|
893
|
+
if (agentDirs.length === 0) {
|
|
894
|
+
ui.error("No Cockpit configuration found.");
|
|
895
|
+
ui.info("Run 'cockpit init' to initialize a workspace.");
|
|
896
|
+
process.exit(1);
|
|
897
|
+
}
|
|
898
|
+
const registry = new AgentRegistry();
|
|
899
|
+
registry.loadFromDirs(agentDirs);
|
|
900
|
+
const agent = registry.get(name);
|
|
901
|
+
if (!agent) {
|
|
902
|
+
ui.error(`Agent '${name}' not found.`);
|
|
903
|
+
ui.dim(`Run 'cockpit agent list' to see available agents.`);
|
|
904
|
+
process.exit(1);
|
|
905
|
+
}
|
|
906
|
+
setAgentStatus(name, "running");
|
|
907
|
+
const claudeDir = join4(cwd, ".claude");
|
|
908
|
+
if (existsSync4(claudeDir)) {
|
|
909
|
+
const agentsDir = join4(claudeDir, "agents");
|
|
910
|
+
mkdirSync4(agentsDir, { recursive: true });
|
|
911
|
+
const contextLines = [
|
|
912
|
+
`# Agent: ${agent.name}`,
|
|
913
|
+
``,
|
|
914
|
+
`**Role:** ${agent.role}`,
|
|
915
|
+
`**Model:** ${agent.model}`
|
|
916
|
+
];
|
|
917
|
+
if (agent.skills.length > 0) {
|
|
918
|
+
contextLines.push(``, `## Skills`, ``);
|
|
919
|
+
for (const skill of agent.skills) {
|
|
920
|
+
contextLines.push(`- ${skill}`);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (agent.contextRules.length > 0) {
|
|
924
|
+
contextLines.push(``, `## Context Rules`, ``);
|
|
925
|
+
for (const rule of agent.contextRules) {
|
|
926
|
+
contextLines.push(`- ${rule}`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
if (agent.contextIncludes.length > 0) {
|
|
930
|
+
contextLines.push(``, `## Context Includes`, ``);
|
|
931
|
+
for (const include of agent.contextIncludes) {
|
|
932
|
+
contextLines.push(`- ${include}`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
const contextPath = join4(agentsDir, `${name}.md`);
|
|
936
|
+
writeFileSync4(contextPath, contextLines.join("\n") + "\n", "utf-8");
|
|
937
|
+
ui.dim(`Wrote context to ${contextPath}`);
|
|
938
|
+
}
|
|
939
|
+
ui.success(`Agent '${name}' spawned.`);
|
|
940
|
+
ui.dim(`Run 'cockpit agent stop ${name}' to mark it as stopped.`);
|
|
941
|
+
}
|
|
942
|
+
async function agentStopCommand(name) {
|
|
943
|
+
setAgentStatus(name, "stopped");
|
|
944
|
+
ui.success(`Agent '${name}' stopped.`);
|
|
945
|
+
}
|
|
946
|
+
async function agentStatusCommand() {
|
|
947
|
+
const state = readAgentState();
|
|
948
|
+
const entries = Object.entries(state.agents);
|
|
949
|
+
if (entries.length === 0) {
|
|
950
|
+
ui.info("No agent state recorded.");
|
|
951
|
+
ui.dim("Spawn an agent with 'cockpit agent spawn <name>'.");
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
ui.heading("Agent Status Dashboard");
|
|
955
|
+
for (const [agentName, entry] of entries) {
|
|
956
|
+
console.log(` ${chalk4.cyan(agentName)}`);
|
|
957
|
+
console.log(` ${chalk4.dim("status:")} ${statusColor(entry.status)}`);
|
|
958
|
+
if (entry.startedAt) {
|
|
959
|
+
console.log(` ${chalk4.dim("started:")} ${entry.startedAt}`);
|
|
960
|
+
}
|
|
961
|
+
if (entry.stoppedAt) {
|
|
962
|
+
console.log(` ${chalk4.dim("stopped:")} ${entry.stoppedAt}`);
|
|
963
|
+
}
|
|
964
|
+
if (entry.pid != null) {
|
|
965
|
+
console.log(` ${chalk4.dim("pid:")} ${entry.pid}`);
|
|
966
|
+
}
|
|
967
|
+
ui.blank();
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// src/commands/worktree.ts
|
|
972
|
+
import {
|
|
973
|
+
WorktreeManager,
|
|
974
|
+
registerWorktree,
|
|
975
|
+
unregisterWorktree,
|
|
976
|
+
assignAgent,
|
|
977
|
+
readWorktreeState,
|
|
978
|
+
getWorktreeState
|
|
979
|
+
} from "@cockpit-ai/worktree";
|
|
980
|
+
import chalk5 from "chalk";
|
|
981
|
+
import { resolve as resolve7 } from "path";
|
|
982
|
+
import { existsSync as existsSync5 } from "fs";
|
|
983
|
+
async function worktreeCreateCommand(branch, options) {
|
|
984
|
+
const repoPath = resolve7(options.repo ?? process.cwd());
|
|
985
|
+
const manager = new WorktreeManager(repoPath);
|
|
986
|
+
try {
|
|
987
|
+
const info = manager.create({
|
|
988
|
+
branch,
|
|
989
|
+
path: options.path ? resolve7(options.path) : void 0
|
|
990
|
+
});
|
|
991
|
+
registerWorktree(info.path, info.branch);
|
|
992
|
+
ui.success(`Created worktree for branch ${chalk5.cyan(info.branch)}`);
|
|
993
|
+
ui.info(`Path: ${info.path}`);
|
|
994
|
+
} catch (err) {
|
|
995
|
+
ui.error(`Failed to create worktree: ${err instanceof Error ? err.message : String(err)}`);
|
|
996
|
+
process.exit(1);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
async function worktreeListCommand() {
|
|
1000
|
+
const repoPath = resolve7(process.cwd());
|
|
1001
|
+
const manager = new WorktreeManager(repoPath);
|
|
1002
|
+
let gitWorktrees = [];
|
|
1003
|
+
try {
|
|
1004
|
+
gitWorktrees = manager.list();
|
|
1005
|
+
} catch (err) {
|
|
1006
|
+
ui.warn(`Could not list git worktrees: ${err instanceof Error ? err.message : String(err)}`);
|
|
1007
|
+
}
|
|
1008
|
+
const state = readWorktreeState();
|
|
1009
|
+
if (gitWorktrees.length === 0 && Object.keys(state.worktrees).length === 0) {
|
|
1010
|
+
ui.info("No worktrees found.");
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
ui.heading("Worktrees");
|
|
1014
|
+
const allPaths = /* @__PURE__ */ new Set([
|
|
1015
|
+
...gitWorktrees.map((wt) => wt.path),
|
|
1016
|
+
...Object.keys(state.worktrees)
|
|
1017
|
+
]);
|
|
1018
|
+
console.log(
|
|
1019
|
+
` ${chalk5.bold("Path".padEnd(40))} ${chalk5.bold("Branch".padEnd(25))} ${chalk5.bold("Agent")}`
|
|
1020
|
+
);
|
|
1021
|
+
console.log(` ${chalk5.dim("\u2500".repeat(80))}`);
|
|
1022
|
+
for (const path of allPaths) {
|
|
1023
|
+
const gitEntry = gitWorktrees.find((wt) => wt.path === path);
|
|
1024
|
+
const stateEntry = state.worktrees[path];
|
|
1025
|
+
const branch = gitEntry?.branch ?? stateEntry?.branch ?? chalk5.dim("(unknown)");
|
|
1026
|
+
const agent = stateEntry?.assignedAgent ?? chalk5.dim("(none)");
|
|
1027
|
+
const mainTag = gitEntry?.isMain ? chalk5.dim(" [main]") : "";
|
|
1028
|
+
const truncatedPath = path.length > 39 ? "..." + path.slice(path.length - 36) : path;
|
|
1029
|
+
console.log(
|
|
1030
|
+
` ${chalk5.cyan(truncatedPath.padEnd(40))} ${chalk5.white(branch.padEnd(25))}${mainTag} ${chalk5.yellow(agent)}`
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
ui.blank();
|
|
1034
|
+
}
|
|
1035
|
+
async function worktreeStatusCommand() {
|
|
1036
|
+
const state = readWorktreeState();
|
|
1037
|
+
const entries = Object.values(state.worktrees);
|
|
1038
|
+
if (entries.length === 0) {
|
|
1039
|
+
ui.info("No tracked worktrees in Cockpit state.");
|
|
1040
|
+
ui.dim("Use 'cockpit worktree create <branch>' to create one.");
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
ui.heading(`Worktree Status (${entries.length})`);
|
|
1044
|
+
for (const entry of entries) {
|
|
1045
|
+
const exists = existsSync5(entry.path);
|
|
1046
|
+
const statusMark = exists ? chalk5.green("active") : chalk5.red("missing");
|
|
1047
|
+
console.log(` ${chalk5.cyan(entry.branch)} ${chalk5.dim("\u2014")} ${statusMark}`);
|
|
1048
|
+
console.log(` ${chalk5.dim("Path:")} ${entry.path}`);
|
|
1049
|
+
console.log(` ${chalk5.dim("Agent:")} ${entry.assignedAgent ?? chalk5.dim("(none)")}`);
|
|
1050
|
+
console.log(` ${chalk5.dim("Created:")} ${entry.createdAt}`);
|
|
1051
|
+
ui.blank();
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
async function worktreeAssignCommand(worktreePath, agentName) {
|
|
1055
|
+
const resolvedPath = resolve7(worktreePath);
|
|
1056
|
+
const existing = getWorktreeState(resolvedPath);
|
|
1057
|
+
if (!existing) {
|
|
1058
|
+
ui.error(`Worktree not found in state: ${resolvedPath}`);
|
|
1059
|
+
ui.dim("Use 'cockpit worktree list' to see tracked worktrees.");
|
|
1060
|
+
process.exit(1);
|
|
1061
|
+
}
|
|
1062
|
+
try {
|
|
1063
|
+
assignAgent(resolvedPath, agentName);
|
|
1064
|
+
ui.success(
|
|
1065
|
+
`Assigned agent ${chalk5.yellow(agentName)} to worktree ${chalk5.cyan(existing.branch)}`
|
|
1066
|
+
);
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
ui.error(`Failed to assign agent: ${err instanceof Error ? err.message : String(err)}`);
|
|
1069
|
+
process.exit(1);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
async function worktreeCleanCommand() {
|
|
1073
|
+
const repoPath = resolve7(process.cwd());
|
|
1074
|
+
const manager = new WorktreeManager(repoPath);
|
|
1075
|
+
try {
|
|
1076
|
+
manager.prune();
|
|
1077
|
+
ui.info("Pruned stale git worktree references.");
|
|
1078
|
+
} catch (err) {
|
|
1079
|
+
ui.warn(
|
|
1080
|
+
`git worktree prune failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
const state = readWorktreeState();
|
|
1084
|
+
const staleEntries = Object.values(state.worktrees).filter(
|
|
1085
|
+
(entry) => !existsSync5(entry.path)
|
|
1086
|
+
);
|
|
1087
|
+
if (staleEntries.length === 0) {
|
|
1088
|
+
ui.info("No stale worktree entries to clean.");
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
for (const entry of staleEntries) {
|
|
1092
|
+
unregisterWorktree(entry.path);
|
|
1093
|
+
ui.success(`Removed stale entry: ${chalk5.cyan(entry.branch)} (${entry.path})`);
|
|
1094
|
+
}
|
|
1095
|
+
ui.blank();
|
|
1096
|
+
ui.dim(`Cleaned ${staleEntries.length} stale entry${staleEntries.length === 1 ? "" : "ies"}.`);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// src/commands/context.ts
|
|
1100
|
+
import { existsSync as existsSync6, readFileSync as readFileSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
1101
|
+
import { join as join5, resolve as resolve8 } from "path";
|
|
1102
|
+
import { ContextManager, contextSummary, buildClaudeMdSection } from "@cockpit-ai/context";
|
|
1103
|
+
import {
|
|
1104
|
+
findConfigPaths as findConfigPaths6,
|
|
1105
|
+
resolveConfig as resolveConfig4,
|
|
1106
|
+
buildResolvedContext as buildResolvedContext2
|
|
1107
|
+
} from "@cockpit-ai/core";
|
|
1108
|
+
import chalk6 from "chalk";
|
|
1109
|
+
var COCKPIT_MARKER = "<!-- cockpit:managed -->";
|
|
1110
|
+
var CLAUDE_MD = "CLAUDE.md";
|
|
1111
|
+
async function contextShowCommand() {
|
|
1112
|
+
const cwd = resolve8(process.cwd());
|
|
1113
|
+
const paths = findConfigPaths6(cwd);
|
|
1114
|
+
if (!paths.workspacePath && !paths.projectPath) {
|
|
1115
|
+
ui.error("No Cockpit configuration found.");
|
|
1116
|
+
ui.info("Run 'cockpit init' to initialize a workspace.");
|
|
1117
|
+
process.exit(1);
|
|
1118
|
+
}
|
|
1119
|
+
const config = resolveConfig4(paths);
|
|
1120
|
+
const manager = new ContextManager(cwd);
|
|
1121
|
+
const context = manager.getResolved();
|
|
1122
|
+
ui.heading("Context");
|
|
1123
|
+
ui.blank();
|
|
1124
|
+
console.log(chalk6.bold("Config Sources"));
|
|
1125
|
+
if (config.profilePath) {
|
|
1126
|
+
console.log(` ${chalk6.cyan("profile".padEnd(12))} ${chalk6.dim(config.profilePath)}`);
|
|
1127
|
+
}
|
|
1128
|
+
if (config.workspacePath) {
|
|
1129
|
+
console.log(` ${chalk6.cyan("workspace".padEnd(12))} ${chalk6.dim(config.workspacePath)}`);
|
|
1130
|
+
}
|
|
1131
|
+
if (config.projectPath) {
|
|
1132
|
+
console.log(` ${chalk6.cyan("project".padEnd(12))} ${chalk6.dim(config.projectPath)}`);
|
|
1133
|
+
}
|
|
1134
|
+
ui.blank();
|
|
1135
|
+
console.log(chalk6.bold("Global Rules"));
|
|
1136
|
+
if (context.global.length === 0) {
|
|
1137
|
+
console.log(` ${chalk6.dim("(none)")}`);
|
|
1138
|
+
} else {
|
|
1139
|
+
for (const rule of context.global) {
|
|
1140
|
+
const source = rule.source ? chalk6.dim(` [${rule.source}]`) : "";
|
|
1141
|
+
console.log(` ${chalk6.white("-")} ${rule.content}${source}`);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
ui.blank();
|
|
1145
|
+
console.log(chalk6.bold("Project Rules"));
|
|
1146
|
+
if (context.project.length === 0) {
|
|
1147
|
+
console.log(` ${chalk6.dim("(none)")}`);
|
|
1148
|
+
} else {
|
|
1149
|
+
for (const rule of context.project) {
|
|
1150
|
+
const source = rule.source ? chalk6.dim(` [${rule.source}]`) : "";
|
|
1151
|
+
console.log(` ${chalk6.white("-")} ${rule.content}${source}`);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
ui.blank();
|
|
1155
|
+
const summary = contextSummary(context);
|
|
1156
|
+
console.log(chalk6.bold("Summary"));
|
|
1157
|
+
console.log(` ${chalk6.cyan("total".padEnd(12))} ${summary.totalRules}`);
|
|
1158
|
+
console.log(` ${chalk6.cyan("global".padEnd(12))} ${summary.globalCount}`);
|
|
1159
|
+
console.log(` ${chalk6.cyan("project".padEnd(12))} ${summary.projectCount}`);
|
|
1160
|
+
ui.blank();
|
|
1161
|
+
}
|
|
1162
|
+
async function contextAddCommand(rule, options) {
|
|
1163
|
+
const cwd = resolve8(process.cwd());
|
|
1164
|
+
const paths = findConfigPaths6(cwd);
|
|
1165
|
+
if (!paths.workspacePath && !paths.projectPath) {
|
|
1166
|
+
ui.error("No Cockpit configuration found.");
|
|
1167
|
+
ui.info("Run 'cockpit init' to initialize a workspace.");
|
|
1168
|
+
process.exit(1);
|
|
1169
|
+
}
|
|
1170
|
+
const scope = options.project || options.scope === "project" ? "project" : "global";
|
|
1171
|
+
const manager = new ContextManager(cwd);
|
|
1172
|
+
try {
|
|
1173
|
+
manager.addRule(rule, scope);
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
ui.error(err instanceof Error ? err.message : String(err));
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
const targetPath = paths.projectPath ?? paths.workspacePath;
|
|
1179
|
+
ui.success(`Added ${scope} rule`);
|
|
1180
|
+
ui.dim(` Rule: ${rule}`);
|
|
1181
|
+
ui.dim(` Scope: ${scope}`);
|
|
1182
|
+
ui.dim(` File: ${targetPath}`);
|
|
1183
|
+
ui.blank();
|
|
1184
|
+
ui.info("Run 'cockpit context generate' to apply the context to your AI tools.");
|
|
1185
|
+
}
|
|
1186
|
+
async function contextGenerateCommand() {
|
|
1187
|
+
const cwd = resolve8(process.cwd());
|
|
1188
|
+
const paths = findConfigPaths6(cwd);
|
|
1189
|
+
if (!paths.workspacePath && !paths.projectPath) {
|
|
1190
|
+
ui.error("No Cockpit configuration found.");
|
|
1191
|
+
ui.info("Run 'cockpit init' to initialize a workspace.");
|
|
1192
|
+
process.exit(1);
|
|
1193
|
+
}
|
|
1194
|
+
const config = resolveConfig4(paths);
|
|
1195
|
+
const context = buildResolvedContext2(
|
|
1196
|
+
config.context.global,
|
|
1197
|
+
config.context.project,
|
|
1198
|
+
"cockpit"
|
|
1199
|
+
);
|
|
1200
|
+
const claudeMdPath = join5(cwd, CLAUDE_MD);
|
|
1201
|
+
const claudeSection = buildClaudeMdSection(context);
|
|
1202
|
+
const existing = existsSync6(claudeMdPath) ? readFileSync2(claudeMdPath, "utf-8") : null;
|
|
1203
|
+
let finalContent;
|
|
1204
|
+
if (!existing) {
|
|
1205
|
+
finalContent = claudeSection;
|
|
1206
|
+
} else {
|
|
1207
|
+
const markerIndex = existing.indexOf(COCKPIT_MARKER);
|
|
1208
|
+
const base = markerIndex >= 0 ? existing.slice(0, markerIndex).trimEnd() : existing.trimEnd();
|
|
1209
|
+
finalContent = base ? `${base}
|
|
1210
|
+
|
|
1211
|
+
${claudeSection}` : claudeSection;
|
|
1212
|
+
}
|
|
1213
|
+
writeFileSync5(claudeMdPath, finalContent, "utf-8");
|
|
1214
|
+
ui.heading("Context Generate");
|
|
1215
|
+
ui.blank();
|
|
1216
|
+
const totalRules = context.global.length + context.project.length;
|
|
1217
|
+
if (totalRules === 0) {
|
|
1218
|
+
ui.warn("No context rules defined. Wrote empty cockpit section.");
|
|
1219
|
+
} else {
|
|
1220
|
+
ui.success(`Wrote ${totalRules} context rule${totalRules === 1 ? "" : "s"} to ${CLAUDE_MD}`);
|
|
1221
|
+
ui.dim(` Global: ${context.global.length}`);
|
|
1222
|
+
ui.dim(` Project: ${context.project.length}`);
|
|
1223
|
+
}
|
|
1224
|
+
ui.dim(` Path: ${claudeMdPath}`);
|
|
1225
|
+
ui.blank();
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// src/index.ts
|
|
1229
|
+
var program = new Command();
|
|
1230
|
+
program.name("cockpit").description("AI-first Development Environment Orchestrator").version("0.0.1");
|
|
1231
|
+
program.command("init [path]").description("Initialize a Cockpit workspace or project").option("--project", "Initialize as a project config (instead of workspace)").action(async (path, opts) => {
|
|
1232
|
+
try {
|
|
1233
|
+
await initCommand(path, { project: opts.project });
|
|
1234
|
+
} catch (err) {
|
|
1235
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1236
|
+
process.exit(1);
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
program.command("status [path]").description("Show current Cockpit environment status").action(async (path) => {
|
|
1240
|
+
try {
|
|
1241
|
+
await statusCommand(path);
|
|
1242
|
+
} catch (err) {
|
|
1243
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1244
|
+
process.exit(1);
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
program.command("apply").description("Apply Cockpit config to AI tools in the current project").option("--adapter <name>", "Apply only to a specific adapter").option("--clean", "Remove cockpit-managed files instead of applying").action(async (opts) => {
|
|
1248
|
+
try {
|
|
1249
|
+
await applyCommand({
|
|
1250
|
+
adapter: opts.adapter,
|
|
1251
|
+
clean: opts.clean
|
|
1252
|
+
});
|
|
1253
|
+
} catch (err) {
|
|
1254
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1255
|
+
process.exit(1);
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
var skillCmd = program.command("skill").description("Manage Cockpit skills");
|
|
1259
|
+
skillCmd.command("list").description("List all available skills").action(async () => {
|
|
1260
|
+
try {
|
|
1261
|
+
await skillListCommand();
|
|
1262
|
+
} catch (err) {
|
|
1263
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1264
|
+
process.exit(1);
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
skillCmd.command("create <name>").description("Scaffold a new skill from template").action(async (name) => {
|
|
1268
|
+
try {
|
|
1269
|
+
await skillCreateCommand(name);
|
|
1270
|
+
} catch (err) {
|
|
1271
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1272
|
+
process.exit(1);
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
skillCmd.command("add <path>").description("Add a skill from a local file").action(async (path) => {
|
|
1276
|
+
try {
|
|
1277
|
+
await skillAddCommand(path);
|
|
1278
|
+
} catch (err) {
|
|
1279
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1280
|
+
process.exit(1);
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
skillCmd.command("remove <name>").description("Remove a skill").action(async (name) => {
|
|
1284
|
+
try {
|
|
1285
|
+
await skillRemoveCommand(name);
|
|
1286
|
+
} catch (err) {
|
|
1287
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1288
|
+
process.exit(1);
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
var profileCmd = program.command("profile").description("Manage your personal Cockpit profile");
|
|
1292
|
+
profileCmd.command("show").description("Display current profile info").action(async () => {
|
|
1293
|
+
try {
|
|
1294
|
+
await profileShowCommand();
|
|
1295
|
+
} catch (err) {
|
|
1296
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1297
|
+
process.exit(1);
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
profileCmd.command("create").description("Create a new profile interactively").action(async () => {
|
|
1301
|
+
try {
|
|
1302
|
+
await profileCreateCommand();
|
|
1303
|
+
} catch (err) {
|
|
1304
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1305
|
+
process.exit(1);
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
var syncCmd = profileCmd.command("sync").description("Sync profile with remote git repository");
|
|
1309
|
+
syncCmd.command("push").description("Push profile to remote").action(async () => {
|
|
1310
|
+
try {
|
|
1311
|
+
await profileSyncPushCommand();
|
|
1312
|
+
} catch (err) {
|
|
1313
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1314
|
+
process.exit(1);
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
syncCmd.command("pull").description("Pull profile from remote").action(async () => {
|
|
1318
|
+
try {
|
|
1319
|
+
await profileSyncPullCommand();
|
|
1320
|
+
} catch (err) {
|
|
1321
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1322
|
+
process.exit(1);
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
profileCmd.command("export [file]").description("Export profile to a single YAML file").action(async (file) => {
|
|
1326
|
+
try {
|
|
1327
|
+
await profileExportCommand(file);
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
profileCmd.command("import <file>").description("Import profile from a YAML export file").action(async (file) => {
|
|
1334
|
+
try {
|
|
1335
|
+
await profileImportCommand(file);
|
|
1336
|
+
} catch (err) {
|
|
1337
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1338
|
+
process.exit(1);
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
var agentCmd = program.command("agent").description("Manage Cockpit agents");
|
|
1342
|
+
agentCmd.command("list").description("List available agents").action(async () => {
|
|
1343
|
+
try {
|
|
1344
|
+
await agentListCommand();
|
|
1345
|
+
} catch (err) {
|
|
1346
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1347
|
+
process.exit(1);
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
agentCmd.command("spawn <name>").description("Spawn an agent").action(async (name) => {
|
|
1351
|
+
try {
|
|
1352
|
+
await agentSpawnCommand(name);
|
|
1353
|
+
} catch (err) {
|
|
1354
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1355
|
+
process.exit(1);
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
agentCmd.command("stop <name>").description("Stop a running agent").action(async (name) => {
|
|
1359
|
+
try {
|
|
1360
|
+
await agentStopCommand(name);
|
|
1361
|
+
} catch (err) {
|
|
1362
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1363
|
+
process.exit(1);
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
agentCmd.command("status").description("Show agent status dashboard").action(async () => {
|
|
1367
|
+
try {
|
|
1368
|
+
await agentStatusCommand();
|
|
1369
|
+
} catch (err) {
|
|
1370
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1371
|
+
process.exit(1);
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
var worktreeCmd = program.command("worktree").description("Manage git worktrees");
|
|
1375
|
+
worktreeCmd.command("create <branch>").description("Create a new worktree").option("--repo <path>", "Path to git repo (default: cwd)").option("--path <path>", "Where to create the worktree").action(async (branch, opts) => {
|
|
1376
|
+
try {
|
|
1377
|
+
await worktreeCreateCommand(branch, { repo: opts.repo, path: opts.path });
|
|
1378
|
+
} catch (err) {
|
|
1379
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1380
|
+
process.exit(1);
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
worktreeCmd.command("list").description("List all worktrees").action(async () => {
|
|
1384
|
+
try {
|
|
1385
|
+
await worktreeListCommand();
|
|
1386
|
+
} catch (err) {
|
|
1387
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1388
|
+
process.exit(1);
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
worktreeCmd.command("status").description("Show worktree status dashboard").action(async () => {
|
|
1392
|
+
try {
|
|
1393
|
+
await worktreeStatusCommand();
|
|
1394
|
+
} catch (err) {
|
|
1395
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1396
|
+
process.exit(1);
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
worktreeCmd.command("assign <worktree> <agent>").description("Assign an agent to a worktree").action(async (wt, agent) => {
|
|
1400
|
+
try {
|
|
1401
|
+
await worktreeAssignCommand(wt, agent);
|
|
1402
|
+
} catch (err) {
|
|
1403
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1404
|
+
process.exit(1);
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
worktreeCmd.command("clean").description("Clean up stale worktrees").action(async () => {
|
|
1408
|
+
try {
|
|
1409
|
+
await worktreeCleanCommand();
|
|
1410
|
+
} catch (err) {
|
|
1411
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1412
|
+
process.exit(1);
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
var contextCmd = program.command("context").description("Manage context rules");
|
|
1416
|
+
contextCmd.command("show").description("Show current merged context").action(async () => {
|
|
1417
|
+
try {
|
|
1418
|
+
await contextShowCommand();
|
|
1419
|
+
} catch (err) {
|
|
1420
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1421
|
+
process.exit(1);
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1424
|
+
contextCmd.command("add <rule>").description("Add a context rule").option("--project", "Add as a project-scoped rule (default: global)").option("--scope <scope>", "Rule scope: global or project").action(async (rule, opts) => {
|
|
1425
|
+
try {
|
|
1426
|
+
await contextAddCommand(rule, {
|
|
1427
|
+
scope: opts.scope,
|
|
1428
|
+
project: opts.project
|
|
1429
|
+
});
|
|
1430
|
+
} catch (err) {
|
|
1431
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1432
|
+
process.exit(1);
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
contextCmd.command("generate").description("Generate adapter context files (CLAUDE.md etc.)").action(async () => {
|
|
1436
|
+
try {
|
|
1437
|
+
await contextGenerateCommand();
|
|
1438
|
+
} catch (err) {
|
|
1439
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1440
|
+
process.exit(1);
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
program.parse(process.argv);
|
|
1444
|
+
//# sourceMappingURL=index.js.map
|