@orchid-labs/pluxx 0.1.0 → 0.1.3

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 (110) hide show
  1. package/README.md +110 -515
  2. package/bin/pluxx.js +19 -28
  3. package/dist/agents.d.ts +16 -0
  4. package/dist/agents.d.ts.map +1 -0
  5. package/dist/cli/agent.d.ts +69 -0
  6. package/dist/cli/agent.d.ts.map +1 -1
  7. package/dist/cli/doctor.d.ts +3 -0
  8. package/dist/cli/doctor.d.ts.map +1 -1
  9. package/dist/cli/entry.d.ts +2 -0
  10. package/dist/cli/entry.d.ts.map +1 -0
  11. package/dist/cli/eval.d.ts +22 -0
  12. package/dist/cli/eval.d.ts.map +1 -0
  13. package/dist/cli/index.d.ts +26 -3
  14. package/dist/cli/index.d.ts.map +1 -1
  15. package/dist/cli/index.js +21810 -0
  16. package/dist/cli/init-from-mcp.d.ts +34 -3
  17. package/dist/cli/init-from-mcp.d.ts.map +1 -1
  18. package/dist/cli/install.d.ts +3 -0
  19. package/dist/cli/install.d.ts.map +1 -1
  20. package/dist/cli/lint.d.ts +7 -1
  21. package/dist/cli/lint.d.ts.map +1 -1
  22. package/dist/cli/mcp-proxy.d.ts +10 -0
  23. package/dist/cli/mcp-proxy.d.ts.map +1 -0
  24. package/dist/cli/migrate.d.ts.map +1 -1
  25. package/dist/cli/primitive-summary.d.ts +14 -0
  26. package/dist/cli/primitive-summary.d.ts.map +1 -0
  27. package/dist/cli/prompt.d.ts +1 -1
  28. package/dist/cli/publish.d.ts +6 -1
  29. package/dist/cli/publish.d.ts.map +1 -1
  30. package/dist/cli/sync-from-mcp.d.ts.map +1 -1
  31. package/dist/cli/test.d.ts +2 -0
  32. package/dist/cli/test.d.ts.map +1 -1
  33. package/dist/cli/verify-install.d.ts +25 -0
  34. package/dist/cli/verify-install.d.ts.map +1 -0
  35. package/dist/commands.d.ts +10 -0
  36. package/dist/commands.d.ts.map +1 -0
  37. package/dist/compiler-intent.d.ts +165 -0
  38. package/dist/compiler-intent.d.ts.map +1 -0
  39. package/dist/config/load.d.ts.map +1 -1
  40. package/dist/delegation.d.ts +11 -0
  41. package/dist/delegation.d.ts.map +1 -0
  42. package/dist/generators/amp/index.d.ts.map +1 -1
  43. package/dist/generators/base.d.ts +5 -0
  44. package/dist/generators/base.d.ts.map +1 -1
  45. package/dist/generators/claude-code/index.d.ts +2 -0
  46. package/dist/generators/claude-code/index.d.ts.map +1 -1
  47. package/dist/generators/cline/index.d.ts.map +1 -1
  48. package/dist/generators/codex/index.d.ts +5 -0
  49. package/dist/generators/codex/index.d.ts.map +1 -1
  50. package/dist/generators/cursor/index.d.ts +1 -0
  51. package/dist/generators/cursor/index.d.ts.map +1 -1
  52. package/dist/generators/gemini-cli/index.d.ts.map +1 -1
  53. package/dist/generators/github-copilot/index.d.ts.map +1 -1
  54. package/dist/generators/opencode/index.d.ts +1 -0
  55. package/dist/generators/opencode/index.d.ts.map +1 -1
  56. package/dist/generators/openhands/index.d.ts.map +1 -1
  57. package/dist/generators/roo-code/index.d.ts.map +1 -1
  58. package/dist/generators/shared/claude-family.d.ts.map +1 -1
  59. package/dist/generators/warp/index.d.ts.map +1 -1
  60. package/dist/index.d.ts +4 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +5464 -548
  63. package/dist/mcp/introspect.d.ts +43 -1
  64. package/dist/mcp/introspect.d.ts.map +1 -1
  65. package/dist/permissions.d.ts.map +1 -1
  66. package/dist/schema.d.ts +91 -42
  67. package/dist/schema.d.ts.map +1 -1
  68. package/dist/text-files.d.ts +5 -0
  69. package/dist/text-files.d.ts.map +1 -0
  70. package/dist/validation/platform-rules.d.ts +35 -1
  71. package/dist/validation/platform-rules.d.ts.map +1 -1
  72. package/package.json +16 -14
  73. package/src/cli/agent.ts +0 -1030
  74. package/src/cli/dev.ts +0 -112
  75. package/src/cli/doctor.ts +0 -588
  76. package/src/cli/index.ts +0 -2414
  77. package/src/cli/init-from-mcp.ts +0 -1611
  78. package/src/cli/install.ts +0 -698
  79. package/src/cli/lint.ts +0 -1219
  80. package/src/cli/migrate.ts +0 -614
  81. package/src/cli/prompt.ts +0 -82
  82. package/src/cli/publish.ts +0 -401
  83. package/src/cli/runtime.ts +0 -86
  84. package/src/cli/sync-from-mcp.ts +0 -563
  85. package/src/cli/test.ts +0 -134
  86. package/src/compatibility/matrix.ts +0 -149
  87. package/src/config/define.ts +0 -20
  88. package/src/config/load.ts +0 -74
  89. package/src/generators/amp/index.ts +0 -63
  90. package/src/generators/base.ts +0 -188
  91. package/src/generators/claude-code/index.ts +0 -29
  92. package/src/generators/cline/index.ts +0 -35
  93. package/src/generators/codex/index.ts +0 -120
  94. package/src/generators/cursor/index.ts +0 -158
  95. package/src/generators/gemini-cli/index.ts +0 -83
  96. package/src/generators/github-copilot/index.ts +0 -32
  97. package/src/generators/hooks-warning.ts +0 -51
  98. package/src/generators/index.ts +0 -71
  99. package/src/generators/opencode/index.ts +0 -526
  100. package/src/generators/openhands/index.ts +0 -32
  101. package/src/generators/roo-code/index.ts +0 -35
  102. package/src/generators/shared/claude-family.ts +0 -215
  103. package/src/generators/warp/index.ts +0 -32
  104. package/src/hook-events.ts +0 -33
  105. package/src/index.ts +0 -23
  106. package/src/mcp/introspect.ts +0 -834
  107. package/src/permissions.ts +0 -258
  108. package/src/schema.ts +0 -312
  109. package/src/user-config.ts +0 -177
  110. package/src/validation/platform-rules.ts +0 -565
