@opellen/opengoal 1.0.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.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +400 -0
  3. package/README.ko.md +409 -0
  4. package/README.md +416 -0
  5. package/README.zh-CN.md +436 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +298 -0
  8. package/dist/extend.d.ts +13 -0
  9. package/dist/extend.js +286 -0
  10. package/dist/hook-parser.d.ts +15 -0
  11. package/dist/hook-parser.js +95 -0
  12. package/dist/i18n/index.d.ts +5 -0
  13. package/dist/i18n/index.js +20 -0
  14. package/dist/i18n/messages.d.ts +28 -0
  15. package/dist/i18n/messages.js +49 -0
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.js +4 -0
  18. package/dist/installer.d.ts +30 -0
  19. package/dist/installer.js +165 -0
  20. package/dist/marker.d.ts +11 -0
  21. package/dist/marker.js +25 -0
  22. package/dist/platforms/amazon-q.d.ts +2 -0
  23. package/dist/platforms/amazon-q.js +12 -0
  24. package/dist/platforms/antigravity.d.ts +2 -0
  25. package/dist/platforms/antigravity.js +12 -0
  26. package/dist/platforms/auggie.d.ts +2 -0
  27. package/dist/platforms/auggie.js +15 -0
  28. package/dist/platforms/claude.d.ts +2 -0
  29. package/dist/platforms/claude.js +7 -0
  30. package/dist/platforms/cline.d.ts +2 -0
  31. package/dist/platforms/cline.js +11 -0
  32. package/dist/platforms/codebuddy.d.ts +2 -0
  33. package/dist/platforms/codebuddy.js +16 -0
  34. package/dist/platforms/codex.d.ts +2 -0
  35. package/dist/platforms/codex.js +7 -0
  36. package/dist/platforms/continue.d.ts +2 -0
  37. package/dist/platforms/continue.js +16 -0
  38. package/dist/platforms/costrict.d.ts +2 -0
  39. package/dist/platforms/costrict.js +15 -0
  40. package/dist/platforms/crush.d.ts +2 -0
  41. package/dist/platforms/crush.js +19 -0
  42. package/dist/platforms/cursor.d.ts +2 -0
  43. package/dist/platforms/cursor.js +18 -0
  44. package/dist/platforms/factory.d.ts +2 -0
  45. package/dist/platforms/factory.js +15 -0
  46. package/dist/platforms/gemini.d.ts +2 -0
  47. package/dist/platforms/gemini.js +14 -0
  48. package/dist/platforms/github-copilot.d.ts +2 -0
  49. package/dist/platforms/github-copilot.js +12 -0
  50. package/dist/platforms/iflow.d.ts +2 -0
  51. package/dist/platforms/iflow.js +18 -0
  52. package/dist/platforms/index.d.ts +10 -0
  53. package/dist/platforms/index.js +68 -0
  54. package/dist/platforms/kilocode.d.ts +2 -0
  55. package/dist/platforms/kilocode.js +8 -0
  56. package/dist/platforms/kiro.d.ts +2 -0
  57. package/dist/platforms/kiro.js +12 -0
  58. package/dist/platforms/opencode.d.ts +2 -0
  59. package/dist/platforms/opencode.js +12 -0
  60. package/dist/platforms/pi.d.ts +2 -0
  61. package/dist/platforms/pi.js +12 -0
  62. package/dist/platforms/qoder.d.ts +2 -0
  63. package/dist/platforms/qoder.js +19 -0
  64. package/dist/platforms/qwen.d.ts +2 -0
  65. package/dist/platforms/qwen.js +14 -0
  66. package/dist/platforms/roocode.d.ts +2 -0
  67. package/dist/platforms/roocode.js +11 -0
  68. package/dist/platforms/toml.d.ts +9 -0
  69. package/dist/platforms/toml.js +18 -0
  70. package/dist/platforms/trae.d.ts +2 -0
  71. package/dist/platforms/trae.js +11 -0
  72. package/dist/platforms/types.d.ts +19 -0
  73. package/dist/platforms/types.js +1 -0
  74. package/dist/platforms/windsurf.d.ts +2 -0
  75. package/dist/platforms/windsurf.js +19 -0
  76. package/dist/platforms/yaml.d.ts +10 -0
  77. package/dist/platforms/yaml.js +30 -0
  78. package/dist/prompts/platform-select.d.ts +1 -0
  79. package/dist/prompts/platform-select.js +174 -0
  80. package/dist/skills/generate.d.ts +13 -0
  81. package/dist/skills/generate.js +16 -0
  82. package/dist/templates.d.ts +20 -0
  83. package/dist/templates.js +98 -0
  84. package/dist/ui/ascii-logo.d.ts +6 -0
  85. package/dist/ui/ascii-logo.js +45 -0
  86. package/dist/ui/colors.d.ts +6 -0
  87. package/dist/ui/colors.js +17 -0
  88. package/dist/ui/welcome.d.ts +1 -0
  89. package/dist/ui/welcome.js +77 -0
  90. package/package.json +51 -0
  91. package/templates/commands/context.md +92 -0
  92. package/templates/commands/design.md +113 -0
  93. package/templates/commands/go.md +84 -0
  94. package/templates/commands/goal.md +360 -0
  95. package/templates/commands/overview.md +78 -0
  96. package/templates/commands/recap.md +77 -0
  97. package/templates/commands/roadmap.md +99 -0
  98. package/templates/commands/scout.md +89 -0
  99. package/templates/commands/verify.md +140 -0
  100. package/templates/skills/opengoal-flow/SKILL.md +169 -0
  101. package/templates/skills/opengoal-subagent/SKILL.md +159 -0
