@orchid-labs/pluxx 0.1.1 → 0.1.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.
Files changed (103) hide show
  1. package/README.md +25 -8
  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 +62 -0
  6. package/dist/cli/agent.d.ts.map +1 -1
  7. package/dist/cli/doctor.d.ts +2 -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/index.d.ts +7 -1
  12. package/dist/cli/index.d.ts.map +1 -1
  13. package/dist/cli/index.js +21810 -0
  14. package/dist/cli/init-from-mcp.d.ts +17 -1
  15. package/dist/cli/init-from-mcp.d.ts.map +1 -1
  16. package/dist/cli/install.d.ts +1 -0
  17. package/dist/cli/install.d.ts.map +1 -1
  18. package/dist/cli/lint.d.ts +3 -1
  19. package/dist/cli/lint.d.ts.map +1 -1
  20. package/dist/cli/mcp-proxy.d.ts.map +1 -1
  21. package/dist/cli/migrate.d.ts.map +1 -1
  22. package/dist/cli/primitive-summary.d.ts +14 -0
  23. package/dist/cli/primitive-summary.d.ts.map +1 -0
  24. package/dist/cli/prompt.d.ts +1 -1
  25. package/dist/cli/publish.d.ts +6 -1
  26. package/dist/cli/publish.d.ts.map +1 -1
  27. package/dist/cli/sync-from-mcp.d.ts.map +1 -1
  28. package/dist/cli/verify-install.d.ts +25 -0
  29. package/dist/cli/verify-install.d.ts.map +1 -0
  30. package/dist/commands.d.ts +10 -0
  31. package/dist/commands.d.ts.map +1 -0
  32. package/dist/compiler-intent.d.ts +165 -0
  33. package/dist/compiler-intent.d.ts.map +1 -0
  34. package/dist/config/load.d.ts.map +1 -1
  35. package/dist/delegation.d.ts +11 -0
  36. package/dist/delegation.d.ts.map +1 -0
  37. package/dist/generators/amp/index.d.ts.map +1 -1
  38. package/dist/generators/base.d.ts +5 -0
  39. package/dist/generators/base.d.ts.map +1 -1
  40. package/dist/generators/claude-code/index.d.ts.map +1 -1
  41. package/dist/generators/cline/index.d.ts.map +1 -1
  42. package/dist/generators/codex/index.d.ts +4 -0
  43. package/dist/generators/codex/index.d.ts.map +1 -1
  44. package/dist/generators/cursor/index.d.ts +1 -0
  45. package/dist/generators/cursor/index.d.ts.map +1 -1
  46. package/dist/generators/gemini-cli/index.d.ts.map +1 -1
  47. package/dist/generators/github-copilot/index.d.ts.map +1 -1
  48. package/dist/generators/opencode/index.d.ts +1 -0
  49. package/dist/generators/opencode/index.d.ts.map +1 -1
  50. package/dist/generators/openhands/index.d.ts.map +1 -1
  51. package/dist/generators/roo-code/index.d.ts.map +1 -1
  52. package/dist/generators/shared/claude-family.d.ts.map +1 -1
  53. package/dist/generators/warp/index.d.ts.map +1 -1
  54. package/dist/index.d.ts +4 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +5371 -553
  57. package/dist/schema.d.ts +91 -42
  58. package/dist/schema.d.ts.map +1 -1
  59. package/dist/text-files.d.ts +5 -0
  60. package/dist/text-files.d.ts.map +1 -0
  61. package/dist/validation/platform-rules.d.ts +15 -1
  62. package/dist/validation/platform-rules.d.ts.map +1 -1
  63. package/package.json +15 -13
  64. package/src/cli/agent.ts +0 -1455
  65. package/src/cli/dev.ts +0 -112
  66. package/src/cli/doctor.ts +0 -987
  67. package/src/cli/eval.ts +0 -470
  68. package/src/cli/index.ts +0 -2933
  69. package/src/cli/init-from-mcp.ts +0 -2115
  70. package/src/cli/install.ts +0 -860
  71. package/src/cli/lint.ts +0 -1249
  72. package/src/cli/mcp-proxy.ts +0 -322
  73. package/src/cli/migrate.ts +0 -867
  74. package/src/cli/prompt.ts +0 -82
  75. package/src/cli/publish.ts +0 -401
  76. package/src/cli/runtime.ts +0 -86
  77. package/src/cli/sync-from-mcp.ts +0 -586
  78. package/src/cli/test.ts +0 -142
  79. package/src/compatibility/matrix.ts +0 -149
  80. package/src/config/define.ts +0 -20
  81. package/src/config/load.ts +0 -74
  82. package/src/generators/amp/index.ts +0 -63
  83. package/src/generators/base.ts +0 -188
  84. package/src/generators/claude-code/index.ts +0 -172
  85. package/src/generators/cline/index.ts +0 -35
  86. package/src/generators/codex/index.ts +0 -143
  87. package/src/generators/cursor/index.ts +0 -158
  88. package/src/generators/gemini-cli/index.ts +0 -83
  89. package/src/generators/github-copilot/index.ts +0 -32
  90. package/src/generators/hooks-warning.ts +0 -51
  91. package/src/generators/index.ts +0 -71
  92. package/src/generators/opencode/index.ts +0 -526
  93. package/src/generators/openhands/index.ts +0 -32
  94. package/src/generators/roo-code/index.ts +0 -35
  95. package/src/generators/shared/claude-family.ts +0 -215
  96. package/src/generators/warp/index.ts +0 -32
  97. package/src/hook-events.ts +0 -33
  98. package/src/index.ts +0 -34
  99. package/src/mcp/introspect.ts +0 -1107
  100. package/src/permissions.ts +0 -260
  101. package/src/schema.ts +0 -312
  102. package/src/user-config.ts +0 -177
  103. package/src/validation/platform-rules.ts +0 -686