@@ -1,258 +0,0 @@
1
- export const PERMISSION_SELECTOR_KINDS = ['Bash', 'Edit', 'Read', 'MCP', 'Skill'] as const
2
-
3
- export type PermissionRuleKind = typeof PERMISSION_SELECTOR_KINDS[number]
4
- export type PermissionAction = 'allow' | 'ask' | 'deny'
5
-
6
- export interface ParsedPermissionRule {
7
- action: PermissionAction
8
- raw: string
9
- kind: PermissionRuleKind
10
- pattern: string
11
- }
12
-
13
- export interface OpenCodePermissionMap {
14
- [tool: string]: PermissionAction
15
- }
16
-
17
- const PERMISSION_RULE_REGEX = new RegExp(
18
- `^(${PERMISSION_SELECTOR_KINDS.join('|')})\\((.+)\\)$`,
19
- )
20
-
21
- export function parsePermissionRule(raw: string): Omit<ParsedPermissionRule, 'action'> | null {
22
- const trimmed = raw.trim()
23
- const match = trimmed.match(PERMISSION_RULE_REGEX)
24
- if (!match) return null
25
-
26
- const kind = match[1] as PermissionRuleKind
27
- const pattern = match[2].trim()
28
- if (!pattern) return null
29
-
30
- return {
31
- raw: trimmed,
32
- kind,
33
- pattern,
34
- }
35
- }
36
-
37
- export function collectPermissionRules(
38
- permissions: Partial<Record<PermissionAction, string[]>> | undefined,
39
- ): ParsedPermissionRule[] {
40
- if (!permissions) return []
41
-
42
- const actions: PermissionAction[] = ['allow', 'ask', 'deny']
43
- const rules: ParsedPermissionRule[] = []
44
-
45
- for (const action of actions) {
46
- for (const raw of permissions[action] ?? []) {
47
- const parsed = parsePermissionRule(raw)
48
- if (!parsed) continue
49
- rules.push({
50
- ...parsed,
51
- action,
52
- })
53
- }
54
- }
55
-
56
- return rules
57
- }
58
-
59
- const ACTION_PRIORITY: Record<PermissionAction, number> = {
60
- allow: 0,
61
- ask: 1,
62
- deny: 2,
63
- }
64
-
65
- function mergeAction(current: PermissionAction | undefined, next: PermissionAction): PermissionAction {
66
- if (!current) return next
67
- return ACTION_PRIORITY[next] > ACTION_PRIORITY[current] ? next : current
68
- }
69
-
70
- export function permissionRulesNeedToolLevelDowngrade(
71
- permissions: Partial<Record<PermissionAction, string[]>> | undefined,
72
- ): boolean {
73
- return collectPermissionRules(permissions).some((rule) => rule.pattern !== '*')
74
- }
75
-
76
- export function buildOpenCodePermissionMap(
77
- permissions: Partial<Record<PermissionAction, string[]>> | undefined,
78
- ): OpenCodePermissionMap {
79
- const rules = collectPermissionRules(permissions)
80
- const output: OpenCodePermissionMap = {}
81
-
82
- const toolAliases: Record<PermissionRuleKind, string[]> = {
83
- Bash: ['bash', 'shell'],
84
- Edit: ['edit', 'write'],
85
- Read: ['read'],
86
- MCP: ['mcp'],
87
- Skill: ['skill'],
88
- }
89
-
90
- for (const rule of rules) {
91
- for (const tool of toolAliases[rule.kind]) {
92
- output[tool] = mergeAction(output[tool], rule.action)
93
- }
94
- }
95
-
96
- return output
97
- }
98
-
99
- export function buildGeneratedPermissionHookScript(
100
- permissions: Partial<Record<PermissionAction, string[]>> | undefined,
101
- ): string | null {
102
- const rules = collectPermissionRules(permissions)
103
- if (rules.length === 0) return null
104
-
105
- return `#!/usr/bin/env node
106
- const RULES = ${JSON.stringify(rules, null, 2)};
107
- const ACTION_PRIORITY = { allow: 0, ask: 1, deny: 2 };
108
-
109
- function wildcardToRegExp(pattern) {
110
- const escaped = pattern
111
- .replace(/[.+^$(){}|[\\]\\\\]/g, "\\\\$&")
112
- .replace(/\\*/g, ".*");
113
- return new RegExp("^" + escaped + "$", "i");
114
- }
115
-
116
- function matchesPattern(pattern, value) {
117
- if (!value) return false;
118
- return wildcardToRegExp(pattern).test(String(value));
119
- }
120
-
121
- function firstValue(...values) {
122
- return values.find((value) => typeof value === "string" && value.trim() !== "");
123
- }
124
-
125
- function parseJsonInput() {
126
- const chunks = [];
127
- process.stdin.setEncoding("utf8");
128
- return new Promise((resolve, reject) => {
129
- process.stdin.on("data", (chunk) => chunks.push(chunk));
130
- process.stdin.on("end", () => {
131
- const raw = chunks.join("").trim();
132
- if (!raw) return resolve({});
133
- try {
134
- resolve(JSON.parse(raw));
135
- } catch (error) {
136
- reject(error);
137
- }
138
- });
139
- process.stdin.on("error", reject);
140
- });
141
- }
142
-
143
- function parseMcpName(raw) {
144
- if (!raw) return undefined;
145
- if (raw.startsWith("mcp__")) {
146
- const match = raw.match(/^mcp__([^_]+)__(.+)$/);
147
- if (match) return match[1] + "." + match[2].replace(/__/g, ".");
148
- }
149
- return raw;
150
- }
151
-
152
- function inferCandidates(mode, event) {
153
- const toolName = firstValue(event.tool_name, event.toolName, event.tool, event.matcher);
154
- const input = event.tool_input ?? event.toolInput ?? event.input ?? {};
155
- const candidates = [];
156
-
157
- const filePath = firstValue(
158
- event.file_path,
159
- input.file_path,
160
- input.path,
161
- input.filePath,
162
- input.target_file,
163
- input.targetFile
164
- );
165
- const command = firstValue(event.command, input.command, input.cmd, input.shell_command);
166
- const skillName = firstValue(input.name, input.skill_name, input.skillName);
167
- const mcpName = parseMcpName(firstValue(event.tool_name, input.tool_name, input.name));
168
-
169
- if (mode === "cursor-shell") {
170
- if (command) candidates.push({ kind: "Bash", value: command });
171
- return candidates;
172
- }
173
-
174
- if (mode === "cursor-read") {
175
- if (filePath) candidates.push({ kind: "Read", value: filePath });
176
- return candidates;
177
- }
178
-
179
- if (mode === "cursor-mcp") {
180
- if (mcpName) candidates.push({ kind: "MCP", value: mcpName });
181
- return candidates;
182
- }
183
-
184
- const normalizedTool = String(toolName || "").toLowerCase();
185
-
186
- if (command && /(bash|shell|terminal|command)/i.test(normalizedTool)) {
187
- candidates.push({ kind: "Bash", value: command });
188
- }
189
-
190
- if (filePath && /(edit|write|applypatch|replace|multiedit)/i.test(normalizedTool)) {
191
- candidates.push({ kind: "Edit", value: filePath });
192
- }
193
-
194
- if (filePath && /(read|view|open)/i.test(normalizedTool)) {
195
- candidates.push({ kind: "Read", value: filePath });
196
- }
197
-
198
- if ((normalizedTool.includes("mcp") || String(toolName || "").startsWith("mcp__")) && mcpName) {
199
- candidates.push({ kind: "MCP", value: mcpName });
200
- }
201
-
202
- if (skillName && /skill/i.test(normalizedTool)) {
203
- candidates.push({ kind: "Skill", value: skillName });
204
- }
205
-
206
- return candidates;
207
- }
208
-
209
- function evaluate(mode, event) {
210
- const candidates = inferCandidates(mode, event);
211
- let selected;
212
-
213
- for (const rule of RULES) {
214
- for (const candidate of candidates) {
215
- if (candidate.kind !== rule.kind) continue;
216
- if (!matchesPattern(rule.pattern, candidate.value)) continue;
217
-
218
- if (!selected || ACTION_PRIORITY[rule.action] > ACTION_PRIORITY[selected.action]) {
219
- selected = { rule, action: rule.action, candidate };
220
- }
221
- }
222
- }
223
-
224
- return selected;
225
- }
226
-
227
- function cursorResponse(match) {
228
- if (!match) return {};
229
- return {
230
- permission: match.action,
231
- user_message: "Pluxx permissions matched " + match.rule.raw,
232
- };
233
- }
234
-
235
- function claudeResponse(match) {
236
- if (!match) return {};
237
- return {
238
- hookSpecificOutput: {
239
- permissionDecision: match.action,
240
- permissionDecisionReason: "Pluxx permissions matched " + match.rule.raw,
241
- },
242
- };
243
- }
244
-
245
- async function main() {
246
- const mode = process.argv[2];
247
- const event = await parseJsonInput();
248
- const match = evaluate(mode, event);
249
- const payload = mode === "claude-pretool" ? claudeResponse(match) : cursorResponse(match);
250
- process.stdout.write(JSON.stringify(payload));
251
- }
252
-
253
- main().catch((error) => {
254
- process.stderr.write(String(error instanceof Error ? error.message : error));
255
- process.exit(1);
256
- });
257
- `
258
- }
package/src/schema.ts DELETED
@@ -1,312 +0,0 @@
1
- import { z } from 'zod'
2
- import { PERMISSION_SELECTOR_KINDS, parsePermissionRule } from './permissions'
3
-
4
- // ── MCP Server Auth ──────────────────────────────────────────────
5
-
6
- const McpAuthNoneSchema = z.object({
7
- type: z.literal('none'),
8
- }).strict()
9
-
10
- const McpAuthBearerSchema = z.object({
11
- type: z.literal('bearer'),
12
- envVar: z.string(),
13
- headerName: z.string().default('Authorization'),
14
- headerTemplate: z.string().default('Bearer ${value}'),
15
- }).strict()
16
-
17
- const McpAuthHeaderSchema = z.object({
18
- type: z.literal('header'),
19
- envVar: z.string(),
20
- headerName: z.string(),
21
- headerTemplate: z.string().default('Bearer ${value}'),
22
- }).strict()
23
-
24
- const McpAuthPlatformSchema = z.object({
25
- type: z.literal('platform'),
26
- mode: z.enum(['oauth']).default('oauth'),
27
- }).strict()
28
-
29
- export const McpAuthSchema = z.preprocess(
30
- (value) => {
31
- if (value && typeof value === 'object' && !('type' in (value as Record<string, unknown>))) {
32
- return { ...(value as Record<string, unknown>), type: 'bearer' }
33
- }
34
- return value
35
- },
36
- z.discriminatedUnion('type', [McpAuthNoneSchema, McpAuthBearerSchema, McpAuthHeaderSchema, McpAuthPlatformSchema])
37
- )
38
-
39
- const McpServerHttpSchema = z.object({
40
- transport: z.literal('http'),
41
- url: z.string().url(),
42
- command: z.never().optional(),
43
- args: z.array(z.string()).optional(),
44
- env: z.record(z.string()).optional(),
45
- auth: McpAuthSchema.optional(),
46
- }).strict()
47
-
48
- const McpServerSseSchema = z.object({
49
- transport: z.literal('sse'),
50
- url: z.string().url(),
51
- command: z.never().optional(),
52
- args: z.array(z.string()).optional(),
53
- env: z.record(z.string()).optional(),
54
- auth: McpAuthSchema.optional(),
55
- }).strict()
56
-
57
- const McpServerStdioSchema = z.object({
58
- transport: z.literal('stdio'),
59
- command: z.string(),
60
- url: z.never().optional(),
61
- args: z.array(z.string()).optional(),
62
- env: z.record(z.string()).optional(),
63
- auth: McpAuthSchema.optional(),
64
- }).strict()
65
-
66
- export const McpServerSchema = z.preprocess(
67
- (value) => {
68
- if (value && typeof value === 'object' && !('transport' in (value as Record<string, unknown>))) {
69
- return { ...(value as Record<string, unknown>), transport: 'http' }
70
- }
71
- return value
72
- },
73
- z.discriminatedUnion('transport', [McpServerHttpSchema, McpServerSseSchema, McpServerStdioSchema])
74
- )
75
-
76
- // ── Hooks ────────────────────────────────────────────────────────
77
-
78
- export const HookEntrySchema = z.object({
79
- type: z.enum(['command', 'prompt']).default('command'),
80
- command: z.string().optional(),
81
- prompt: z.string().optional(),
82
- model: z.string().optional(),
83
- timeout: z.number().optional(),
84
- matcher: z.union([z.string(), z.record(z.unknown())]).optional(),
85
- failClosed: z.boolean().optional(),
86
- loop_limit: z.number().nullable().optional(),
87
- }).superRefine((entry, ctx) => {
88
- if (entry.type === 'command' && !entry.command) {
89
- ctx.addIssue({
90
- code: z.ZodIssueCode.custom,
91
- path: ['command'],
92
- message: 'Command hooks require a command.',
93
- })
94
- }
95
-
96
- if (entry.type === 'prompt' && !entry.prompt) {
97
- ctx.addIssue({
98
- code: z.ZodIssueCode.custom,
99
- path: ['prompt'],
100
- message: 'Prompt hooks require a prompt.',
101
- })
102
- }
103
- })
104
-
105
- export const HooksSchema = z.object({
106
- sessionStart: z.array(HookEntrySchema).optional(),
107
- sessionEnd: z.array(HookEntrySchema).optional(),
108
- preToolUse: z.array(HookEntrySchema).optional(),
109
- postToolUse: z.array(HookEntrySchema).optional(),
110
- postToolUseFailure: z.array(HookEntrySchema).optional(),
111
- subagentStart: z.array(HookEntrySchema).optional(),
112
- subagentStop: z.array(HookEntrySchema).optional(),
113
- beforeShellExecution: z.array(HookEntrySchema).optional(),
114
- afterShellExecution: z.array(HookEntrySchema).optional(),
115
- beforeMCPExecution: z.array(HookEntrySchema).optional(),
116
- afterMCPExecution: z.array(HookEntrySchema).optional(),
117
- afterFileEdit: z.array(HookEntrySchema).optional(),
118
- beforeReadFile: z.array(HookEntrySchema).optional(),
119
- beforeSubmitPrompt: z.array(HookEntrySchema).optional(),
120
- preCompact: z.array(HookEntrySchema).optional(),
121
- stop: z.array(HookEntrySchema).optional(),
122
- afterAgentResponse: z.array(HookEntrySchema).optional(),
123
- afterAgentThought: z.array(HookEntrySchema).optional(),
124
- beforeTabFileRead: z.array(HookEntrySchema).optional(),
125
- afterTabFileEdit: z.array(HookEntrySchema).optional(),
126
- }).catchall(z.array(HookEntrySchema))
127
-
128
- // ── Brand / Interface ────────────────────────────────────────────
129
-
130
- export const BrandSchema = z.object({
131
- displayName: z.string(),
132
- shortDescription: z.string().optional(),
133
- longDescription: z.string().optional(),
134
- category: z.string().default('Productivity'),
135
- color: z.string().optional(),
136
- icon: z.string().optional(),
137
- logo: z.string().optional(),
138
- screenshots: z.array(z.string()).optional(),
139
- defaultPrompts: z.array(z.string()).optional(),
140
- websiteURL: z.string().url().optional(),
141
- privacyPolicyURL: z.string().url().optional(),
142
- termsOfServiceURL: z.string().url().optional(),
143
- })
144
-
145
- // ── Targets / User Config ───────────────────────────────────────
146
-
147
- export const TargetPlatform = z.enum([
148
- 'claude-code',
149
- 'cursor',
150
- 'codex',
151
- 'opencode',
152
- 'github-copilot',
153
- 'openhands',
154
- 'warp',
155
- 'gemini-cli',
156
- 'roo-code',
157
- 'cline',
158
- 'amp',
159
- ])
160
- export type TargetPlatform = z.infer<typeof TargetPlatform>
161
-
162
- const UserConfigValueSchema = z.union([z.string(), z.number(), z.boolean()])
163
-
164
- export const UserConfigEntrySchema = z.object({
165
- key: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Use lowercase kebab-case for user config keys'),
166
- title: z.string(),
167
- description: z.string(),
168
- type: z.enum(['secret', 'string', 'number', 'boolean']).default('string'),
169
- required: z.boolean().default(true),
170
- envVar: z.string().optional(),
171
- defaultValue: UserConfigValueSchema.optional(),
172
- placeholder: z.string().optional(),
173
- targets: z.array(TargetPlatform).optional(),
174
- }).superRefine((entry, ctx) => {
175
- if (entry.type === 'secret' && entry.defaultValue !== undefined) {
176
- ctx.addIssue({
177
- code: z.ZodIssueCode.custom,
178
- path: ['defaultValue'],
179
- message: 'Secret userConfig entries should not define a defaultValue.',
180
- })
181
- }
182
-
183
- if (entry.type === 'number' && entry.defaultValue !== undefined && typeof entry.defaultValue !== 'number') {
184
- ctx.addIssue({
185
- code: z.ZodIssueCode.custom,
186
- path: ['defaultValue'],
187
- message: 'Number userConfig entries require a numeric defaultValue.',
188
- })
189
- }
190
-
191
- if (entry.type === 'boolean' && entry.defaultValue !== undefined && typeof entry.defaultValue !== 'boolean') {
192
- ctx.addIssue({
193
- code: z.ZodIssueCode.custom,
194
- path: ['defaultValue'],
195
- message: 'Boolean userConfig entries require a boolean defaultValue.',
196
- })
197
- }
198
- })
199
-
200
- export const UserConfigSchema = z.array(UserConfigEntrySchema)
201
-
202
- // ── Permissions ─────────────────────────────────────────────────
203
-
204
- export const PermissionRuleSchema = z.string().superRefine((value, ctx) => {
205
- if (!parsePermissionRule(value)) {
206
- ctx.addIssue({
207
- code: z.ZodIssueCode.custom,
208
- message: `Permission rules must use the canonical DSL: ${PERMISSION_SELECTOR_KINDS.map(kind => `${kind}(...)`).join(', ')}`,
209
- })
210
- }
211
- })
212
-
213
- export const PermissionsSchema = z.object({
214
- allow: z.array(PermissionRuleSchema).optional(),
215
- ask: z.array(PermissionRuleSchema).optional(),
216
- deny: z.array(PermissionRuleSchema).optional(),
217
- }).refine(
218
- permissions => (permissions.allow?.length ?? 0) + (permissions.ask?.length ?? 0) + (permissions.deny?.length ?? 0) > 0,
219
- {
220
- message: 'Permissions must declare at least one allow/ask/deny rule.',
221
- },
222
- )
223
-
224
- // ── Platform Overrides ───────────────────────────────────────────
225
-
226
- export const ClaudeCodeOverridesSchema = z.object({
227
- skillDefaults: z.record(z.unknown()).optional(),
228
- mcpAuth: z.enum(['inline', 'platform']).optional(),
229
- }).catchall(z.unknown())
230
-
231
- export const CursorOverridesSchema = z.object({
232
- mcpAuth: z.enum(['inline', 'platform']).optional(),
233
- rules: z.array(z.object({
234
- description: z.string(),
235
- globs: z.union([z.string(), z.array(z.string())]).optional(),
236
- alwaysApply: z.boolean().optional(),
237
- content: z.string().optional(),
238
- })).optional(),
239
- }).catchall(z.unknown())
240
-
241
- export const CodexOverridesSchema = z.object({
242
- interface: z.record(z.unknown()).optional(),
243
- }).catchall(z.unknown())
244
-
245
- export const OpenCodeOverridesSchema = z.object({
246
- npmPackage: z.string().optional(),
247
- }).catchall(z.unknown())
248
-
249
- export const PlatformOverridesSchema = z.object({
250
- 'claude-code': ClaudeCodeOverridesSchema.optional(),
251
- cursor: CursorOverridesSchema.optional(),
252
- codex: CodexOverridesSchema.optional(),
253
- opencode: OpenCodeOverridesSchema.optional(),
254
- })
255
-
256
- // ── Main Plugin Config ───────────────────────────────────────────
257
-
258
- export const PluginConfigSchema = z.object({
259
- // Identity
260
- name: z.string().regex(/^[a-z0-9-]+$/, 'Must be lowercase with hyphens only'),
261
- version: z.string().default('0.1.0'),
262
- description: z.string(),
263
- author: z.object({
264
- name: z.string(),
265
- url: z.string().url().optional(),
266
- email: z.string().email().optional(),
267
- }),
268
- repository: z.string().optional(),
269
- license: z.string().default('MIT'),
270
- keywords: z.array(z.string()).optional(),
271
-
272
- // Brand
273
- brand: BrandSchema.optional(),
274
- userConfig: UserConfigSchema.optional(),
275
- permissions: PermissionsSchema.optional(),
276
-
277
- // Plugin components (paths relative to config file)
278
- skills: z.string().default('./skills/'),
279
- commands: z.string().optional(),
280
- agents: z.string().optional(),
281
- instructions: z.string().optional(),
282
-
283
- // MCP servers
284
- mcp: z.record(McpServerSchema).optional(),
285
-
286
- // Hooks
287
- hooks: HooksSchema.optional(),
288
-
289
- // Scripts (copied to all targets)
290
- scripts: z.string().optional(),
291
-
292
- // Assets (copied to all targets)
293
- assets: z.string().optional(),
294
-
295
- // Platform-specific overrides
296
- platforms: PlatformOverridesSchema.optional(),
297
-
298
- // Which platforms to generate
299
- targets: z.array(TargetPlatform).default(['claude-code', 'cursor', 'codex', 'opencode']),
300
-
301
- // Output directory
302
- outDir: z.string().default('./dist'),
303
- })
304
-
305
- export type PluginConfig = z.infer<typeof PluginConfigSchema>
306
- export type McpServer = z.infer<typeof McpServerSchema>
307
- export type McpAuth = z.infer<typeof McpAuthSchema>
308
- export type HookEntry = z.infer<typeof HookEntrySchema>
309
- export type Brand = z.infer<typeof BrandSchema>
310
- export type UserConfigEntry = z.infer<typeof UserConfigEntrySchema>
311
- export type PermissionRule = z.infer<typeof PermissionRuleSchema>
312
- export type Permissions = z.infer<typeof PermissionsSchema>