@@ -0,0 +1,95 @@
1
+ const HOOK_BEGIN = /^\s*<!--\s*@hook\s+(\S+)\s*-->/;
2
+ const HOOK_END = /^\s*<!--\s*@end-hook\s+(\S+)\s*-->/;
3
+ /**
4
+ * Validate that a file has properly paired hook markers.
5
+ * Returns list of hook ids found.
6
+ */
7
+ export function findHookPoints(fileContent) {
8
+ const ids = [];
9
+ const lines = fileContent.split("\n");
10
+ let openId = null;
11
+ for (const line of lines) {
12
+ const beginMatch = line.match(HOOK_BEGIN);
13
+ const endMatch = line.match(HOOK_END);
14
+ if (beginMatch) {
15
+ if (openId !== null) {
16
+ throw new Error(`Unclosed hook '${openId}'`);
17
+ }
18
+ openId = beginMatch[1];
19
+ }
20
+ else if (endMatch) {
21
+ const endId = endMatch[1];
22
+ if (openId === null) {
23
+ throw new Error(`Unexpected hook end '${endId}'`);
24
+ }
25
+ if (endId !== openId) {
26
+ throw new Error(`Mismatched hook end: expected '${openId}', got '${endId}'`);
27
+ }
28
+ ids.push(openId);
29
+ openId = null;
30
+ }
31
+ }
32
+ if (openId !== null) {
33
+ throw new Error(`Unclosed hook '${openId}'`);
34
+ }
35
+ return ids;
36
+ }
37
+ /**
38
+ * Inject hook content into a file's text.
39
+ * Returns the modified text.
40
+ * Throws on errors (missing markers, mismatched pairs, non-empty hooks).
41
+ */
42
+ export function injectHooks(fileContent, hooks) {
43
+ if (hooks.length === 0) {
44
+ return fileContent;
45
+ }
46
+ const lines = fileContent.split("\n");
47
+ const result = [];
48
+ const injected = new Set();
49
+ const hookMap = new Map(hooks.map((h) => [h.id, h.content]));
50
+ let openId = null;
51
+ let openLine = -1;
52
+ for (let i = 0; i < lines.length; i++) {
53
+ const line = lines[i];
54
+ const beginMatch = line.match(HOOK_BEGIN);
55
+ const endMatch = line.match(HOOK_END);
56
+ if (beginMatch) {
57
+ if (openId !== null) {
58
+ throw new Error(`Unclosed hook '${openId}'`);
59
+ }
60
+ openId = beginMatch[1];
61
+ openLine = i;
62
+ result.push(line);
63
+ }
64
+ else if (endMatch) {
65
+ const endId = endMatch[1];
66
+ if (openId === null) {
67
+ throw new Error(`Unexpected hook end '${endId}'`);
68
+ }
69
+ if (endId !== openId) {
70
+ throw new Error(`Mismatched hook end: expected '${openId}', got '${endId}'`);
71
+ }
72
+ if (i !== openLine + 1) {
73
+ throw new Error(`Hook '${openId}' already has content`);
74
+ }
75
+ if (hookMap.has(openId)) {
76
+ result.push(hookMap.get(openId));
77
+ injected.add(openId);
78
+ }
79
+ result.push(line);
80
+ openId = null;
81
+ }
82
+ else {
83
+ result.push(line);
84
+ }
85
+ }
86
+ if (openId !== null) {
87
+ throw new Error(`Unclosed hook '${openId}'`);
88
+ }
89
+ for (const hook of hooks) {
90
+ if (!injected.has(hook.id)) {
91
+ throw new Error(`Hook point '${hook.id}' not found in file`);
92
+ }
93
+ }
94
+ return result.join("\n");
95
+ }
@@ -0,0 +1,5 @@
1
+ import { type Locale, type MessageKey } from "./messages.js";
2
+ export declare function getLocale(): Locale;
3
+ export declare function setLocale(locale: Locale): void;
4
+ export declare function t(key: MessageKey): string;
5
+ export type { Locale, MessageKey };
@@ -0,0 +1,20 @@
1
+ import { messages } from "./messages.js";
2
+ function detectLocale() {
3
+ const raw = process.env.LC_ALL ||
4
+ process.env.LC_MESSAGES ||
5
+ process.env.LANG ||
6
+ "";
7
+ if (raw.startsWith("ko"))
8
+ return "ko";
9
+ return "en";
10
+ }
11
+ let current = detectLocale();
12
+ export function getLocale() {
13
+ return current;
14
+ }
15
+ export function setLocale(locale) {
16
+ current = locale;
17
+ }
18
+ export function t(key) {
19
+ return messages[current][key];
20
+ }
@@ -0,0 +1,28 @@
1
+ declare const en: {
2
+ readonly "welcome.tagline": "Goal-centric AI harness. Open structure, clear present.";
3
+ readonly "welcome.setsUp": "Installs:";
4
+ readonly "welcome.skills": "Slash commands";
5
+ readonly "welcome.commands": "Agent skills";
6
+ readonly "welcome.pressEnter": "Press Enter to select tools.";
7
+ readonly "select.message": "Select tools";
8
+ readonly "select.typeToFilter": "type to filter";
9
+ readonly "select.detected": "detected";
10
+ readonly "select.selected": "selected";
11
+ readonly "select.none": "none";
12
+ readonly "select.error.empty": "Select at least one tool.";
13
+ readonly "select.hint": "↑↓ navigate • Space toggle • Backspace remove • Enter confirm";
14
+ readonly "select.available": "available";
15
+ readonly "select.selectedLabel": "Selected:";
16
+ readonly "select.searchLabel": "Search:";
17
+ readonly "cli.dryRun": "Dry run — files that would be created:";
18
+ readonly "cli.complete": "opengoal init complete.";
19
+ readonly "cli.installed": "Installed files:";
20
+ readonly "cli.skipped": "Skipped (use --force to overwrite):";
21
+ readonly "cli.nextSteps": "What's next:";
22
+ readonly "cli.step1": "Review installed files";
23
+ readonly "cli.step2": "Run {cmd}";
24
+ };
25
+ export type MessageKey = keyof typeof en;
26
+ export type Locale = "en" | "ko";
27
+ export declare const messages: Record<Locale, Record<MessageKey, string>>;
28
+ export {};
@@ -0,0 +1,49 @@
1
+ const en = {
2
+ "welcome.tagline": "Goal-centric AI harness. Open structure, clear present.",
3
+ "welcome.setsUp": "Installs:",
4
+ "welcome.skills": "Slash commands",
5
+ "welcome.commands": "Agent skills",
6
+ "welcome.pressEnter": "Press Enter to select tools.",
7
+ "select.message": "Select tools",
8
+ "select.typeToFilter": "type to filter",
9
+ "select.detected": "detected",
10
+ "select.selected": "selected",
11
+ "select.none": "none",
12
+ "select.error.empty": "Select at least one tool.",
13
+ "select.hint": "↑↓ navigate • Space toggle • Backspace remove • Enter confirm",
14
+ "select.available": "available",
15
+ "select.selectedLabel": "Selected:",
16
+ "select.searchLabel": "Search:",
17
+ "cli.dryRun": "Dry run — files that would be created:",
18
+ "cli.complete": "opengoal init complete.",
19
+ "cli.installed": "Installed files:",
20
+ "cli.skipped": "Skipped (use --force to overwrite):",
21
+ "cli.nextSteps": "What's next:",
22
+ "cli.step1": "Review installed files",
23
+ "cli.step2": "Run {cmd}",
24
+ };
25
+ const ko = {
26
+ "welcome.tagline": "목표 중심 AI 하네스. 열린 구조, 선명한 현재.",
27
+ "welcome.setsUp": "포함:",
28
+ "welcome.skills": "슬래시 커맨드",
29
+ "welcome.commands": "에이전트 스킬",
30
+ "welcome.pressEnter": "Enter를 눌러 도구를 선택합니다.",
31
+ "select.message": "설치할 도구 선택",
32
+ "select.typeToFilter": "검색어 입력",
33
+ "select.detected": "감지됨",
34
+ "select.selected": "선택됨",
35
+ "select.none": "없음",
36
+ "select.error.empty": "하나 이상 선택해야 합니다.",
37
+ "select.hint": "↑↓ 이동 • Space 토글 • Backspace 제거 • Enter 확인",
38
+ "select.available": "개",
39
+ "select.selectedLabel": "선택:",
40
+ "select.searchLabel": "검색:",
41
+ "cli.dryRun": "드라이 런 — 생성될 파일:",
42
+ "cli.complete": "opengoal init 완료.",
43
+ "cli.installed": "설치된 파일:",
44
+ "cli.skipped": "건너뜀 (--force로 덮어쓰기):",
45
+ "cli.nextSteps": "다음:",
46
+ "cli.step1": "설치된 파일 확인",
47
+ "cli.step2": "{cmd} 실행",
48
+ };
49
+ export const messages = { en, ko };
@@ -0,0 +1,4 @@
1
+ export { parseMarkers, renderTemplate, type MarkerBlock, type RenderContext } from "./marker.js";
2
+ export { resolveTemplates, renderFile, type OpenGoalConfig, type TemplateEntry } from "./templates.js";
3
+ export { install, dryRun, type InstallResult, type PromptFn } from "./installer.js";
4
+ export { getAdapter, getAdapters, getAllAdapters, isValidPlatform, PLATFORM_IDS, DEFAULT_PLATFORM, type PlatformId, type PlatformAdapter, type CommandMeta, } from "./platforms/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { parseMarkers, renderTemplate } from "./marker.js";
2
+ export { resolveTemplates, renderFile } from "./templates.js";
3
+ export { install, dryRun } from "./installer.js";
4
+ export { getAdapter, getAdapters, getAllAdapters, isValidPlatform, PLATFORM_IDS, DEFAULT_PLATFORM, } from "./platforms/index.js";
@@ -0,0 +1,30 @@
1
+ import { type OpenGoalConfig, type TemplateEntry } from "./templates.js";
2
+ export interface InstallResult {
3
+ files: string[];
4
+ skipped: string[];
5
+ }
6
+ export type PromptFn = (question: string) => Promise<string>;
7
+ export declare function install(config: OpenGoalConfig, prompt?: PromptFn): Promise<InstallResult>;
8
+ export declare function dryRun(config: OpenGoalConfig): {
9
+ files: string[];
10
+ };
11
+ /**
12
+ * Finds command files left behind by a previous install under a stale
13
+ * prefix (e.g. after `--prefix` changes). Candidate paths are never
14
+ * globbed: they are exactly what resolveTemplates() would produce for the
15
+ * old prefix, so only files opengoal itself would have written are ever
16
+ * eligible. Skill entries are excluded (rule: skills don't carry the
17
+ * prefix, so they can never go stale), and the current prefix is never
18
+ * considered stale even if passed in explicitly.
19
+ */
20
+ export declare function detectStale(config: OpenGoalConfig, oldPrefixes: string[]): TemplateEntry[];
21
+ /**
22
+ * Removes previously-detected stale files, with the same consent model as
23
+ * install(): prompt via the shared askUser style unless config.force is
24
+ * set, never delete silently, and report what was left behind if the user
25
+ * declines. Emptied directories are pruned afterward (see pruneEmptyDirs).
26
+ */
27
+ export declare function removeStale(config: OpenGoalConfig, stale: TemplateEntry[], prompt?: PromptFn): Promise<{
28
+ removed: string[];
29
+ kept: string[];
30
+ }>;
@@ -0,0 +1,165 @@
1
+ import { existsSync, mkdirSync, writeFileSync, unlinkSync, readdirSync, rmdirSync } from "node:fs";
2
+ import { createInterface } from "node:readline";
3
+ import { join, dirname, resolve, sep } from "node:path";
4
+ import { resolveTemplates, renderFile } from "./templates.js";
5
+ function createDefaultPrompt() {
6
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
7
+ let closed = false;
8
+ rl.on("close", () => { closed = true; });
9
+ const prompt = (question) => {
10
+ if (closed)
11
+ return Promise.resolve("");
12
+ return new Promise((resolve) => {
13
+ rl.question(question, (answer) => {
14
+ resolve(answer.trim().toLowerCase());
15
+ });
16
+ rl.once("close", () => resolve(""));
17
+ });
18
+ };
19
+ return { prompt, close: () => rl.close() };
20
+ }
21
+ export async function install(config, prompt) {
22
+ const defaultPrompt = prompt ? null : createDefaultPrompt();
23
+ const askUser = prompt ?? defaultPrompt.prompt;
24
+ const entries = resolveTemplates(config);
25
+ const files = [];
26
+ const skipped = [];
27
+ let applyAll = null;
28
+ try {
29
+ for (const entry of entries) {
30
+ const dst = join(config.root, entry.dst);
31
+ const dir = dirname(dst);
32
+ if (existsSync(dst) && !config.force) {
33
+ if (applyAll === "skip") {
34
+ skipped.push(entry.dst);
35
+ continue;
36
+ }
37
+ if (applyAll !== "overwrite") {
38
+ const answer = await askUser(` ${entry.dst} already exists. Overwrite? [y/N/a(all)/s(skip all)] `);
39
+ if (answer === "a" || answer === "all") {
40
+ applyAll = "overwrite";
41
+ }
42
+ else if (answer === "s") {
43
+ applyAll = "skip";
44
+ skipped.push(entry.dst);
45
+ continue;
46
+ }
47
+ else if (answer !== "y" && answer !== "yes") {
48
+ skipped.push(entry.dst);
49
+ continue;
50
+ }
51
+ }
52
+ }
53
+ if (!existsSync(dir)) {
54
+ mkdirSync(dir, { recursive: true });
55
+ }
56
+ const content = renderFile(entry, config);
57
+ writeFileSync(dst, content, "utf-8");
58
+ files.push(entry.dst);
59
+ }
60
+ const docsDir = join(config.root, config.docsDir);
61
+ if (!existsSync(docsDir)) {
62
+ mkdirSync(docsDir, { recursive: true });
63
+ }
64
+ }
65
+ finally {
66
+ defaultPrompt?.close();
67
+ }
68
+ return { files, skipped };
69
+ }
70
+ export function dryRun(config) {
71
+ const entries = resolveTemplates(config);
72
+ const files = entries.map((e) => e.dst);
73
+ return { files };
74
+ }
75
+ /**
76
+ * Finds command files left behind by a previous install under a stale
77
+ * prefix (e.g. after `--prefix` changes). Candidate paths are never
78
+ * globbed: they are exactly what resolveTemplates() would produce for the
79
+ * old prefix, so only files opengoal itself would have written are ever
80
+ * eligible. Skill entries are excluded (rule: skills don't carry the
81
+ * prefix, so they can never go stale), and the current prefix is never
82
+ * considered stale even if passed in explicitly.
83
+ */
84
+ export function detectStale(config, oldPrefixes) {
85
+ const seen = new Set();
86
+ const stale = [];
87
+ for (const oldPrefix of oldPrefixes) {
88
+ if (oldPrefix === config.prefix)
89
+ continue;
90
+ const entries = resolveTemplates({ ...config, prefix: oldPrefix });
91
+ for (const entry of entries) {
92
+ if (entry.src.startsWith("skills/"))
93
+ continue;
94
+ if (seen.has(entry.dst))
95
+ continue;
96
+ seen.add(entry.dst);
97
+ if (existsSync(join(config.root, entry.dst))) {
98
+ stale.push(entry);
99
+ }
100
+ }
101
+ }
102
+ return stale;
103
+ }
104
+ /**
105
+ * Walks upward from startDir, removing directories that are now empty,
106
+ * stopping at (and never removing) boundaryDir itself, and stopping as
107
+ * soon as a directory still has contents. This is what lets directory-
108
+ * shaped adapters (.claude/commands/<prefix>/) and per-command-directory
109
+ * shapes (.codex/skills/<prefix>-<name>/) get cleaned up without any
110
+ * adapter-specific logic.
111
+ */
112
+ function pruneEmptyDirs(startDir, boundaryDir) {
113
+ const boundary = resolve(boundaryDir);
114
+ let dir = resolve(startDir);
115
+ while (dir !== boundary && dir.startsWith(boundary + sep)) {
116
+ if (!existsSync(dir)) {
117
+ dir = dirname(dir);
118
+ continue;
119
+ }
120
+ if (readdirSync(dir).length > 0)
121
+ break;
122
+ rmdirSync(dir);
123
+ dir = dirname(dir);
124
+ }
125
+ }
126
+ /**
127
+ * Removes previously-detected stale files, with the same consent model as
128
+ * install(): prompt via the shared askUser style unless config.force is
129
+ * set, never delete silently, and report what was left behind if the user
130
+ * declines. Emptied directories are pruned afterward (see pruneEmptyDirs).
131
+ */
132
+ export async function removeStale(config, stale, prompt) {
133
+ const removed = [];
134
+ const kept = [];
135
+ if (stale.length === 0) {
136
+ return { removed, kept };
137
+ }
138
+ const defaultPrompt = prompt ? null : createDefaultPrompt();
139
+ const askUser = prompt ?? defaultPrompt.prompt;
140
+ try {
141
+ let proceed = config.force === true;
142
+ if (!proceed) {
143
+ const answer = await askUser(` Found ${stale.length} stale command file(s) from a previous prefix. Remove? [y/N] `);
144
+ proceed = answer === "y" || answer === "yes";
145
+ }
146
+ if (!proceed) {
147
+ for (const entry of stale)
148
+ kept.push(entry.dst);
149
+ return { removed, kept };
150
+ }
151
+ for (const entry of stale) {
152
+ const dst = join(config.root, entry.dst);
153
+ if (!existsSync(dst)) {
154
+ continue;
155
+ }
156
+ unlinkSync(dst);
157
+ removed.push(entry.dst);
158
+ pruneEmptyDirs(join(config.root, dirname(entry.dst)), join(config.root, entry.adapter.configDir));
159
+ }
160
+ }
161
+ finally {
162
+ defaultPrompt?.close();
163
+ }
164
+ return { removed, kept };
165
+ }
@@ -0,0 +1,11 @@
1
+ export interface MarkerBlock {
2
+ fullMatch: string;
3
+ condition: string;
4
+ ifBlock: string;
5
+ elseBlock: string | undefined;
6
+ }
7
+ export interface RenderContext {
8
+ conditions: Record<string, boolean>;
9
+ }
10
+ export declare function parseMarkers(content: string): MarkerBlock[];
11
+ export declare function renderTemplate(content: string, ctx: RenderContext): string;
package/dist/marker.js ADDED
@@ -0,0 +1,25 @@
1
+ const MARKER_SOURCE = "<!-- @if (\\w+) -->\\n([\\s\\S]*?)(?:<!-- @else -->\\n([\\s\\S]*?))?<!-- @endif -->";
2
+ export function parseMarkers(content) {
3
+ const blocks = [];
4
+ let match;
5
+ const regex = new RegExp(MARKER_SOURCE, "g");
6
+ while ((match = regex.exec(content)) !== null) {
7
+ blocks.push({
8
+ fullMatch: match[0],
9
+ condition: match[1],
10
+ ifBlock: match[2],
11
+ elseBlock: match[3],
12
+ });
13
+ }
14
+ return blocks;
15
+ }
16
+ export function renderTemplate(content, ctx) {
17
+ const regex = new RegExp(MARKER_SOURCE, "g");
18
+ return content.replace(regex, (_match, condition, ifBlock, elseBlock) => {
19
+ const enabled = ctx.conditions[condition] ?? false;
20
+ if (enabled) {
21
+ return ifBlock.trimEnd();
22
+ }
23
+ return elseBlock?.trimEnd() ?? "";
24
+ });
25
+ }
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const amazonQ: PlatformAdapter;
@@ -0,0 +1,12 @@
1
+ import { stripFrontmatter, yamlFrontmatter } from "./yaml.js";
2
+ export const amazonQ = {
3
+ id: "amazon-q",
4
+ displayName: "Amazon Q Developer",
5
+ configDir: ".amazonq",
6
+ commandPath: (name, prefix) => `.amazonq/prompts/${prefix}-${name}.md`,
7
+ formatCommand: (body, meta) => {
8
+ const stripped = stripFrontmatter(body);
9
+ const fm = yamlFrontmatter({ description: meta.description });
10
+ return fm + stripped;
11
+ },
12
+ };
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const antigravity: PlatformAdapter;
@@ -0,0 +1,12 @@
1
+ import { stripFrontmatter, yamlFrontmatter } from "./yaml.js";
2
+ export const antigravity = {
3
+ id: "antigravity",
4
+ displayName: "Antigravity",
5
+ configDir: ".agent",
6
+ commandPath: (name, prefix) => `.agent/workflows/${prefix}-${name}.md`,
7
+ formatCommand: (body, meta) => {
8
+ const stripped = stripFrontmatter(body);
9
+ const fm = yamlFrontmatter({ description: meta.description });
10
+ return fm + stripped;
11
+ },
12
+ };
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const auggie: PlatformAdapter;
@@ -0,0 +1,15 @@
1
+ import { stripFrontmatter, yamlFrontmatter } from "./yaml.js";
2
+ export const auggie = {
3
+ id: "auggie",
4
+ displayName: "Augment CLI",
5
+ configDir: ".augment",
6
+ commandPath: (name, prefix) => `.augment/commands/${prefix}-${name}.md`,
7
+ formatCommand: (body, meta) => {
8
+ const stripped = stripFrontmatter(body);
9
+ const fm = yamlFrontmatter({
10
+ description: meta.description,
11
+ "argument-hint": "command arguments",
12
+ });
13
+ return fm + stripped;
14
+ },
15
+ };
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const claude: PlatformAdapter;
@@ -0,0 +1,7 @@
1
+ export const claude = {
2
+ id: "claude",
3
+ displayName: "Claude Code",
4
+ configDir: ".claude",
5
+ commandPath: (name, prefix) => `.claude/commands/${prefix}/${name}.md`,
6
+ formatCommand: (body) => body,
7
+ };
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const cline: PlatformAdapter;
@@ -0,0 +1,11 @@
1
+ import { stripFrontmatter } from "./yaml.js";
2
+ export const cline = {
3
+ id: "cline",
4
+ displayName: "Cline",
5
+ configDir: ".cline",
6
+ commandPath: (name, prefix) => `.clinerules/workflows/${prefix}-${name}.md`,
7
+ formatCommand: (body, meta, prefix) => {
8
+ const stripped = stripFrontmatter(body);
9
+ return `# ${prefix}-${meta.name}\n\n${meta.description}\n\n${stripped}`;
10
+ },
11
+ };
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const codebuddy: PlatformAdapter;
@@ -0,0 +1,16 @@
1
+ import { stripFrontmatter, yamlFrontmatter } from "./yaml.js";
2
+ export const codebuddy = {
3
+ id: "codebuddy",
4
+ displayName: "CodeBuddy",
5
+ configDir: ".codebuddy",
6
+ commandPath: (name, prefix) => `.codebuddy/commands/${prefix}/${name}.md`,
7
+ formatCommand: (body, meta, prefix) => {
8
+ const stripped = stripFrontmatter(body);
9
+ const fm = yamlFrontmatter({
10
+ name: `${prefix}:${meta.name}`,
11
+ description: meta.description,
12
+ "argument-hint": "[command arguments]",
13
+ });
14
+ return fm + stripped;
15
+ },
16
+ };
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const codex: PlatformAdapter;
@@ -0,0 +1,7 @@
1
+ export const codex = {
2
+ id: "codex",
3
+ displayName: "Codex",
4
+ configDir: ".codex",
5
+ commandPath: (name, prefix) => `.codex/skills/${prefix}-${name}/SKILL.md`,
6
+ formatCommand: (body) => body,
7
+ };
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const continueAdapter: PlatformAdapter;
@@ -0,0 +1,16 @@
1
+ import { stripFrontmatter, yamlFrontmatter } from "./yaml.js";
2
+ export const continueAdapter = {
3
+ id: "continue",
4
+ displayName: "Continue",
5
+ configDir: ".continue",
6
+ commandPath: (name, prefix) => `.continue/prompts/${prefix}-${name}.prompt`,
7
+ formatCommand: (body, meta, prefix) => {
8
+ const stripped = stripFrontmatter(body);
9
+ const fm = yamlFrontmatter({
10
+ name: `${prefix}-${meta.name}`,
11
+ description: meta.description,
12
+ invokable: "true",
13
+ });
14
+ return fm + stripped;
15
+ },
16
+ };
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const costrict: PlatformAdapter;
@@ -0,0 +1,15 @@
1
+ import { stripFrontmatter, yamlFrontmatter } from "./yaml.js";
2
+ export const costrict = {
3
+ id: "costrict",
4
+ displayName: "CoStrict",
5
+ configDir: ".cospec",
6
+ commandPath: (name, prefix) => `.cospec/${prefix}/commands/${prefix}-${name}.md`,
7
+ formatCommand: (body, meta) => {
8
+ const stripped = stripFrontmatter(body);
9
+ const fm = yamlFrontmatter({
10
+ description: meta.description,
11
+ "argument-hint": "command arguments",
12
+ });
13
+ return fm + stripped;
14
+ },
15
+ };
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const crush: PlatformAdapter;
@@ -0,0 +1,19 @@
1
+ import { stripFrontmatter, yamlFrontmatter, } from "./yaml.js";
2
+ export const crush = {
3
+ id: "crush",
4
+ displayName: "Crush",
5
+ configDir: ".crush",
6
+ commandPath: (name, prefix) => `.crush/commands/${prefix}/${name}.md`,
7
+ formatCommand: (body, meta, prefix) => {
8
+ const stripped = stripFrontmatter(body);
9
+ const fields = {
10
+ name: `${prefix}:${meta.name}`,
11
+ description: meta.description,
12
+ };
13
+ if (meta.category)
14
+ fields.category = meta.category;
15
+ if (meta.tags && meta.tags.length > 0)
16
+ fields.tags = meta.tags;
17
+ return yamlFrontmatter(fields) + stripped;
18
+ },
19
+ };
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const cursor: PlatformAdapter;
@@ -0,0 +1,18 @@
1
+ import { stripFrontmatter, yamlFrontmatter, } from "./yaml.js";
2
+ export const cursor = {
3
+ id: "cursor",
4
+ displayName: "Cursor",
5
+ configDir: ".cursor",
6
+ commandPath: (name, prefix) => `.cursor/commands/${prefix}-${name}.md`,
7
+ formatCommand: (body, meta, prefix) => {
8
+ const stripped = stripFrontmatter(body);
9
+ const fields = {
10
+ name: `/${prefix}-${meta.name}`,
11
+ id: `${prefix}-${meta.name}`,
12
+ };
13
+ if (meta.category)
14
+ fields.category = meta.category;
15
+ fields.description = meta.description;
16
+ return yamlFrontmatter(fields) + stripped;
17
+ },
18
+ };