@@ -1,260 +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
- // OpenCode's native permission surface is tool-level and does not expose
88
- // a dedicated skill permission key.
89
- Skill: [],
90
- }
91
-
92
- for (const rule of rules) {
93
- for (const tool of toolAliases[rule.kind]) {
94
- output[tool] = mergeAction(output[tool], rule.action)
95
- }
96
- }
97
-
98
- return output
99
- }
100
-
101
- export function buildGeneratedPermissionHookScript(
102
- permissions: Partial<Record<PermissionAction, string[]>> | undefined,
103
- ): string | null {
104
- const rules = collectPermissionRules(permissions)
105
- if (rules.length === 0) return null
106
-
107
- return `#!/usr/bin/env node
108
- const RULES = ${JSON.stringify(rules, null, 2)};
109
- const ACTION_PRIORITY = { allow: 0, ask: 1, deny: 2 };
110
-
111
- function wildcardToRegExp(pattern) {
112
- const escaped = pattern
113
- .replace(/[.+^$(){}|[\\]\\\\]/g, "\\\\$&")
114
- .replace(/\\*/g, ".*");
115
- return new RegExp("^" + escaped + "$", "i");
116
- }
117
-
118
- function matchesPattern(pattern, value) {
119
- if (!value) return false;
120
- return wildcardToRegExp(pattern).test(String(value));
121
- }
122
-
123
- function firstValue(...values) {
124
- return values.find((value) => typeof value === "string" && value.trim() !== "");
125
- }
126
-
127
- function parseJsonInput() {
128
- const chunks = [];
129
- process.stdin.setEncoding("utf8");
130
- return new Promise((resolve, reject) => {
131
- process.stdin.on("data", (chunk) => chunks.push(chunk));
132
- process.stdin.on("end", () => {
133
- const raw = chunks.join("").trim();
134
- if (!raw) return resolve({});
135
- try {
136
- resolve(JSON.parse(raw));
137
- } catch (error) {
138
- reject(error);
139
- }
140
- });
141
- process.stdin.on("error", reject);
142
- });
143
- }
144
-
145
- function parseMcpName(raw) {
146
- if (!raw) return undefined;
147
- if (raw.startsWith("mcp__")) {
148
- const match = raw.match(/^mcp__([^_]+)__(.+)$/);
149
- if (match) return match[1] + "." + match[2].replace(/__/g, ".");
150
- }
151
- return raw;
152
- }
153
-
154
- function inferCandidates(mode, event) {
155
- const toolName = firstValue(event.tool_name, event.toolName, event.tool, event.matcher);
156
- const input = event.tool_input ?? event.toolInput ?? event.input ?? {};
157
- const candidates = [];
158
-
159
- const filePath = firstValue(
160
- event.file_path,
161
- input.file_path,
162
- input.path,
163
- input.filePath,
164
- input.target_file,
165
- input.targetFile
166
- );
167
- const command = firstValue(event.command, input.command, input.cmd, input.shell_command);
168
- const skillName = firstValue(input.name, input.skill_name, input.skillName);
169
- const mcpName = parseMcpName(firstValue(event.tool_name, input.tool_name, input.name));
170
-
171
- if (mode === "cursor-shell") {
172
- if (command) candidates.push({ kind: "Bash", value: command });
173
- return candidates;
174
- }
175
-
176
- if (mode === "cursor-read") {
177
- if (filePath) candidates.push({ kind: "Read", value: filePath });
178
- return candidates;
179
- }
180
-
181
- if (mode === "cursor-mcp") {
182
- if (mcpName) candidates.push({ kind: "MCP", value: mcpName });
183
- return candidates;
184
- }
185
-
186
- const normalizedTool = String(toolName || "").toLowerCase();
187
-
188
- if (command && /(bash|shell|terminal|command)/i.test(normalizedTool)) {
189
- candidates.push({ kind: "Bash", value: command });
190
- }
191
-
192
- if (filePath && /(edit|write|applypatch|replace|multiedit)/i.test(normalizedTool)) {
193
- candidates.push({ kind: "Edit", value: filePath });
194
- }
195
-
196
- if (filePath && /(read|view|open)/i.test(normalizedTool)) {
197
- candidates.push({ kind: "Read", value: filePath });
198
- }
199
-
200
- if ((normalizedTool.includes("mcp") || String(toolName || "").startsWith("mcp__")) && mcpName) {
201
- candidates.push({ kind: "MCP", value: mcpName });
202
- }
203
-
204
- if (skillName && /skill/i.test(normalizedTool)) {
205
- candidates.push({ kind: "Skill", value: skillName });
206
- }
207
-
208
- return candidates;
209
- }
210
-
211
- function evaluate(mode, event) {
212
- const candidates = inferCandidates(mode, event);
213
- let selected;
214
-
215
- for (const rule of RULES) {
216
- for (const candidate of candidates) {
217
- if (candidate.kind !== rule.kind) continue;
218
- if (!matchesPattern(rule.pattern, candidate.value)) continue;
219
-
220
- if (!selected || ACTION_PRIORITY[rule.action] > ACTION_PRIORITY[selected.action]) {
221
- selected = { rule, action: rule.action, candidate };
222
- }
223
- }
224
- }
225
-
226
- return selected;
227
- }
228
-
229
- function cursorResponse(match) {
230
- if (!match) return {};
231
- return {
232
- permission: match.action,
233
- user_message: "Pluxx permissions matched " + match.rule.raw,
234
- };
235
- }
236
-
237
- function claudeResponse(match) {
238
- if (!match) return {};
239
- return {
240
- hookSpecificOutput: {
241
- permissionDecision: match.action,
242
- permissionDecisionReason: "Pluxx permissions matched " + match.rule.raw,
243
- },
244
- };
245
- }
246
-
247
- async function main() {
248
- const mode = process.argv[2];
249
- const event = await parseJsonInput();
250
- const match = evaluate(mode, event);
251
- const payload = mode === "claude-pretool" ? claudeResponse(match) : cursorResponse(match);
252
- process.stdout.write(JSON.stringify(payload));
253
- }
254
-
255
- main().catch((error) => {
256
- process.stderr.write(String(error instanceof Error ? error.message : error));
257
- process.exit(1);
258
- });
259
- `
260
- }
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>