@frenchtoastman/oh-my-groundcontrol 0.0.18 → 0.0.20

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.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A custom command bundled in this repository.
3
+ * Copied from src/commands/ to ~/.config/opencode/command/ during install.
4
+ */
5
+ export interface CustomCommand {
6
+ /** Command name (filename without .md extension) */
7
+ name: string;
8
+ /** Human-readable description */
9
+ description: string;
10
+ /** Source path in this repo (relative to project root) */
11
+ sourcePath: string;
12
+ }
13
+ /**
14
+ * Registry of custom commands bundled in this repository.
15
+ */
16
+ export declare const CUSTOM_COMMANDS: CustomCommand[];
17
+ /**
18
+ * Get the target directory for custom command installation.
19
+ */
20
+ export declare function getCustomCommandsDir(): string;
21
+ /**
22
+ * Install a custom command by copying from src/commands/ to ~/.config/opencode/command/
23
+ * @param command - The custom command to install
24
+ * @returns True if installation succeeded, false otherwise
25
+ */
26
+ export declare function installCustomCommand(command: CustomCommand): boolean;
package/dist/cli/index.js CHANGED
@@ -13772,6 +13772,10 @@ var HashlineEditConfigSchema = exports_external.object({
13772
13772
  var DoubleConfirmationConfigSchema = exports_external.object({
13773
13773
  enabled: exports_external.boolean().default(false)
13774
13774
  });
13775
+ var LangfuseHeadersConfigSchema = exports_external.object({
13776
+ enabled: exports_external.boolean().default(false),
13777
+ customHeaders: exports_external.record(exports_external.string(), exports_external.string()).default({})
13778
+ });
13775
13779
  var PluginConfigSchema = exports_external.object({
13776
13780
  preset: exports_external.string().optional(),
13777
13781
  scoringEngineVersion: exports_external.enum(["v1", "v2-shadow", "v2"]).optional(),
@@ -13786,7 +13790,8 @@ var PluginConfigSchema = exports_external.object({
13786
13790
  allowedProviders: exports_external.array(exports_external.string()).optional(),
13787
13791
  sessionExport: SessionExportConfigSchema.optional(),
13788
13792
  hashline_edit: HashlineEditConfigSchema.optional(),
13789
- double_confirmation: DoubleConfirmationConfigSchema.optional()
13793
+ double_confirmation: DoubleConfirmationConfigSchema.optional(),
13794
+ langfuse_headers: LangfuseHeadersConfigSchema.optional()
13790
13795
  });
13791
13796
  // src/config/agent-mcps.ts
13792
13797
  var DEFAULT_AGENT_MCPS = {
@@ -16129,6 +16134,42 @@ function pickSupportOpenCodeModel(models, primaryModel) {
16129
16134
  }, primaryModel);
16130
16135
  return support;
16131
16136
  }
16137
+ // src/cli/custom-commands.ts
16138
+ import { copyFileSync as copyFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
16139
+ import { homedir as homedir3 } from "os";
16140
+ import { join as join3 } from "path";
16141
+ import { fileURLToPath as fileURLToPath2 } from "url";
16142
+ var CUSTOM_COMMANDS = [
16143
+ {
16144
+ name: "analyze",
16145
+ description: "Code review and analysis for uncommitted changes, commits, branches, PRs, or specific files",
16146
+ sourcePath: "src/commands/analyze.md"
16147
+ }
16148
+ ];
16149
+ function getCustomCommandsDir() {
16150
+ return join3(homedir3(), ".config", "opencode", "command");
16151
+ }
16152
+ function installCustomCommand(command) {
16153
+ try {
16154
+ const packageRoot = fileURLToPath2(new URL("../..", import.meta.url));
16155
+ const sourcePath = join3(packageRoot, command.sourcePath);
16156
+ const targetDir = getCustomCommandsDir();
16157
+ const targetPath = join3(targetDir, `${command.name}.md`);
16158
+ if (!existsSync4(sourcePath)) {
16159
+ console.error(`Custom command source not found: ${sourcePath}`);
16160
+ return false;
16161
+ }
16162
+ if (!existsSync4(targetDir)) {
16163
+ mkdirSync3(targetDir, { recursive: true });
16164
+ }
16165
+ copyFileSync3(sourcePath, targetPath);
16166
+ return true;
16167
+ } catch (error48) {
16168
+ console.error(`Failed to install custom command: ${command.name}`, error48);
16169
+ return false;
16170
+ }
16171
+ }
16172
+
16132
16173
  // src/cli/install.ts
16133
16174
  var GREEN = "\x1B[32m";
16134
16175
  var BLUE = "\x1B[34m";
@@ -16525,12 +16566,15 @@ async function runManualSetupMode(rl, detected, modelsOnly = false) {
16525
16566
  console.log();
16526
16567
  skills = await askYesNo(rl, "Install recommended skills?", "yes");
16527
16568
  console.log();
16528
- console.log(`${BOLD}Custom Skills:${RESET}`);
16569
+ console.log(`${BOLD}Custom Skills & Commands:${RESET}`);
16529
16570
  for (const skill of CUSTOM_SKILLS) {
16530
- console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET}: ${skill.description}`);
16571
+ console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET} (skill): ${skill.description}`);
16572
+ }
16573
+ for (const command of CUSTOM_COMMANDS) {
16574
+ console.log(` ${SYMBOLS.bullet} ${BOLD}/${command.name}${RESET} (command): ${command.description}`);
16531
16575
  }
16532
16576
  console.log();
16533
- customSkills = await askYesNo(rl, "Install custom skills?", "yes");
16577
+ customSkills = await askYesNo(rl, "Install custom skills & commands?", "yes");
16534
16578
  console.log();
16535
16579
  } else {
16536
16580
  printInfo("Models-only mode: skipping plugin/auth setup and skills prompts.");
@@ -16676,12 +16720,15 @@ async function runInteractiveMode(detected, modelsOnly = false) {
16676
16720
  console.log();
16677
16721
  skills = await askYesNo(rl, "Install recommended skills?", "yes");
16678
16722
  console.log();
16679
- console.log(`${BOLD}Custom Skills:${RESET}`);
16723
+ console.log(`${BOLD}Custom Skills & Commands:${RESET}`);
16680
16724
  for (const skill of CUSTOM_SKILLS) {
16681
- console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET}: ${skill.description}`);
16725
+ console.log(` ${SYMBOLS.bullet} ${BOLD}${skill.name}${RESET} (skill): ${skill.description}`);
16726
+ }
16727
+ for (const command of CUSTOM_COMMANDS) {
16728
+ console.log(` ${SYMBOLS.bullet} ${BOLD}/${command.name}${RESET} (command): ${command.description}`);
16682
16729
  }
16683
16730
  console.log();
16684
- customSkills = await askYesNo(rl, "Install custom skills?", "yes");
16731
+ customSkills = await askYesNo(rl, "Install custom skills & commands?", "yes");
16685
16732
  console.log();
16686
16733
  } else {
16687
16734
  printInfo("Models-only mode: skipping plugin/auth setup and skills prompts.");
@@ -16738,6 +16785,8 @@ async function runInstall(config2) {
16738
16785
  totalSteps += 1;
16739
16786
  if (!modelsOnly && resolvedConfig.installCustomSkills)
16740
16787
  totalSteps += 1;
16788
+ if (!modelsOnly && resolvedConfig.installCustomSkills)
16789
+ totalSteps += 1;
16741
16790
  let step = 1;
16742
16791
  if (modelsOnly) {
16743
16792
  printInfo("Models-only mode: updating model assignments without reinstalling plugins/skills.");
@@ -16912,6 +16961,27 @@ ${JSON.stringify(liteConfig, null, 2)}
16912
16961
  printSuccess(`${customSkillsInstalled}/${CUSTOM_SKILLS.length} custom skills installed`);
16913
16962
  }
16914
16963
  }
16964
+ if (!modelsOnly && resolvedConfig.installCustomSkills) {
16965
+ printStep(step++, totalSteps, "Installing custom commands...");
16966
+ if (resolvedConfig.dryRun) {
16967
+ printInfo("Dry run mode - would install custom commands:");
16968
+ for (const command of CUSTOM_COMMANDS) {
16969
+ printInfo(` - /${command.name}`);
16970
+ }
16971
+ } else {
16972
+ let commandsInstalled = 0;
16973
+ for (const command of CUSTOM_COMMANDS) {
16974
+ printInfo(`Installing /${command.name}...`);
16975
+ if (installCustomCommand(command)) {
16976
+ printSuccess(`Installed: /${command.name}`);
16977
+ commandsInstalled++;
16978
+ } else {
16979
+ printWarning(`Failed to install: /${command.name}`);
16980
+ }
16981
+ }
16982
+ printSuccess(`${commandsInstalled}/${CUSTOM_COMMANDS.length} custom commands installed`);
16983
+ }
16984
+ }
16915
16985
  console.log();
16916
16986
  console.log(formatConfigSummary(resolvedConfig));
16917
16987
  console.log();
@@ -165,6 +165,11 @@ export declare const DoubleConfirmationConfigSchema: z.ZodObject<{
165
165
  enabled: z.ZodDefault<z.ZodBoolean>;
166
166
  }, z.core.$strip>;
167
167
  export type DoubleConfirmationConfig = z.infer<typeof DoubleConfirmationConfigSchema>;
168
+ export declare const LangfuseHeadersConfigSchema: z.ZodObject<{
169
+ enabled: z.ZodDefault<z.ZodBoolean>;
170
+ customHeaders: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
171
+ }, z.core.$strip>;
172
+ export type LangfuseHeadersConfig = z.infer<typeof LangfuseHeadersConfigSchema>;
168
173
  export declare const PluginConfigSchema: z.ZodObject<{
169
174
  preset: z.ZodOptional<z.ZodString>;
170
175
  scoringEngineVersion: z.ZodOptional<z.ZodEnum<{
@@ -298,6 +303,10 @@ export declare const PluginConfigSchema: z.ZodObject<{
298
303
  double_confirmation: z.ZodOptional<z.ZodObject<{
299
304
  enabled: z.ZodDefault<z.ZodBoolean>;
300
305
  }, z.core.$strip>>;
306
+ langfuse_headers: z.ZodOptional<z.ZodObject<{
307
+ enabled: z.ZodDefault<z.ZodBoolean>;
308
+ customHeaders: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
309
+ }, z.core.$strip>>;
301
310
  }, z.core.$strip>;
302
311
  export type PluginConfig = z.infer<typeof PluginConfigSchema>;
303
312
  export type { AgentName } from './constants';
@@ -6,6 +6,7 @@ export { createDoubleConfirmationHook } from './double-confirmation';
6
6
  export { createEditErrorRecoveryHook } from './edit-error-recovery';
7
7
  export { createHashlineReadEnhancerHook } from './hashline-read-enhancer';
8
8
  export { createJsonErrorRecoveryHook } from './json-error-recovery';
9
+ export { createLangfuseHeadersHook } from './langfuse-headers';
9
10
  export { createPhaseReminderHook } from './phase-reminder';
10
11
  export { createPostReadNudgeHook } from './post-read-nudge';
11
12
  export { createQuestionRouterHook } from './question-router';
@@ -0,0 +1,31 @@
1
+ import type { LangfuseHeadersConfig } from '../../config/schema';
2
+ interface ChatHeadersInput {
3
+ sessionID: string;
4
+ agent: string;
5
+ model: {
6
+ id: string;
7
+ providerID: string;
8
+ };
9
+ provider: {
10
+ source: string;
11
+ info: unknown;
12
+ options: Record<string, unknown>;
13
+ };
14
+ message: unknown;
15
+ }
16
+ interface ChatHeadersOutput {
17
+ headers: Record<string, string>;
18
+ }
19
+ /**
20
+ * Creates the Langfuse trace enrichment headers hook.
21
+ *
22
+ * Injects dynamic `X-OC-*` HTTP headers into every LLM API request,
23
+ * providing rich OpenCode context for Langfuse trace enrichment and
24
+ * training data tagging.
25
+ *
26
+ * Ships disabled by default — enable via config.langfuse_headers.enabled.
27
+ */
28
+ export declare function createLangfuseHeadersHook(config?: Partial<LangfuseHeadersConfig>): {
29
+ 'chat.headers': (input: ChatHeadersInput, output: ChatHeadersOutput) => Promise<void>;
30
+ };
31
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Plugin } from '@opencode-ai/plugin';
2
- declare const OhMyOpenCodeLite: Plugin;
3
- export default OhMyOpenCodeLite;
2
+ declare const OhMyGroundControl: Plugin;
3
+ export default OhMyGroundControl;
4
4
  export type { AgentName, AgentOverrideConfig, HashlineEditConfig, McpName, PluginConfig, SessionExportConfig, TmuxConfig, TmuxLayout, } from './config';
5
5
  export type { RemoteMcpConfig } from './mcp';
package/dist/index.js CHANGED
@@ -17104,6 +17104,10 @@ var HashlineEditConfigSchema = exports_external.object({
17104
17104
  var DoubleConfirmationConfigSchema = exports_external.object({
17105
17105
  enabled: exports_external.boolean().default(false)
17106
17106
  });
17107
+ var LangfuseHeadersConfigSchema = exports_external.object({
17108
+ enabled: exports_external.boolean().default(false),
17109
+ customHeaders: exports_external.record(exports_external.string(), exports_external.string()).default({})
17110
+ });
17107
17111
  var PluginConfigSchema = exports_external.object({
17108
17112
  preset: exports_external.string().optional(),
17109
17113
  scoringEngineVersion: exports_external.enum(["v1", "v2-shadow", "v2"]).optional(),
@@ -17118,7 +17122,8 @@ var PluginConfigSchema = exports_external.object({
17118
17122
  allowedProviders: exports_external.array(exports_external.string()).optional(),
17119
17123
  sessionExport: SessionExportConfigSchema.optional(),
17120
17124
  hashline_edit: HashlineEditConfigSchema.optional(),
17121
- double_confirmation: DoubleConfirmationConfigSchema.optional()
17125
+ double_confirmation: DoubleConfirmationConfigSchema.optional(),
17126
+ langfuse_headers: LangfuseHeadersConfigSchema.optional()
17122
17127
  });
17123
17128
 
17124
17129
  // src/config/loader.ts
@@ -23144,6 +23149,58 @@ ${JSON_ERROR_REMINDER}`;
23144
23149
  }
23145
23150
  };
23146
23151
  }
23152
+ // src/hooks/langfuse-headers/index.ts
23153
+ var AGENT_TASK_MAP = {
23154
+ orchestrator: "planning",
23155
+ explorer: "research",
23156
+ fixer: "coding",
23157
+ designer: "design",
23158
+ librarian: "research",
23159
+ oracle: "analysis",
23160
+ build: "coding",
23161
+ verification: "verification",
23162
+ "pre-flight": "planning",
23163
+ contractor: "planning",
23164
+ groundcontrol: "planning",
23165
+ "power-slap-red": "coding",
23166
+ "power-slap-blue": "coding",
23167
+ "power-slap-green": "coding"
23168
+ };
23169
+ function createLangfuseHeadersHook(config2) {
23170
+ const enabled = config2?.enabled ?? false;
23171
+ const customHeaders = config2?.customHeaders ?? {};
23172
+ const pluginVersion = getCachedVersion() ?? "unknown";
23173
+ return {
23174
+ "chat.headers": async (input, output) => {
23175
+ if (!enabled)
23176
+ return;
23177
+ try {
23178
+ const dynamic = {
23179
+ "X-OC-App": "opencode",
23180
+ "X-OC-Agent": input.agent || undefined,
23181
+ "X-OC-Task": input.agent && AGENT_TASK_MAP[input.agent] || (input.agent ? "general" : undefined),
23182
+ "X-OC-Session-Id": input.sessionID || undefined,
23183
+ "X-OC-Model": input.model?.id || undefined,
23184
+ "X-OC-Provider": input.model?.providerID || undefined,
23185
+ "X-OC-Plugin-Version": pluginVersion || undefined,
23186
+ "X-OC-Prompt-Version": "opencode-default"
23187
+ };
23188
+ for (const [key, value] of Object.entries(dynamic)) {
23189
+ if (value) {
23190
+ output.headers[key] = value;
23191
+ }
23192
+ }
23193
+ for (const [key, value] of Object.entries(customHeaders)) {
23194
+ if (value) {
23195
+ output.headers[key] = value;
23196
+ }
23197
+ }
23198
+ } catch (err) {
23199
+ log("[langfuse-headers] Error injecting headers:", err);
23200
+ }
23201
+ }
23202
+ };
23203
+ }
23147
23204
  // src/hooks/phase-reminder/index.ts
23148
23205
  var PHASE_REMINDER = `<reminder>Recall Workflow Rules:
23149
23206
  Before acting: <think> \u2014 analyze what you see, what's been accomplished, what still needs to be done.
@@ -39318,7 +39375,7 @@ var lsp_rename = tool({
39318
39375
  }
39319
39376
  });
39320
39377
  // src/index.ts
39321
- var OhMyOpenCodeLite = async (ctx) => {
39378
+ var OhMyGroundControl = async (ctx) => {
39322
39379
  const config3 = loadPluginConfig(ctx.directory);
39323
39380
  const agentDefs = createAgents(config3);
39324
39381
  const agents = getAgentConfigs(config3);
@@ -39359,6 +39416,7 @@ var OhMyOpenCodeLite = async (ctx) => {
39359
39416
  const hashlineReadEnhancerHook = createHashlineReadEnhancerHook(config3.hashline_edit);
39360
39417
  const editErrorRecoveryHook = createEditErrorRecoveryHook();
39361
39418
  const doubleConfirmationHook = createDoubleConfirmationHook(config3.double_confirmation);
39419
+ const langfuseHeadersHook = createLangfuseHeadersHook(config3.langfuse_headers);
39362
39420
  const hashlineEditEnabled = config3.hashline_edit?.enabled !== false;
39363
39421
  const hashlineEditTool = hashlineEditEnabled ? createHashlineEditTool() : undefined;
39364
39422
  return {
@@ -39463,6 +39521,7 @@ var OhMyOpenCodeLite = async (ctx) => {
39463
39521
  "experimental.chat.messages.transform": phaseReminderHook["experimental.chat.messages.transform"],
39464
39522
  "chat.message": questionRouterHook["chat.message"],
39465
39523
  "command.execute.before": analyzeCommandHook["command.execute.before"],
39524
+ "chat.headers": langfuseHeadersHook["chat.headers"],
39466
39525
  "tool.execute.after": async (input, output) => {
39467
39526
  await delegateTaskRetryHook["tool.execute.after"](input, output);
39468
39527
  await jsonErrorRecoveryHook["tool.execute.after"](input, output);
@@ -39473,7 +39532,7 @@ var OhMyOpenCodeLite = async (ctx) => {
39473
39532
  }
39474
39533
  };
39475
39534
  };
39476
- var src_default = OhMyOpenCodeLite;
39535
+ var src_default = OhMyGroundControl;
39477
39536
  export {
39478
39537
  src_default as default
39479
39538
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frenchtoastman/oh-my-groundcontrol",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "An OpenCode plugin for multi-agent orchestration for structured planning with NASA-style guardrails.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",