@opellen/opengoal 1.0.3 → 1.0.4

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.
@@ -1,7 +1,29 @@
1
+ import { dirname } from "node:path";
2
+ const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
3
+ // Codex labels a skill from agents/openai.yaml when present; without it the
4
+ // label is derived from the frontmatter name with the casing flattened
5
+ // ("opengoal:go" shows as "Opengoal: Go").
6
+ const commandLabel = (name) => `OpenGoal: ${capitalize(name)}`;
7
+ function displayName(target) {
8
+ return target.kind === "command"
9
+ ? commandLabel(target.name)
10
+ : `OpenGoal ${capitalize(target.name.replace(/^opengoal-/, ""))}`;
11
+ }
1
12
  export const codex = {
2
13
  id: "codex",
3
14
  displayName: "Codex",
4
15
  configDir: ".codex",
5
16
  commandPath: (name, prefix) => `.codex/skills/${prefix}-${name}/SKILL.md`,
6
17
  formatCommand: (body) => body,
18
+ // Skills are invoked from Codex's picker, so prose refers to them by the
19
+ // label the picker shows, not by a slash form.
20
+ commandRef: (name) => commandLabel(name),
21
+ companionFiles: (target) => [
22
+ {
23
+ dst: `${dirname(target.dst).replace(/\\/g, "/")}/agents/openai.yaml`,
24
+ content: "interface:\n" +
25
+ ` display_name: ${JSON.stringify(displayName(target))}\n` +
26
+ ` short_description: ${JSON.stringify(target.description)}\n`,
27
+ },
28
+ ],
7
29
  };
@@ -4,6 +4,21 @@ export interface CommandMeta {
4
4
  category?: string;
5
5
  tags?: string[];
6
6
  }
7
+ /** An extra file a platform wants installed next to a command or skill. */
8
+ export interface CompanionFile {
9
+ /** Install path relative to the project root. */
10
+ dst: string;
11
+ content: string;
12
+ }
13
+ export interface CompanionTarget {
14
+ kind: "command" | "skill";
15
+ /** Command name ("go") or skill name ("opengoal-flow"). */
16
+ name: string;
17
+ description: string;
18
+ /** Install path of the file the companions accompany. */
19
+ dst: string;
20
+ prefix: string;
21
+ }
7
22
  export interface PlatformAdapter {
8
23
  id: string;
9
24
  displayName: string;
@@ -15,5 +30,18 @@ export interface PlatformAdapter {
15
30
  supportsCommands?: boolean;
16
31
  commandPath(name: string, prefix: string): string;
17
32
  formatCommand(body: string, meta: CommandMeta, prefix: string): string;
33
+ /**
34
+ * How an installed command is invoked on this platform, as written in the
35
+ * prose that templates emit ("run /opgl:go"). Platforms whose invocation
36
+ * differs from the Claude Code slash form `/<prefix>:<name>` override this;
37
+ * when omitted that form is used.
38
+ */
39
+ commandRef?(name: string, prefix: string): string;
40
+ /**
41
+ * Extra files the platform needs beside an installed command or skill, such
42
+ * as Codex's `agents/openai.yaml` display manifest. They are installed and
43
+ * uninstalled together with the file they accompany.
44
+ */
45
+ companionFiles?(target: CompanionTarget): CompanionFile[];
18
46
  }
19
47
  export declare const DEFAULT_PLATFORM = "claude";
@@ -15,6 +15,8 @@ export interface TemplateEntry {
15
15
  adapter: PlatformAdapter;
16
16
  meta: CommandMeta;
17
17
  rawContent: string;
18
+ /** Companion file produced by the adapter; rawContent is final, no rendering. */
19
+ generated?: boolean;
18
20
  }
19
21
  export declare function resolveTemplates(config: OpenGoalConfig): TemplateEntry[];
20
22
  export declare function renderFile(entry: TemplateEntry, config: OpenGoalConfig): string;
package/dist/templates.js CHANGED
@@ -18,6 +18,18 @@ const OPENGOAL_VERSION = (() => {
18
18
  }
19
19
  })();
20
20
  export const DEFAULT_PREFIX = "opgl";
21
+ function companionEntries(adapter, target, meta) {
22
+ if (!adapter.companionFiles)
23
+ return [];
24
+ return adapter.companionFiles(target).map((file) => ({
25
+ src: `generated/${target.kind}/${target.name}`,
26
+ dst: file.dst,
27
+ adapter,
28
+ meta,
29
+ rawContent: file.content,
30
+ generated: true,
31
+ }));
32
+ }
21
33
  const COMMAND_TEMPLATES = [
22
34
  "scout", "goal", "context", "design",
23
35
  "roadmap", "verify", "recap", "overview", "go",
@@ -64,32 +76,36 @@ export function resolveTemplates(config) {
64
76
  for (const adapter of adapters) {
65
77
  if (adapter.supportsCommands !== false) {
66
78
  for (const { cmd, src, rawContent, meta } of commandMetas) {
67
- entries.push({ src, dst: adapter.commandPath(cmd, config.prefix), adapter, meta, rawContent });
79
+ const dst = adapter.commandPath(cmd, config.prefix);
80
+ entries.push({ src, dst, adapter, meta, rawContent });
81
+ entries.push(...companionEntries(adapter, { kind: "command", name: cmd, description: meta.description, dst, prefix: config.prefix }, meta));
68
82
  }
69
83
  }
70
84
  for (const skill of skillContents) {
71
- entries.push({
72
- src: skill.src,
73
- dst: skillInstallPath(adapter, skill.skillName),
74
- adapter,
75
- meta: { name: skill.skillName, description: "" },
76
- rawContent: skill.rawContent,
77
- });
85
+ const dst = skillInstallPath(adapter, skill.skillName);
86
+ const description = skill.rawContent.replace(/\r\n/g, "\n").match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? "";
87
+ const meta = { name: skill.skillName, description };
88
+ entries.push({ src: skill.src, dst, adapter, meta, rawContent: skill.rawContent });
89
+ entries.push(...companionEntries(adapter, { kind: "skill", name: skill.skillName, description, dst, prefix: config.prefix }, meta));
78
90
  }
79
91
  }
80
92
  return entries;
81
93
  }
82
94
  export function renderFile(entry, config) {
95
+ if (entry.generated)
96
+ return entry.rawContent;
83
97
  const content = entry.rawContent.replace(/\r\n/g, "\n");
84
98
  const rendered = renderTemplate(content, {
85
99
  conditions: {
86
100
  subagent: config.subagent,
87
101
  },
88
102
  });
103
+ const commandRef = entry.adapter.commandRef ?? ((name, prefix) => `/${prefix}:${name}`);
89
104
  const substituted = rendered
90
105
  .replace(/\$DocsDir/g, config.docsDir)
91
106
  .replace(/\$CodebaseDir/g, config.codebaseDir)
92
107
  .replace(/\$OpenGoalVersion/g, OPENGOAL_VERSION)
108
+ .replace(/\/\$CommandPrefix:([a-z-]+)/g, (_match, name) => commandRef(name, config.prefix))
93
109
  .replace(/\$CommandPrefix/g, config.prefix);
94
110
  if (entry.src.startsWith("skills/")) {
95
111
  return formatSkill(substituted);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opellen/opengoal",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Goal-centric AI harness. Plain markdown, zero runtime — nothing runs but your agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -48,4 +48,4 @@
48
48
  "chalk": "^5.6.2",
49
49
  "yaml": "^2.8.3"
50
50
  }
51
- }
51
+ }