@opellen/scaff 0.1.15 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +2 -0
  3. package/README.md +2 -0
  4. package/dist/agents-partial.d.ts +2 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +235 -0
  7. package/dist/extend.d.ts +12 -0
  8. package/dist/extend.js +242 -0
  9. package/dist/hook-parser.d.ts +15 -0
  10. package/dist/hook-parser.js +95 -0
  11. package/dist/i18n/index.d.ts +5 -0
  12. package/dist/i18n/index.js +20 -0
  13. package/dist/i18n/messages.d.ts +28 -0
  14. package/dist/i18n/messages.js +49 -0
  15. package/dist/index.d.ts +4 -0
  16. package/dist/index.js +4 -0
  17. package/dist/installer.d.ts +10 -0
  18. package/dist/installer.js +74 -0
  19. package/dist/marker.d.ts +11 -0
  20. package/dist/marker.js +25 -0
  21. package/dist/platforms/amazon-q.d.ts +2 -0
  22. package/dist/platforms/amazon-q.js +12 -0
  23. package/dist/platforms/antigravity.d.ts +2 -0
  24. package/dist/platforms/antigravity.js +12 -0
  25. package/dist/platforms/auggie.d.ts +2 -0
  26. package/dist/platforms/auggie.js +15 -0
  27. package/dist/platforms/claude.d.ts +2 -0
  28. package/dist/platforms/claude.js +7 -0
  29. package/dist/platforms/cline.d.ts +2 -0
  30. package/dist/platforms/cline.js +11 -0
  31. package/dist/platforms/codebuddy.d.ts +2 -0
  32. package/dist/platforms/codebuddy.js +16 -0
  33. package/dist/platforms/codex.d.ts +2 -0
  34. package/dist/platforms/codex.js +7 -0
  35. package/dist/platforms/continue.d.ts +2 -0
  36. package/dist/platforms/continue.js +16 -0
  37. package/dist/platforms/costrict.d.ts +2 -0
  38. package/dist/platforms/costrict.js +15 -0
  39. package/dist/platforms/crush.d.ts +2 -0
  40. package/dist/platforms/crush.js +19 -0
  41. package/dist/platforms/cursor.d.ts +2 -0
  42. package/dist/platforms/cursor.js +18 -0
  43. package/dist/platforms/factory.d.ts +2 -0
  44. package/dist/platforms/factory.js +15 -0
  45. package/dist/platforms/gemini.d.ts +2 -0
  46. package/dist/platforms/gemini.js +14 -0
  47. package/dist/platforms/github-copilot.d.ts +2 -0
  48. package/dist/platforms/github-copilot.js +12 -0
  49. package/dist/platforms/iflow.d.ts +2 -0
  50. package/dist/platforms/iflow.js +18 -0
  51. package/dist/platforms/index.d.ts +10 -0
  52. package/dist/platforms/index.js +68 -0
  53. package/dist/platforms/kilocode.d.ts +2 -0
  54. package/dist/platforms/kilocode.js +8 -0
  55. package/dist/platforms/kiro.d.ts +2 -0
  56. package/dist/platforms/kiro.js +12 -0
  57. package/dist/platforms/opencode.d.ts +2 -0
  58. package/dist/platforms/opencode.js +12 -0
  59. package/dist/platforms/pi.d.ts +2 -0
  60. package/dist/platforms/pi.js +12 -0
  61. package/dist/platforms/qoder.d.ts +2 -0
  62. package/dist/platforms/qoder.js +19 -0
  63. package/dist/platforms/qwen.d.ts +2 -0
  64. package/dist/platforms/qwen.js +14 -0
  65. package/dist/platforms/roocode.d.ts +2 -0
  66. package/dist/platforms/roocode.js +11 -0
  67. package/dist/platforms/toml.d.ts +9 -0
  68. package/dist/platforms/toml.js +18 -0
  69. package/dist/platforms/trae.d.ts +2 -0
  70. package/dist/platforms/trae.js +11 -0
  71. package/dist/platforms/types.d.ts +19 -0
  72. package/dist/platforms/types.js +1 -0
  73. package/dist/platforms/windsurf.d.ts +2 -0
  74. package/dist/platforms/windsurf.js +19 -0
  75. package/dist/platforms/yaml.d.ts +10 -0
  76. package/dist/platforms/yaml.js +30 -0
  77. package/dist/platforms.d.ts +20 -0
  78. package/dist/platforms.js +139 -0
  79. package/dist/prompts/platform-select.d.ts +1 -0
  80. package/dist/prompts/platform-select.js +174 -0
  81. package/dist/skills/generate.d.ts +13 -0
  82. package/dist/skills/generate.js +16 -0
  83. package/dist/templates.d.ts +18 -0
  84. package/dist/templates.js +96 -0
  85. package/dist/ui/ascii-logo.d.ts +6 -0
  86. package/dist/ui/ascii-logo.js +38 -0
  87. package/dist/ui/colors.d.ts +2 -0
  88. package/dist/ui/colors.js +3 -0
  89. package/dist/ui/welcome.d.ts +1 -0
  90. package/dist/ui/welcome.js +77 -0
  91. package/package.json +1 -1
  92. package/templates/commands/context.md +1 -0
  93. package/templates/commands/goal.md +3 -1
  94. package/templates/commands/scout.md +3 -1
  95. package/templates/skills/scaff-flow/SKILL.md +10 -0
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const opencode: PlatformAdapter;
@@ -0,0 +1,12 @@
1
+ import { stripFrontmatter, yamlFrontmatter } from "./yaml.js";
2
+ export const opencode = {
3
+ id: "opencode",
4
+ displayName: "OpenCode",
5
+ configDir: ".opencode",
6
+ commandPath: (name) => `.opencode/command/scaff-${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 pi: PlatformAdapter;
@@ -0,0 +1,12 @@
1
+ import { stripFrontmatter, yamlFrontmatter } from "./yaml.js";
2
+ export const pi = {
3
+ id: "pi",
4
+ displayName: "Pi",
5
+ configDir: ".pi",
6
+ commandPath: (name) => `.pi/prompts/scaff-${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 qoder: PlatformAdapter;
@@ -0,0 +1,19 @@
1
+ import { stripFrontmatter, yamlFrontmatter, } from "./yaml.js";
2
+ export const qoder = {
3
+ id: "qoder",
4
+ displayName: "Qoder",
5
+ configDir: ".qoder",
6
+ commandPath: (name) => `.qoder/commands/scaff/${name}.md`,
7
+ formatCommand: (body, meta) => {
8
+ const stripped = stripFrontmatter(body);
9
+ const fields = {
10
+ name: `scaff:${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 qwen: PlatformAdapter;
@@ -0,0 +1,14 @@
1
+ import { stripFrontmatter } from "./yaml.js";
2
+ import { escapeTomlString, escapeTomlMultiline } from "./toml.js";
3
+ export const qwen = {
4
+ id: "qwen",
5
+ displayName: "Qwen Code",
6
+ configDir: ".qwen",
7
+ commandPath: (name) => `.qwen/commands/scaff-${name}.toml`,
8
+ formatCommand: (body, meta) => {
9
+ const stripped = stripFrontmatter(body);
10
+ const description = escapeTomlString(meta.description);
11
+ const prompt = escapeTomlMultiline(stripped.trimEnd());
12
+ return `description = "${description}"\n\nprompt = """\n${prompt}\n"""\n`;
13
+ },
14
+ };
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const roocode: PlatformAdapter;
@@ -0,0 +1,11 @@
1
+ import { stripFrontmatter } from "./yaml.js";
2
+ export const roocode = {
3
+ id: "roocode",
4
+ displayName: "RooCode",
5
+ configDir: ".roo",
6
+ commandPath: (name) => `.roo/commands/scaff-${name}.md`,
7
+ formatCommand: (body, meta) => {
8
+ const stripped = stripFrontmatter(body);
9
+ return `# scaff-${meta.name}\n\n${meta.description}\n\n${stripped}`;
10
+ },
11
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Escapes a TOML basic string (double-quoted).
3
+ */
4
+ export declare function escapeTomlString(value: string): string;
5
+ /**
6
+ * Escapes a TOML multi-line basic string (triple-quoted).
7
+ * Only the sequence `"""` needs escaping inside the body.
8
+ */
9
+ export declare function escapeTomlMultiline(value: string): string;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Escapes a TOML basic string (double-quoted).
3
+ */
4
+ export function escapeTomlString(value) {
5
+ return value
6
+ .replace(/\\/g, "\\\\")
7
+ .replace(/"/g, '\\"')
8
+ .replace(/\n/g, "\\n")
9
+ .replace(/\r/g, "\\r")
10
+ .replace(/\t/g, "\\t");
11
+ }
12
+ /**
13
+ * Escapes a TOML multi-line basic string (triple-quoted).
14
+ * Only the sequence `"""` needs escaping inside the body.
15
+ */
16
+ export function escapeTomlMultiline(value) {
17
+ return value.replace(/"""/g, '\\"\\"\\"');
18
+ }
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const trae: PlatformAdapter;
@@ -0,0 +1,11 @@
1
+ export const trae = {
2
+ id: "trae",
3
+ displayName: "Trae",
4
+ configDir: ".trae",
5
+ supportsCommands: false,
6
+ // Trae is skills-only; commandPath/formatCommand are never invoked by the
7
+ // installer because supportsCommands=false. They exist to satisfy the
8
+ // interface contract.
9
+ commandPath: (name) => `.trae/commands/scaff-${name}.md`,
10
+ formatCommand: (body) => body,
11
+ };
@@ -0,0 +1,19 @@
1
+ export interface CommandMeta {
2
+ name: string;
3
+ description: string;
4
+ category?: string;
5
+ tags?: string[];
6
+ }
7
+ export interface PlatformAdapter {
8
+ id: string;
9
+ displayName: string;
10
+ configDir: string;
11
+ /**
12
+ * Whether this platform supports slash commands. When false, only skills
13
+ * are installed (e.g. Trae). Defaults to true.
14
+ */
15
+ supportsCommands?: boolean;
16
+ commandPath(name: string): string;
17
+ formatCommand(body: string, meta: CommandMeta): string;
18
+ }
19
+ export declare const DEFAULT_PLATFORM = "claude";
@@ -0,0 +1 @@
1
+ export const DEFAULT_PLATFORM = "claude";
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const windsurf: PlatformAdapter;
@@ -0,0 +1,19 @@
1
+ import { stripFrontmatter, yamlFrontmatter, } from "./yaml.js";
2
+ export const windsurf = {
3
+ id: "windsurf",
4
+ displayName: "Windsurf",
5
+ configDir: ".windsurf",
6
+ commandPath: (name) => `.windsurf/workflows/scaff-${name}.md`,
7
+ formatCommand: (body, meta) => {
8
+ const stripped = stripFrontmatter(body);
9
+ const fields = {
10
+ name: `scaff:${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,10 @@
1
+ export declare function stripFrontmatter(content: string): string;
2
+ /**
3
+ * Escapes a string value for safe YAML output.
4
+ * Returns a plain scalar when possible; double-quotes only when the value
5
+ * contains special YAML characters or looks ambiguous.
6
+ */
7
+ export declare function escapeYamlValue(value: string): string;
8
+ export declare function formatYamlArray(items: string[]): string;
9
+ export type YamlFieldValue = string | string[];
10
+ export declare function yamlFrontmatter(fields: Record<string, YamlFieldValue>): string;
@@ -0,0 +1,30 @@
1
+ export function stripFrontmatter(content) {
2
+ const match = content.match(/^---\n[\s\S]*?\n---\n/);
3
+ return match ? content.slice(match[0].length) : content;
4
+ }
5
+ /**
6
+ * Escapes a string value for safe YAML output.
7
+ * Returns a plain scalar when possible; double-quotes only when the value
8
+ * contains special YAML characters or looks ambiguous.
9
+ */
10
+ export function escapeYamlValue(value) {
11
+ const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
12
+ if (needsQuoting) {
13
+ const escaped = value
14
+ .replace(/\\/g, "\\\\")
15
+ .replace(/"/g, '\\"')
16
+ .replace(/\n/g, "\\n");
17
+ return `"${escaped}"`;
18
+ }
19
+ return value;
20
+ }
21
+ export function formatYamlArray(items) {
22
+ return `[${items.map(escapeYamlValue).join(", ")}]`;
23
+ }
24
+ export function yamlFrontmatter(fields) {
25
+ const lines = Object.entries(fields).map(([k, v]) => {
26
+ const rendered = Array.isArray(v) ? formatYamlArray(v) : escapeYamlValue(v);
27
+ return `${k}: ${rendered}`;
28
+ });
29
+ return `---\n${lines.join("\n")}\n---\n`;
30
+ }
@@ -0,0 +1,20 @@
1
+ export interface CommandMeta {
2
+ name: string;
3
+ description: string;
4
+ }
5
+ export interface PlatformAdapter {
6
+ id: string;
7
+ displayName: string;
8
+ configDir: string;
9
+ fileExtension: string;
10
+ commandPath(name: string): string;
11
+ skillPath(name: string): string;
12
+ wrapContent(content: string, meta: CommandMeta): string;
13
+ }
14
+ export declare const DEFAULT_PLATFORM = "claude";
15
+ export declare const PLATFORM_IDS: readonly ["claude", "cursor", "windsurf", "cline", "codex", "github-copilot", "continue", "amazon-q", "antigravity", "auggie", "codebuddy", "costrict", "crush", "factory", "gemini", "iflow", "kilocode", "kiro", "opencode", "pi", "qoder", "qwen", "roocode", "trae"];
16
+ export type PlatformId = typeof PLATFORM_IDS[number];
17
+ export declare function isValidPlatform(id: string): id is PlatformId;
18
+ export declare function getAdapter(id: string): PlatformAdapter | undefined;
19
+ export declare function getAdapters(ids: string[]): PlatformAdapter[];
20
+ export declare function getAllAdapters(): PlatformAdapter[];
@@ -0,0 +1,139 @@
1
+ export const DEFAULT_PLATFORM = "claude";
2
+ function stripFrontmatter(content) {
3
+ const match = content.match(/^---\n[\s\S]*?\n---\n/);
4
+ return match ? content.slice(match[0].length) : content;
5
+ }
6
+ function yamlFrontmatter(fields) {
7
+ const lines = Object.entries(fields).map(([k, v]) => `${k}: "${v}"`);
8
+ return `---\n${lines.join("\n")}\n---\n`;
9
+ }
10
+ function makeSkillPath(skillsDir, name) {
11
+ return `${skillsDir}/skills/${name}/SKILL.md`;
12
+ }
13
+ function descOnlyAdapter(id, displayName, skillsDir, pathFn, ext = ".md") {
14
+ return {
15
+ id,
16
+ displayName,
17
+ configDir: pathFn("_").split("/")[0],
18
+ fileExtension: ext,
19
+ commandPath: pathFn,
20
+ skillPath: (name) => makeSkillPath(skillsDir, name),
21
+ wrapContent: (content, meta) => {
22
+ const body = stripFrontmatter(content);
23
+ const fm = yamlFrontmatter({ description: meta.description });
24
+ return fm + body;
25
+ },
26
+ };
27
+ }
28
+ function nameDescAdapter(id, displayName, skillsDir, pathFn, namePrefix, ext = ".md") {
29
+ return {
30
+ id,
31
+ displayName,
32
+ configDir: pathFn("_").split("/")[0],
33
+ fileExtension: ext,
34
+ commandPath: pathFn,
35
+ skillPath: (name) => makeSkillPath(skillsDir, name),
36
+ wrapContent: (content, meta) => {
37
+ const body = stripFrontmatter(content);
38
+ const fm = yamlFrontmatter({
39
+ name: `${namePrefix}${meta.name}`,
40
+ description: meta.description,
41
+ });
42
+ return fm + body;
43
+ },
44
+ };
45
+ }
46
+ function markdownHeaderAdapter(id, displayName, skillsDir, pathFn) {
47
+ return {
48
+ id,
49
+ displayName,
50
+ configDir: pathFn("_").split("/")[0],
51
+ fileExtension: ".md",
52
+ commandPath: pathFn,
53
+ skillPath: (name) => makeSkillPath(skillsDir, name),
54
+ wrapContent: (content, meta) => {
55
+ const body = stripFrontmatter(content);
56
+ return `# scaff-${meta.name}\n\n${meta.description}\n\n${body}`;
57
+ },
58
+ };
59
+ }
60
+ const claude = {
61
+ id: "claude",
62
+ displayName: "Claude Code",
63
+ configDir: ".claude",
64
+ fileExtension: ".md",
65
+ commandPath: (name) => `.claude/commands/scaff/${name}.md`,
66
+ skillPath: (name) => makeSkillPath(".claude", name),
67
+ wrapContent: (content, _meta) => content,
68
+ };
69
+ const cursor = nameDescAdapter("cursor", "Cursor", ".cursor", (name) => `.cursor/commands/scaff-${name}.md`, "/scaff-");
70
+ const windsurf = nameDescAdapter("windsurf", "Windsurf", ".windsurf", (name) => `.windsurf/workflows/scaff-${name}.md`, "scaff-");
71
+ const cline = markdownHeaderAdapter("cline", "Cline", ".cline", (name) => `.clinerules/workflows/scaff-${name}.md`);
72
+ const codex = {
73
+ id: "codex",
74
+ displayName: "Codex",
75
+ configDir: ".codex",
76
+ fileExtension: ".md",
77
+ commandPath: (name) => `.codex/skills/scaff-${name}/SKILL.md`,
78
+ skillPath: (name) => makeSkillPath(".codex", name),
79
+ wrapContent: (content, _meta) => content,
80
+ };
81
+ const githubCopilot = descOnlyAdapter("github-copilot", "GitHub Copilot", ".github", (name) => `.github/prompts/scaff-${name}.prompt.md`, ".prompt.md");
82
+ const continueAdapter = nameDescAdapter("continue", "Continue", ".continue", (name) => `.continue/prompts/scaff-${name}.prompt`, "scaff-", ".prompt");
83
+ const amazonQ = descOnlyAdapter("amazon-q", "Amazon Q Developer", ".amazonq", (name) => `.amazonq/prompts/scaff-${name}.md`);
84
+ const antigravity = descOnlyAdapter("antigravity", "Antigravity", ".agent", (name) => `.agent/workflows/scaff-${name}.md`);
85
+ const auggie = descOnlyAdapter("auggie", "Augment CLI", ".augment", (name) => `.augment/commands/scaff-${name}.md`);
86
+ const codebuddy = nameDescAdapter("codebuddy", "CodeBuddy", ".codebuddy", (name) => `.codebuddy/commands/scaff/${name}.md`, "scaff:");
87
+ const costrict = descOnlyAdapter("costrict", "CoStrict", ".cospec", (name) => `.cospec/openspec/commands/scaff-${name}.md`);
88
+ const crush = nameDescAdapter("crush", "Crush", ".crush", (name) => `.crush/commands/scaff/${name}.md`, "scaff:");
89
+ const factory = descOnlyAdapter("factory", "Factory Droid", ".factory", (name) => `.factory/commands/scaff-${name}.md`);
90
+ const gemini = descOnlyAdapter("gemini", "Gemini CLI", ".gemini", (name) => `.gemini/commands/scaff/${name}.md`);
91
+ const iflow = nameDescAdapter("iflow", "iFlow", ".iflow", (name) => `.iflow/commands/scaff-${name}.md`, "/scaff-");
92
+ const kilocode = {
93
+ id: "kilocode",
94
+ displayName: "Kilo Code",
95
+ configDir: ".kilocode",
96
+ fileExtension: ".md",
97
+ commandPath: (name) => `.kilocode/workflows/scaff-${name}.md`,
98
+ skillPath: (name) => makeSkillPath(".kilocode", name),
99
+ wrapContent: (content, _meta) => stripFrontmatter(content),
100
+ };
101
+ const kiro = descOnlyAdapter("kiro", "Kiro", ".kiro", (name) => `.kiro/prompts/scaff-${name}.prompt.md`, ".prompt.md");
102
+ const opencode = descOnlyAdapter("opencode", "OpenCode", ".opencode", (name) => `.opencode/commands/scaff-${name}.md`);
103
+ const pi = descOnlyAdapter("pi", "Pi", ".pi", (name) => `.pi/prompts/scaff-${name}.md`);
104
+ const qoder = nameDescAdapter("qoder", "Qoder", ".qoder", (name) => `.qoder/commands/scaff/${name}.md`, "scaff:");
105
+ const qwen = descOnlyAdapter("qwen", "Qwen Code", ".qwen", (name) => `.qwen/commands/scaff-${name}.md`);
106
+ const roocode = markdownHeaderAdapter("roocode", "RooCode", ".roo", (name) => `.roo/commands/scaff-${name}.md`);
107
+ const trae = descOnlyAdapter("trae", "Trae", ".trae", (name) => `.trae/commands/scaff-${name}.md`);
108
+ const ALL_ADAPTERS = [
109
+ claude, cursor, windsurf, cline, codex, githubCopilot, continueAdapter,
110
+ amazonQ, antigravity, auggie, codebuddy, costrict, crush, factory,
111
+ gemini, iflow, kilocode, kiro, opencode, pi, qoder, qwen, roocode, trae,
112
+ ];
113
+ const ADAPTER_MAP = new Map(ALL_ADAPTERS.map((a) => [a.id, a]));
114
+ if (ADAPTER_MAP.size !== ALL_ADAPTERS.length)
115
+ throw new Error("duplicate adapter id");
116
+ export const PLATFORM_IDS = [
117
+ "claude", "cursor", "windsurf", "cline", "codex", "github-copilot", "continue",
118
+ "amazon-q", "antigravity", "auggie", "codebuddy", "costrict", "crush", "factory",
119
+ "gemini", "iflow", "kilocode", "kiro", "opencode", "pi", "qoder", "qwen", "roocode", "trae",
120
+ ];
121
+ if (PLATFORM_IDS.length !== ALL_ADAPTERS.length)
122
+ throw new Error("PLATFORM_IDS out of sync");
123
+ export function isValidPlatform(id) {
124
+ return ADAPTER_MAP.has(id);
125
+ }
126
+ export function getAdapter(id) {
127
+ return ADAPTER_MAP.get(id);
128
+ }
129
+ export function getAdapters(ids) {
130
+ return ids.map((id) => {
131
+ const adapter = ADAPTER_MAP.get(id);
132
+ if (!adapter)
133
+ throw new Error(`Unknown platform: ${id}`);
134
+ return adapter;
135
+ });
136
+ }
137
+ export function getAllAdapters() {
138
+ return [...ALL_ADAPTERS];
139
+ }
@@ -0,0 +1 @@
1
+ export declare function selectPlatforms(projectRoot: string): Promise<string[]>;
@@ -0,0 +1,174 @@
1
+ import { createPrompt, useState, useKeypress, useMemo, useRef, usePagination, isUpKey, isDownKey, isSpaceKey, isBackspaceKey, isEnterKey, } from "@inquirer/core";
2
+ import chalk from "chalk";
3
+ import { getAllAdapters, detectInstalledPlatforms, DEFAULT_PLATFORM } from "../platforms/index.js";
4
+ import { t } from "../i18n/index.js";
5
+ import { brand } from "../ui/colors.js";
6
+ const PAGE_SIZE = 15;
7
+ function buildChoices(projectRoot) {
8
+ const detected = new Set(detectInstalledPlatforms(projectRoot));
9
+ const adapters = getAllAdapters();
10
+ const choices = adapters.map((a) => ({
11
+ id: a.id,
12
+ displayName: a.displayName,
13
+ detected: detected.has(a.id),
14
+ }));
15
+ choices.sort((a, b) => {
16
+ if (a.detected !== b.detected)
17
+ return a.detected ? -1 : 1;
18
+ return a.displayName.localeCompare(b.displayName);
19
+ });
20
+ return choices;
21
+ }
22
+ const platformSelect = createPrompt((config, done) => {
23
+ const { projectRoot } = config;
24
+ const allChoices = useRef(null);
25
+ if (allChoices.current === null) {
26
+ allChoices.current = buildChoices(projectRoot);
27
+ }
28
+ const initialSelected = useRef(null);
29
+ if (initialSelected.current === null) {
30
+ const s = new Set();
31
+ s.add(DEFAULT_PLATFORM);
32
+ for (const c of allChoices.current) {
33
+ if (c.detected)
34
+ s.add(c.id);
35
+ }
36
+ initialSelected.current = s;
37
+ }
38
+ const [selected, setSelected] = useState(() => new Set(initialSelected.current));
39
+ const [search, setSearch] = useState("");
40
+ const [cursorIndex, setCursorIndex] = useState(0);
41
+ const [submitted, setSubmitted] = useState(false);
42
+ const [errorMsg, setErrorMsg] = useState("");
43
+ const visibleChoices = useMemo(() => {
44
+ if (!search)
45
+ return allChoices.current;
46
+ const lower = search.toLowerCase();
47
+ return allChoices.current.filter((c) => c.displayName.toLowerCase().includes(lower));
48
+ }, [search]);
49
+ const clampedCursor = useMemo(() => {
50
+ if (visibleChoices.length === 0)
51
+ return 0;
52
+ return Math.min(cursorIndex, visibleChoices.length - 1);
53
+ }, [cursorIndex, visibleChoices.length]);
54
+ useKeypress((event, rl) => {
55
+ if (event.ctrl && event.name === "c") {
56
+ process.exit(0);
57
+ }
58
+ if (submitted)
59
+ return;
60
+ if (isEnterKey(event)) {
61
+ if (selected.size === 0) {
62
+ setErrorMsg(t("select.error.empty"));
63
+ return;
64
+ }
65
+ setSubmitted(true);
66
+ done([...selected]);
67
+ return;
68
+ }
69
+ if (isUpKey(event)) {
70
+ if (clampedCursor > 0) {
71
+ setCursorIndex(clampedCursor - 1);
72
+ }
73
+ return;
74
+ }
75
+ if (isDownKey(event)) {
76
+ if (clampedCursor < visibleChoices.length - 1) {
77
+ setCursorIndex(clampedCursor + 1);
78
+ }
79
+ return;
80
+ }
81
+ if (isSpaceKey(event)) {
82
+ if (visibleChoices.length === 0)
83
+ return;
84
+ const choice = visibleChoices[clampedCursor];
85
+ if (!choice)
86
+ return;
87
+ const next = new Set(selected);
88
+ if (next.has(choice.id)) {
89
+ next.delete(choice.id);
90
+ }
91
+ else {
92
+ next.add(choice.id);
93
+ }
94
+ setSelected(next);
95
+ setErrorMsg("");
96
+ return;
97
+ }
98
+ if (isBackspaceKey(event)) {
99
+ if (search.length > 0) {
100
+ setSearch(search.slice(0, -1));
101
+ setCursorIndex(0);
102
+ }
103
+ else {
104
+ if (selected.size > 0) {
105
+ const arr = [...selected];
106
+ const last = arr[arr.length - 1];
107
+ const next = new Set(selected);
108
+ next.delete(last);
109
+ setSelected(next);
110
+ }
111
+ }
112
+ return;
113
+ }
114
+ if (event.name && event.name.length === 1 && !event.ctrl) {
115
+ setSearch(search + event.name);
116
+ setCursorIndex(0);
117
+ setErrorMsg("");
118
+ }
119
+ });
120
+ if (submitted) {
121
+ const names = allChoices.current
122
+ .filter((c) => selected.has(c.id))
123
+ .map((c) => brand(c.displayName))
124
+ .join(", ");
125
+ return `${chalk.green("✔")} ${config.message} ${names}`;
126
+ }
127
+ const pills = allChoices.current
128
+ .filter((c) => selected.has(c.id))
129
+ .map((c) => brand(`[${c.displayName}]`))
130
+ .join(" ");
131
+ const searchDisplay = search
132
+ ? chalk.yellow(search)
133
+ : chalk.dim(t("select.typeToFilter"));
134
+ const page = usePagination({
135
+ items: visibleChoices,
136
+ active: clampedCursor,
137
+ pageSize: PAGE_SIZE,
138
+ loop: false,
139
+ renderItem({ item, isActive }) {
140
+ const isSelected = selected.has(item.id);
141
+ const icon = isSelected ? brand("●") : "○";
142
+ const arrow = isActive ? brand("›") : " ";
143
+ const label = isActive ? brand(item.displayName) : item.displayName;
144
+ const suffix = item.detected
145
+ ? chalk.dim(` (${t("select.detected")})`)
146
+ : isSelected
147
+ ? chalk.dim(` (${t("select.selected")})`)
148
+ : "";
149
+ return ` ${arrow} ${icon} ${label}${suffix}`;
150
+ },
151
+ });
152
+ const totalPages = Math.ceil(visibleChoices.length / PAGE_SIZE);
153
+ const currentPage = totalPages > 0
154
+ ? Math.floor(clampedCursor / PAGE_SIZE) + 1
155
+ : 0;
156
+ const pageIndicator = totalPages > 1
157
+ ? chalk.dim(` (${currentPage}/${totalPages})`)
158
+ : "";
159
+ const error = errorMsg ? `\n${chalk.red(errorMsg)}` : "";
160
+ const header = `${brand.bold("?")} ${config.message} (${allChoices.current.length} ${t("select.available")})`;
161
+ const selectedLine = ` ${t("select.selectedLabel")} ${pills || chalk.dim(t("select.none"))}`;
162
+ const searchLine = ` ${t("select.searchLabel")} [${searchDisplay}]`;
163
+ const hint = chalk.dim(` ${t("select.hint")}`);
164
+ return [
165
+ `${header}\n${selectedLine}\n${searchLine}\n${hint}\n${page}${pageIndicator}${error}`,
166
+ undefined,
167
+ ];
168
+ });
169
+ export async function selectPlatforms(projectRoot) {
170
+ return platformSelect({
171
+ message: t("select.message"),
172
+ projectRoot,
173
+ });
174
+ }
@@ -0,0 +1,13 @@
1
+ import type { PlatformAdapter } from "../platforms/index.js";
2
+ /**
3
+ * Skill installation path per platform.
4
+ * Skill content is universal (single format across all platforms);
5
+ * platforms differ only in where the SKILL.md file is placed.
6
+ */
7
+ export declare function skillInstallPath(adapter: PlatformAdapter, skillName: string): string;
8
+ /**
9
+ * Produces the final skill file content.
10
+ * Currently a passthrough — reserved for future enrichment
11
+ * (license, compatibility, metadata.author/version/generatedBy).
12
+ */
13
+ export declare function formatSkill(body: string): string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Skill installation path per platform.
3
+ * Skill content is universal (single format across all platforms);
4
+ * platforms differ only in where the SKILL.md file is placed.
5
+ */
6
+ export function skillInstallPath(adapter, skillName) {
7
+ return `${adapter.configDir}/skills/${skillName}/SKILL.md`;
8
+ }
9
+ /**
10
+ * Produces the final skill file content.
11
+ * Currently a passthrough — reserved for future enrichment
12
+ * (license, compatibility, metadata.author/version/generatedBy).
13
+ */
14
+ export function formatSkill(body) {
15
+ return body;
16
+ }
@@ -0,0 +1,18 @@
1
+ import { type PlatformAdapter, type CommandMeta } from "./platforms/index.js";
2
+ export interface ScaffConfig {
3
+ tools: string[];
4
+ subagent: boolean;
5
+ root: string;
6
+ docsDir: string;
7
+ codebaseDir: string;
8
+ force?: boolean;
9
+ }
10
+ export interface TemplateEntry {
11
+ src: string;
12
+ dst: string;
13
+ adapter: PlatformAdapter;
14
+ meta: CommandMeta;
15
+ rawContent: string;
16
+ }
17
+ export declare function resolveTemplates(config: ScaffConfig): TemplateEntry[];
18
+ export declare function renderFile(entry: TemplateEntry, config: ScaffConfig): string;