@gallopsystems/agent-skills 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 (52) hide show
  1. package/README.md +137 -0
  2. package/package.json +26 -0
  3. package/plugins/doctl/.claude-plugin/plugin.json +8 -0
  4. package/plugins/doctl/skills/doctl/SKILL.md +93 -0
  5. package/plugins/kysely-postgres/.claude-plugin/plugin.json +8 -0
  6. package/plugins/kysely-postgres/skills/kysely-postgres/SKILL.md +1101 -0
  7. package/plugins/kysely-postgres/skills/kysely-postgres/references/aggregations.ts +167 -0
  8. package/plugins/kysely-postgres/skills/kysely-postgres/references/ctes.ts +165 -0
  9. package/plugins/kysely-postgres/skills/kysely-postgres/references/expressions.ts +272 -0
  10. package/plugins/kysely-postgres/skills/kysely-postgres/references/joins.ts +206 -0
  11. package/plugins/kysely-postgres/skills/kysely-postgres/references/json-arrays.ts +398 -0
  12. package/plugins/kysely-postgres/skills/kysely-postgres/references/mutations.ts +199 -0
  13. package/plugins/kysely-postgres/skills/kysely-postgres/references/orderby-pagination.ts +117 -0
  14. package/plugins/kysely-postgres/skills/kysely-postgres/references/relations.ts +176 -0
  15. package/plugins/kysely-postgres/skills/kysely-postgres/references/select-where.ts +146 -0
  16. package/plugins/linear/.claude-plugin/plugin.json +8 -0
  17. package/plugins/linear/skills/linear/SKILL.md +1040 -0
  18. package/plugins/linear/skills/linear/bin/linear.mjs +1228 -0
  19. package/plugins/linear/skills/linear/tech-stack.md +273 -0
  20. package/plugins/nitro-testing/.claude-plugin/plugin.json +8 -0
  21. package/plugins/nitro-testing/skills/nitro-testing/SKILL.md +497 -0
  22. package/plugins/nitro-testing/skills/nitro-testing/async-testing.md +270 -0
  23. package/plugins/nitro-testing/skills/nitro-testing/ci-setup.md +226 -0
  24. package/plugins/nitro-testing/skills/nitro-testing/examples/global-setup.ts +90 -0
  25. package/plugins/nitro-testing/skills/nitro-testing/examples/handler.test.ts +167 -0
  26. package/plugins/nitro-testing/skills/nitro-testing/examples/setup.ts +29 -0
  27. package/plugins/nitro-testing/skills/nitro-testing/examples/test-utils-index.ts +297 -0
  28. package/plugins/nitro-testing/skills/nitro-testing/examples/vitest.config.ts +42 -0
  29. package/plugins/nitro-testing/skills/nitro-testing/factories.md +278 -0
  30. package/plugins/nitro-testing/skills/nitro-testing/frontend-testing.md +512 -0
  31. package/plugins/nitro-testing/skills/nitro-testing/test-utils.md +262 -0
  32. package/plugins/nitro-testing/skills/nitro-testing/transaction-rollback.md +183 -0
  33. package/plugins/nitro-testing/skills/nitro-testing/vitest-config.md +236 -0
  34. package/plugins/nuxt-nitro-api/.claude-plugin/plugin.json +8 -0
  35. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/SKILL.md +260 -0
  36. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/auth-patterns.md +228 -0
  37. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/composables-utils.md +174 -0
  38. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/deep-linking.md +190 -0
  39. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/auth-middleware.ts +32 -0
  40. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/auth-utils.ts +51 -0
  41. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/deep-link-page.vue +61 -0
  42. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/service-util.ts +63 -0
  43. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/sse-endpoint.ts +59 -0
  44. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/validation-endpoint.ts +38 -0
  45. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/fetch-patterns.md +178 -0
  46. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/nitro-tasks.md +243 -0
  47. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/page-structure.md +162 -0
  48. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/server-services.md +238 -0
  49. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/sse.md +221 -0
  50. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/ssr-client.md +166 -0
  51. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/validation.md +131 -0
  52. package/scripts/link-skills.mjs +252 -0
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Example: Testing a POST handler
3
+ *
4
+ * Co-located with the handler: index.post.ts -> index.post.test.ts
5
+ */
6
+
7
+ import {
8
+ describe,
9
+ test,
10
+ expect,
11
+ mockPost,
12
+ mockGet,
13
+ expectHttpError,
14
+ } from "~/server/test-utils";
15
+ import handler from "./index.post";
16
+ import getHandler from "./[id].get";
17
+
18
+ describe("POST /api/users", () => {
19
+ test("creates user with valid data", async ({ factories: _, db }) => {
20
+ const event = mockPost({}, {
21
+ email: "new@example.com",
22
+ name: "New User",
23
+ });
24
+
25
+ const result = await handler(event);
26
+
27
+ // Verify response
28
+ expect(result.id).toBeDefined();
29
+ expect(result.email).toBe("new@example.com");
30
+ expect(result.name).toBe("New User");
31
+
32
+ // Verify persisted in database
33
+ const saved = await db
34
+ .selectFrom("user")
35
+ .where("id", "=", result.id)
36
+ .selectAll()
37
+ .executeTakeFirst();
38
+
39
+ expect(saved).toBeDefined();
40
+ expect(saved?.email).toBe("new@example.com");
41
+ });
42
+
43
+ test("creates user with all optional fields", async ({ factories: _ }) => {
44
+ const event = mockPost({}, {
45
+ email: "full@example.com",
46
+ name: "Full User",
47
+ role: "admin",
48
+ metadata: { source: "api", version: 2 },
49
+ });
50
+
51
+ const result = await handler(event);
52
+
53
+ expect(result.role).toBe("admin");
54
+ expect(result.metadata).toEqual({ source: "api", version: 2 });
55
+ });
56
+
57
+ test("sets default values", async ({ factories: _ }) => {
58
+ const event = mockPost({}, {
59
+ email: "minimal@example.com",
60
+ name: "Minimal",
61
+ });
62
+
63
+ const result = await handler(event);
64
+
65
+ expect(result.role).toBe("user"); // Default role
66
+ expect(result.created_at).toBeDefined();
67
+ });
68
+
69
+ test("throws 400 for missing required email", async ({ factories: _ }) => {
70
+ const event = mockPost({}, {
71
+ name: "No Email",
72
+ });
73
+
74
+ await expectHttpError(handler(event), { statusCode: 400 });
75
+ });
76
+
77
+ test("throws 400 for missing required name", async ({ factories: _ }) => {
78
+ const event = mockPost({}, {
79
+ email: "test@example.com",
80
+ });
81
+
82
+ await expectHttpError(handler(event), { statusCode: 400 });
83
+ });
84
+
85
+ test("throws 400 for invalid email format", async ({ factories: _ }) => {
86
+ const event = mockPost({}, {
87
+ email: "not-an-email",
88
+ name: "Test",
89
+ });
90
+
91
+ await expectHttpError(handler(event), { statusCode: 400 });
92
+ });
93
+
94
+ test("throws 400 for invalid role", async ({ factories: _ }) => {
95
+ const event = mockPost({}, {
96
+ email: "test@example.com",
97
+ name: "Test",
98
+ role: "superadmin", // Not a valid role
99
+ });
100
+
101
+ await expectHttpError(handler(event), { statusCode: 400 });
102
+ });
103
+
104
+ test("throws 409 for duplicate email", async ({ factories }) => {
105
+ // Create existing user
106
+ await factories.user({ email: "existing@example.com" });
107
+
108
+ const event = mockPost({}, {
109
+ email: "existing@example.com",
110
+ name: "Duplicate",
111
+ });
112
+
113
+ await expectHttpError(handler(event), { statusCode: 409 });
114
+ });
115
+
116
+ test("created user appears in GET", async ({ factories: _ }) => {
117
+ // Create user
118
+ const createEvent = mockPost({}, {
119
+ email: "findme@example.com",
120
+ name: "Find Me",
121
+ });
122
+ const created = await handler(createEvent);
123
+
124
+ // Fetch user
125
+ const getEvent = mockGet({ id: created.id });
126
+ const fetched = await getHandler(getEvent);
127
+
128
+ expect(fetched.id).toBe(created.id);
129
+ expect(fetched.email).toBe("findme@example.com");
130
+ });
131
+ });
132
+
133
+ describe("POST /api/users - with related data", () => {
134
+ test("creates user with organization", async ({ factories, db }) => {
135
+ const org = await factories.organization({ name: "Acme Corp" });
136
+
137
+ const event = mockPost({}, {
138
+ email: "employee@example.com",
139
+ name: "Employee",
140
+ organizationId: org.id,
141
+ });
142
+
143
+ const result = await handler(event);
144
+
145
+ expect(result.organization_id).toBe(org.id);
146
+
147
+ // Verify relationship
148
+ const user = await db
149
+ .selectFrom("user")
150
+ .innerJoin("organization", "organization.id", "user.organization_id")
151
+ .where("user.id", "=", result.id)
152
+ .select(["user.id", "organization.name as org_name"])
153
+ .executeTakeFirst();
154
+
155
+ expect(user?.org_name).toBe("Acme Corp");
156
+ });
157
+
158
+ test("throws 404 for non-existent organization", async ({ factories: _ }) => {
159
+ const event = mockPost({}, {
160
+ email: "test@example.com",
161
+ name: "Test",
162
+ organizationId: 999999,
163
+ });
164
+
165
+ await expectHttpError(handler(event), { statusCode: 404 });
166
+ });
167
+ });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Vitest Setup File
3
+ *
4
+ * Runs before each test file to stub Nuxt auto-imports.
5
+ * Database reset/migrations happen in global-setup.ts (runs once).
6
+ */
7
+
8
+ import { vi } from "vitest";
9
+
10
+ function getTestConnectionString(): string {
11
+ if (process.env.TEST_POSTGRESQL_CONNECTION_STRING) {
12
+ return process.env.TEST_POSTGRESQL_CONNECTION_STRING;
13
+ }
14
+ return "postgresql://localhost/myapp-test";
15
+ }
16
+
17
+ // Stub useRuntimeConfig before importing anything else
18
+ vi.stubGlobal("useRuntimeConfig", () => ({
19
+ postgresql: {
20
+ connectionString: getTestConnectionString(),
21
+ },
22
+ public: {
23
+ environment: "test",
24
+ },
25
+ }));
26
+
27
+ // Import and run handler mocks (after useRuntimeConfig is stubbed)
28
+ const { setupHandlerMocks } = await import("./index");
29
+ await setupHandlerMocks();
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Test Utilities
3
+ *
4
+ * Helpers for testing Nuxt/Nitro API handlers with real PostgreSQL.
5
+ * Auto-import mocks are set up via vitest setupFiles (see setup.ts).
6
+ */
7
+
8
+ import { vi, test as base, expect } from "vitest";
9
+ import { createEvent } from "h3";
10
+ import { IncomingMessage, ServerResponse } from "http";
11
+ import { Socket } from "net";
12
+ import { db } from "../utils/db";
13
+ import type { Transaction } from "kysely";
14
+ import type { DB } from "../db/db";
15
+
16
+ // ============================================================================
17
+ // Task Registry (for async/automation testing)
18
+ // ============================================================================
19
+
20
+ const taskHandlers: Map<string, (opts: { payload: any }) => Promise<any>> = new Map();
21
+ let pendingTasks: Promise<any>[] = [];
22
+ let currentTrx: Transaction<DB> | null = null;
23
+
24
+ // ============================================================================
25
+ // Global Stubs
26
+ // ============================================================================
27
+
28
+ export async function setupHandlerMocks() {
29
+ vi.stubGlobal("defineEventHandler", (handler: Function) => handler);
30
+
31
+ vi.stubGlobal("getUserSession", async () => ({
32
+ user: { id: 1, firstName: "Test", lastName: "User", role: "admin" },
33
+ }));
34
+
35
+ vi.stubGlobal("setUserSession", async () => {});
36
+
37
+ // Default: no-op for automations
38
+ vi.stubGlobal("triggerAutomation", () => {});
39
+
40
+ // Task registration and execution
41
+ vi.stubGlobal("defineTask", (config: { meta: { name: string }; run: Function }) => {
42
+ taskHandlers.set(config.meta.name, config.run as any);
43
+ return config;
44
+ });
45
+
46
+ vi.stubGlobal("runTask", (taskName: string, options: { payload: any }) => {
47
+ const handler = taskHandlers.get(taskName);
48
+ if (!handler) {
49
+ return Promise.reject(new Error(`Task handler not found: ${taskName}`));
50
+ }
51
+ const promise = handler(options);
52
+ pendingTasks.push(promise);
53
+ return promise;
54
+ });
55
+
56
+ vi.stubGlobal("useDatabase", () => {
57
+ if (!currentTrx) {
58
+ throw new Error("useDatabase called outside of test transaction");
59
+ }
60
+ return currentTrx;
61
+ });
62
+
63
+ // Handle nested transactions
64
+ const KyselyModule = await import("kysely");
65
+ const TransactionClass = (KyselyModule as any).Transaction;
66
+ if (TransactionClass?.prototype) {
67
+ TransactionClass.prototype.transaction = function () {
68
+ const self = this;
69
+ return {
70
+ execute: async <T>(callback: (trx: any) => Promise<T>): Promise<T> => {
71
+ return callback(self);
72
+ },
73
+ };
74
+ };
75
+ }
76
+
77
+ vi.stubGlobal("createError", (opts: {
78
+ statusCode: number;
79
+ message?: string;
80
+ statusMessage?: string;
81
+ data?: unknown;
82
+ }) => {
83
+ const error = new Error(opts.message || opts.statusMessage || "") as any;
84
+ error.statusCode = opts.statusCode;
85
+ if (opts.data !== undefined) error.data = opts.data;
86
+ return error;
87
+ });
88
+
89
+ vi.stubGlobal("getValidatedRouterParams", async (event: any, validate: Function) => {
90
+ return validate(event.context.params ?? {});
91
+ });
92
+
93
+ vi.stubGlobal("getRouterParam", (event: any, param: string) => {
94
+ return event.context.params?.[param];
95
+ });
96
+
97
+ vi.stubGlobal("getValidatedQuery", async (event: any, validate: Function) => {
98
+ return validate(event.context._mockQuery ?? {});
99
+ });
100
+
101
+ vi.stubGlobal("getQuery", (event: any) => {
102
+ return event.context._mockQuery ?? {};
103
+ });
104
+
105
+ vi.stubGlobal("readValidatedBody", async (event: any, validate: Function) => {
106
+ return validate(event.context._mockBody ?? {});
107
+ });
108
+
109
+ vi.stubGlobal("readBody", async (event: any) => event.context._mockBody);
110
+ }
111
+
112
+ // ============================================================================
113
+ // Automation Testing
114
+ // ============================================================================
115
+
116
+ export async function enableAutomationTriggers() {
117
+ await import("../tasks/execute-automation");
118
+ const { triggerAutomation: realTrigger } = await import("../utils/automation");
119
+
120
+ vi.stubGlobal("triggerAutomation", (...args: Parameters<typeof realTrigger>) => {
121
+ const promise = realTrigger(...args);
122
+ pendingTasks.push(promise);
123
+ return promise;
124
+ });
125
+ }
126
+
127
+ export async function waitForAutomations(): Promise<void> {
128
+ while (pendingTasks.length > 0) {
129
+ const tasksToWait = [...pendingTasks];
130
+ pendingTasks = [];
131
+ await Promise.allSettled(tasksToWait);
132
+ }
133
+ }
134
+
135
+ // ============================================================================
136
+ // Mock Event Helpers
137
+ // ============================================================================
138
+
139
+ type Params = Record<string, string | number>;
140
+
141
+ export function createMockEvent(options: {
142
+ method?: string;
143
+ params?: Params;
144
+ body?: unknown;
145
+ query?: Record<string, string>;
146
+ }) {
147
+ const { method = "GET", params = {}, body, query = {} } = options;
148
+
149
+ const socket = new Socket();
150
+ const req = new IncomingMessage(socket);
151
+ req.method = method;
152
+ req.url = "/" + (Object.keys(query).length
153
+ ? "?" + new URLSearchParams(query).toString()
154
+ : "");
155
+
156
+ const res = new ServerResponse(req);
157
+ const event = createEvent(req, res);
158
+
159
+ event.context.params = Object.fromEntries(
160
+ Object.entries(params).map(([k, v]) => [k, String(v)])
161
+ );
162
+
163
+ if (body !== undefined) event.context._mockBody = body;
164
+ if (Object.keys(query).length > 0) event.context._mockQuery = query;
165
+
166
+ return event;
167
+ }
168
+
169
+ export function mockGet(params: Params, query?: Record<string, string>) {
170
+ return createMockEvent({ method: "GET", params, query });
171
+ }
172
+
173
+ export function mockPost(params: Params, body: unknown) {
174
+ return createMockEvent({ method: "POST", params, body });
175
+ }
176
+
177
+ export function mockPatch(params: Params, body: unknown) {
178
+ return createMockEvent({ method: "PATCH", params, body });
179
+ }
180
+
181
+ export function mockDelete(params: Params) {
182
+ return createMockEvent({ method: "DELETE", params });
183
+ }
184
+
185
+ // ============================================================================
186
+ // Assertion Helpers
187
+ // ============================================================================
188
+
189
+ export async function expectHttpError(
190
+ promise: Promise<unknown>,
191
+ expected: { statusCode: number; message?: string }
192
+ ) {
193
+ await expect(promise).rejects.toMatchObject(expected);
194
+ }
195
+
196
+ // ============================================================================
197
+ // Factories
198
+ // ============================================================================
199
+
200
+ function createFactories(trx: Transaction<DB>) {
201
+ return {
202
+ async user(data: Partial<{
203
+ email: string;
204
+ name: string;
205
+ role: string;
206
+ }> = {}) {
207
+ const num = Math.floor(Math.random() * 10000);
208
+ return trx
209
+ .insertInto("user")
210
+ .values({
211
+ email: data.email ?? `test${num}@example.com`,
212
+ name: data.name ?? "Test User",
213
+ role: data.role ?? "user",
214
+ })
215
+ .returningAll()
216
+ .executeTakeFirstOrThrow();
217
+ },
218
+
219
+ async project(data: Partial<{
220
+ name: string;
221
+ ownerId: number;
222
+ status: string;
223
+ }> = {}) {
224
+ let ownerId = data.ownerId;
225
+ if (!ownerId) {
226
+ const owner = await this.user();
227
+ ownerId = owner.id;
228
+ }
229
+ return trx
230
+ .insertInto("project")
231
+ .values({
232
+ name: data.name ?? `Project ${Date.now()}`,
233
+ owner_id: ownerId,
234
+ status: data.status ?? "active",
235
+ })
236
+ .returningAll()
237
+ .executeTakeFirstOrThrow();
238
+ },
239
+
240
+ async task(data: {
241
+ projectId: number;
242
+ title?: string;
243
+ status?: string;
244
+ assigneeId?: number | null;
245
+ }) {
246
+ return trx
247
+ .insertInto("task")
248
+ .values({
249
+ project_id: data.projectId,
250
+ title: data.title ?? "Test Task",
251
+ status: data.status ?? "pending",
252
+ assignee_id: data.assigneeId ?? null,
253
+ })
254
+ .returningAll()
255
+ .executeTakeFirstOrThrow();
256
+ },
257
+
258
+ // Add more factories as needed...
259
+ };
260
+ }
261
+
262
+ export type Factories = ReturnType<typeof createFactories>;
263
+
264
+ // ============================================================================
265
+ // Test Fixture
266
+ // ============================================================================
267
+
268
+ interface TestFixtures {
269
+ factories: Factories;
270
+ db: Transaction<DB>;
271
+ }
272
+
273
+ export const test = base.extend<TestFixtures>({
274
+ factories: async ({}, use) => {
275
+ await db.transaction().execute(async (trx) => {
276
+ currentTrx = trx;
277
+ try {
278
+ await use(createFactories(trx));
279
+ throw { __rollback: true };
280
+ } finally {
281
+ currentTrx = null;
282
+ }
283
+ }).catch((e) => {
284
+ if (e && typeof e === "object" && "__rollback" in e) return;
285
+ throw e;
286
+ });
287
+ },
288
+
289
+ db: async ({ factories: _ }, use) => {
290
+ if (!currentTrx) {
291
+ throw new Error("db fixture used outside transaction context");
292
+ }
293
+ await use(currentTrx);
294
+ },
295
+ });
296
+
297
+ export { describe, expect, beforeAll } from "vitest";
@@ -0,0 +1,42 @@
1
+ import { defineConfig } from "vitest/config";
2
+ import path from "path";
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ // Enable globals (describe, test, expect without imports)
7
+ globals: true,
8
+
9
+ // Node environment for API testing
10
+ environment: "node",
11
+
12
+ // Global setup: runs once before all tests
13
+ // - Drops all tables
14
+ // - Runs migrations
15
+ globalSetup: ["./server/test-utils/global-setup.ts"],
16
+
17
+ // Setup files: runs before each test file
18
+ // - Stubs Nuxt auto-imports
19
+ setupFiles: ["./server/test-utils/setup.ts"],
20
+
21
+ // Coverage configuration
22
+ coverage: {
23
+ provider: "v8",
24
+ reporter: ["text", "html", "lcov", "json", "json-summary"],
25
+ include: ["server/**/*.ts"],
26
+ exclude: [
27
+ "server/**/*.test.ts",
28
+ "server/test-utils/**",
29
+ "server/db/migrations/**",
30
+ "server/db/db.d.ts",
31
+ ],
32
+ reportsDirectory: "./coverage",
33
+ },
34
+ },
35
+
36
+ resolve: {
37
+ alias: {
38
+ "~": path.resolve(__dirname),
39
+ "@": path.resolve(__dirname),
40
+ },
41
+ },
42
+ });