alpic 0.0.0-dev.gf80bbbb → 0.0.0-dev.gf881e70

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 (33) hide show
  1. package/README.md +2 -0
  2. package/dist/__tests__/insights-category.e2e.test.d.ts +1 -0
  3. package/dist/__tests__/insights-category.e2e.test.js +248 -0
  4. package/dist/__tests__/insights-category.e2e.test.js.map +1 -0
  5. package/dist/__tests__/mock-api.d.ts +10 -0
  6. package/dist/__tests__/mock-api.js +128 -0
  7. package/dist/__tests__/mock-api.js.map +1 -1
  8. package/dist/commands/insights/category/create.d.ts +17 -0
  9. package/dist/commands/insights/category/create.js +63 -0
  10. package/dist/commands/insights/category/create.js.map +1 -0
  11. package/dist/commands/insights/category/delete.d.ts +13 -0
  12. package/dist/commands/insights/category/delete.js +40 -0
  13. package/dist/commands/insights/category/delete.js.map +1 -0
  14. package/dist/commands/insights/category/link.d.ts +14 -0
  15. package/dist/commands/insights/category/link.js +36 -0
  16. package/dist/commands/insights/category/link.js.map +1 -0
  17. package/dist/commands/insights/category/list.d.ts +13 -0
  18. package/dist/commands/insights/category/list.js +45 -0
  19. package/dist/commands/insights/category/list.js.map +1 -0
  20. package/dist/commands/insights/category/unlink.d.ts +14 -0
  21. package/dist/commands/insights/category/unlink.js +36 -0
  22. package/dist/commands/insights/category/unlink.js.map +1 -0
  23. package/dist/commands/insights/category/update.d.ts +16 -0
  24. package/dist/commands/insights/category/update.js +44 -0
  25. package/dist/commands/insights/category/update.js.map +1 -0
  26. package/dist/lib/audit.js +2 -1
  27. package/dist/lib/audit.js.map +1 -1
  28. package/dist/lib/insights.d.ts +16 -0
  29. package/dist/lib/insights.js +52 -0
  30. package/dist/lib/insights.js.map +1 -1
  31. package/dist/lib/telemetry.js +8 -5
  32. package/dist/lib/telemetry.js.map +1 -1
  33. package/package.json +10 -10
package/README.md CHANGED
@@ -18,6 +18,8 @@ Full documentation: **https://docs.alpic.ai/cli/overview**
18
18
  | `logs` | Monitor runtime logs |
19
19
  | `insights intent list` | List grouped user intents |
20
20
  | `insights feedback list` | List user feedback |
21
+ | `insights category list\|create\|update\|delete` | Manage intent categories |
22
+ | `insights category link\|unlink` | Assign intent categories to intents |
21
23
  | `publish` | Share an MCP server to the registry |
22
24
  | `telemetry enable\|disable\|status` | Manage usage reporting |
23
25
  | `tunnel` | Expose local server to the internet |
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,248 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { cliSession, createConfigFile, expectSuccessfulApiCall, registerCliE2ELifecycle } from "./utils.js";
3
+ const ENV_ID = "env-insights-category-test";
4
+ const PROJECT_ID = "prj-insights-category-test";
5
+ const TEAM_ID = "mock-team-id";
6
+ describe("alpic insights category (E2E)", () => {
7
+ const context = registerCliE2ELifecycle({
8
+ projects: [
9
+ {
10
+ projectId: PROJECT_ID,
11
+ teamId: TEAM_ID,
12
+ projectName: "insights-category-project",
13
+ environmentId: ENV_ID,
14
+ environmentName: "production",
15
+ },
16
+ ],
17
+ });
18
+ let mockServer;
19
+ let serverUrl;
20
+ let cwd;
21
+ let binPath;
22
+ beforeEach(() => {
23
+ ({ mockServer, serverUrl, cwd, binPath } = context);
24
+ mockServer.setIntents(ENV_ID, [
25
+ {
26
+ id: "group-1",
27
+ intentIds: ["intent-1", "intent-2"],
28
+ environmentId: ENV_ID,
29
+ message: "Schedule a meeting with the product team",
30
+ toolName: "create-event",
31
+ createdAt: new Date("2026-08-12T10:00:00.000Z"),
32
+ occurrenceCount: 2,
33
+ clientName: "Claude Desktop",
34
+ clientId: "claude-desktop",
35
+ categories: [],
36
+ },
37
+ {
38
+ id: "group-2",
39
+ intentIds: ["intent-3"],
40
+ environmentId: ENV_ID,
41
+ message: "Find the latest release notes",
42
+ toolName: "search",
43
+ createdAt: new Date("2026-08-12T09:00:00.000Z"),
44
+ occurrenceCount: 1,
45
+ clientName: null,
46
+ clientId: null,
47
+ categories: [],
48
+ },
49
+ ]);
50
+ mockServer.setIntentCategories(ENV_ID, [
51
+ {
52
+ id: "cat-scheduling",
53
+ createdAt: new Date("2026-08-10T00:00:00.000Z"),
54
+ environmentId: ENV_ID,
55
+ name: "Scheduling",
56
+ color: "blue",
57
+ linkedIntentIds: ["intent-1", "intent-2"],
58
+ },
59
+ {
60
+ id: "cat-search",
61
+ createdAt: new Date("2026-08-11T00:00:00.000Z"),
62
+ environmentId: ENV_ID,
63
+ name: "Search",
64
+ color: "purple",
65
+ },
66
+ ]);
67
+ });
68
+ const env = () => ({ ALPIC_API_BASE_URL: serverUrl, ALPIC_API_KEY: "test-api-key" });
69
+ it("lists categories with link counts using the local environment", async () => {
70
+ createConfigFile(cwd, PROJECT_ID, TEAM_ID, "insights-category-project", ENV_ID, "production");
71
+ const { exitCode, output } = await cliSession(process.execPath, [binPath, "insights", "category", "list"], {
72
+ cwd,
73
+ env: env(),
74
+ });
75
+ expect(exitCode).toBe(0);
76
+ expect(output).toContain("Found 2 categories");
77
+ expect(output).toContain("Scheduling");
78
+ expect(output).toContain("blue");
79
+ expect(output).toContain("cat-scheduling");
80
+ expectSuccessfulApiCall(mockServer.getLastCall("GET", /\/v1\/environments\/.*\/intent-categories$/));
81
+ });
82
+ it("emits category JSON with link counts", async () => {
83
+ const { exitCode, output } = await cliSession(process.execPath, [binPath, "insights", "category", "list", "--environment-id", ENV_ID, "--json", "--non-interactive"], { cwd, env: env() });
84
+ expect(exitCode).toBe(0);
85
+ const json = output.trim().split("\n").at(-1);
86
+ const parsed = JSON.parse(json ?? "");
87
+ expect(parsed).toEqual([
88
+ { id: "cat-scheduling", name: "Scheduling", color: "blue", intentCount: 2 },
89
+ { id: "cat-search", name: "Search", color: "purple", intentCount: 0 },
90
+ ]);
91
+ });
92
+ it("creates a category with a color and linked intents", async () => {
93
+ const { exitCode, output } = await cliSession(process.execPath, [
94
+ binPath,
95
+ "insights",
96
+ "category",
97
+ "create",
98
+ "--environment-id",
99
+ ENV_ID,
100
+ "--name",
101
+ "Releases",
102
+ "--color",
103
+ "green",
104
+ "--intent-id",
105
+ "intent-3",
106
+ "--json",
107
+ ], { cwd, env: env() });
108
+ expect(exitCode).toBe(0);
109
+ expect(output).toContain("Created Releases (green)");
110
+ const call = mockServer.getLastCall("POST", /\/v1\/environments\/.*\/intent-categories$/);
111
+ expectSuccessfulApiCall(call);
112
+ expect(call.input).toMatchObject({ name: "Releases", color: "green", intentIds: ["intent-3"] });
113
+ const json = JSON.parse(output.trim().split("\n").at(-1) ?? "");
114
+ expect(json).toMatchObject({ name: "Releases", color: "green", environmentId: ENV_ID });
115
+ expect(json.createdAt).toBe("2026-08-13T00:00:00.000Z");
116
+ });
117
+ it("requires --name to create in non-interactive mode", async () => {
118
+ const { exitCode, output } = await cliSession(process.execPath, [binPath, "insights", "category", "create", "--environment-id", ENV_ID, "--non-interactive"], { cwd, env: env() });
119
+ expect(exitCode).toBe(1);
120
+ expect(output).toContain("--name is required in non-interactive mode.");
121
+ });
122
+ it("renames and recolors a category", async () => {
123
+ const { exitCode, output } = await cliSession(process.execPath, [
124
+ binPath,
125
+ "insights",
126
+ "category",
127
+ "update",
128
+ "--environment-id",
129
+ ENV_ID,
130
+ "--category-id",
131
+ "cat-search",
132
+ "--name",
133
+ "Discovery",
134
+ "--color",
135
+ "orange",
136
+ ], { cwd, env: env() });
137
+ expect(exitCode).toBe(0);
138
+ expect(output).toContain("Updated Discovery (orange)");
139
+ const call = mockServer.getLastCall("PATCH", /\/v1\/environments\/.*\/intent-categories\/cat-search$/);
140
+ expectSuccessfulApiCall(call);
141
+ expect(call.input).toMatchObject({ name: "Discovery", color: "orange" });
142
+ });
143
+ it("rejects an update without --name or --color", async () => {
144
+ const { exitCode, output } = await cliSession(process.execPath, [binPath, "insights", "category", "update", "--environment-id", ENV_ID, "--category-id", "cat-search"], { cwd, env: env() });
145
+ expect(exitCode).toBe(1);
146
+ expect(output).toContain("At least one of --name or --color must be provided.");
147
+ expect(mockServer.getCalls("PATCH", /\/intent-categories/)).toHaveLength(0);
148
+ });
149
+ it("deletes a category after interactive selection and confirmation", async () => {
150
+ const { exitCode, output } = await cliSession(process.execPath, [binPath, "insights", "category", "delete", "--environment-id", ENV_ID], { cwd, env: env() }, async (cli) => {
151
+ await cli.expect("Select a category to delete");
152
+ await cli.send("");
153
+ await cli.expect("Delete Scheduling?");
154
+ await cli.send("y");
155
+ await cli.expect("Deleted Scheduling.");
156
+ });
157
+ expect(exitCode).toBe(0);
158
+ expect(output).toContain("It will be unlinked from 2 intents.");
159
+ expectSuccessfulApiCall(mockServer.getLastCall("DELETE", /\/intent-categories\/cat-scheduling$/));
160
+ });
161
+ it("deletes without prompting in non-interactive mode", async () => {
162
+ const { exitCode, output } = await cliSession(process.execPath, [
163
+ binPath,
164
+ "insights",
165
+ "category",
166
+ "delete",
167
+ "--environment-id",
168
+ ENV_ID,
169
+ "--category-id",
170
+ "cat-search",
171
+ "--non-interactive",
172
+ ], { cwd, env: env() });
173
+ expect(exitCode).toBe(0);
174
+ expect(output).toContain("Deleted Search.");
175
+ expect(output).not.toContain("Delete Search?");
176
+ expectSuccessfulApiCall(mockServer.getLastCall("DELETE", /\/intent-categories\/cat-search$/));
177
+ });
178
+ it("requires --category-id to delete in non-interactive mode", async () => {
179
+ const { exitCode, output } = await cliSession(process.execPath, [binPath, "insights", "category", "delete", "--environment-id", ENV_ID, "--non-interactive"], { cwd, env: env() });
180
+ expect(exitCode).toBe(1);
181
+ expect(output).toContain("--category-id is required in non-interactive mode.");
182
+ });
183
+ it("links intents and reflects the category in the intent list filter", async () => {
184
+ const link = await cliSession(process.execPath, [
185
+ binPath,
186
+ "insights",
187
+ "category",
188
+ "link",
189
+ "--environment-id",
190
+ ENV_ID,
191
+ "--category-id",
192
+ "cat-search",
193
+ "--intent-id",
194
+ "intent-3",
195
+ ], { cwd, env: env() });
196
+ expect(link.exitCode).toBe(0);
197
+ expect(link.output).toContain("Linked 1 intent to Search.");
198
+ const call = mockServer.getLastCall("POST", /\/intent-categories\/cat-search\/link$/);
199
+ expectSuccessfulApiCall(call);
200
+ expect(call.input).toMatchObject({ intentIds: ["intent-3"] });
201
+ const list = await cliSession(process.execPath, [binPath, "insights", "intent", "list", "--environment-id", ENV_ID, "--category-id", "cat-search"], { cwd, env: env() });
202
+ expect(list.exitCode).toBe(0);
203
+ expect(list.output).toContain("Found 1 intent");
204
+ expect(list.output).toContain("Find the latest release notes");
205
+ expect(list.output).toContain("Search");
206
+ });
207
+ it("unlinks intents from a category", async () => {
208
+ const { exitCode, output } = await cliSession(process.execPath, [
209
+ binPath,
210
+ "insights",
211
+ "category",
212
+ "unlink",
213
+ "--environment-id",
214
+ ENV_ID,
215
+ "--category-id",
216
+ "cat-scheduling",
217
+ "--intent-id",
218
+ "intent-1",
219
+ "--intent-id",
220
+ "intent-2",
221
+ ], { cwd, env: env() });
222
+ expect(exitCode).toBe(0);
223
+ expect(output).toContain("Unlinked 2 intents from Scheduling.");
224
+ const call = mockServer.getLastCall("POST", /\/intent-categories\/cat-scheduling\/unlink$/);
225
+ expectSuccessfulApiCall(call);
226
+ expect(call.input).toMatchObject({ intentIds: ["intent-1", "intent-2"] });
227
+ });
228
+ it("reports unknown categories and environments", async () => {
229
+ const unknownCategory = await cliSession(process.execPath, [
230
+ binPath,
231
+ "insights",
232
+ "category",
233
+ "update",
234
+ "--environment-id",
235
+ ENV_ID,
236
+ "--category-id",
237
+ "missing",
238
+ "--name",
239
+ "X",
240
+ ], { cwd, env: env() });
241
+ expect(unknownCategory.exitCode).toBe(1);
242
+ expect(unknownCategory.output).toContain('Intent category "missing" not found in this environment.');
243
+ const unknownEnvironment = await cliSession(process.execPath, [binPath, "insights", "category", "list", "--environment-id", "missing"], { cwd, env: env() });
244
+ expect(unknownEnvironment.exitCode).toBe(1);
245
+ expect(unknownEnvironment.output).toContain("Environment not found");
246
+ });
247
+ });
248
+ //# sourceMappingURL=insights-category.e2e.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insights-category.e2e.test.js","sourceRoot":"","sources":["../../src/__tests__/insights-category.e2e.test.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,9 @@ type MockTunnelTicket = {
18
18
  };
19
19
  type MockIntents = RouterOutput["intents"]["list"]["v1"];
20
20
  type MockFeedbacks = RouterOutput["feedbacks"]["list"]["v1"];
21
+ type MockIntentCategory = RouterOutput["intentCategories"]["create"]["v1"] & {
22
+ linkedIntentIds: Set<string>;
23
+ };
21
24
  export type ApiCall = {
22
25
  method: string;
23
26
  path: string;
@@ -53,6 +56,13 @@ export declare class MockApiServer {
53
56
  setBeaconGetResponses(responses: BeaconGetResponse[]): void;
54
57
  setPlayground(environmentId: string, playground: MockPlaygroundData): void;
55
58
  setIntents(environmentId: string, intents: MockIntents["intents"]): void;
59
+ setIntentCategories(environmentId: string, categories: Array<Omit<MockIntentCategory, "linkedIntentIds"> & {
60
+ linkedIntentIds?: string[];
61
+ }>): void;
62
+ private requireMockEnvironmentCategories;
63
+ private requireMockIntentCategory;
64
+ private requireMockIntents;
65
+ private removeCategoryFromIntents;
56
66
  setFeedbacks(environmentId: string, feedbacks: MockFeedbacks["feedbacks"]): void;
57
67
  addProject(project: RouterOutput["projects"]["get"]["v1"]): void;
58
68
  }
@@ -27,6 +27,8 @@ function createEmptyMockData() {
27
27
  teamUsages: new Map(),
28
28
  intents: new Map(),
29
29
  feedbacks: new Map(),
30
+ intentCategories: new Map(),
31
+ intentCategoryCounter: 0,
30
32
  };
31
33
  }
32
34
  const __testDirname = dirname(fileURLToPath(import.meta.url));
@@ -219,6 +221,76 @@ export class MockApiServer {
219
221
  }),
220
222
  },
221
223
  },
224
+ intentCategories: {
225
+ list: {
226
+ v1: implement(contract.intentCategories.list.v1).handler(async ({ input }) => {
227
+ const categories = this.requireMockEnvironmentCategories(input.environmentId);
228
+ return categories.map(({ linkedIntentIds, ...category }) => ({
229
+ ...category,
230
+ intentCount: linkedIntentIds.size,
231
+ }));
232
+ }),
233
+ },
234
+ create: {
235
+ v1: implement(contract.intentCategories.create.v1).handler(async ({ input }) => {
236
+ const categories = this.requireMockEnvironmentCategories(input.environmentId);
237
+ this.requireMockIntents(input.environmentId, input.intentIds ?? [], "BAD_REQUEST");
238
+ const { linkedIntentIds, ...category } = {
239
+ id: `cat-mock-${++this.mockData.intentCategoryCounter}`,
240
+ createdAt: new Date("2026-08-13T00:00:00.000Z"),
241
+ environmentId: input.environmentId,
242
+ name: input.name,
243
+ color: input.color ?? "gray",
244
+ linkedIntentIds: new Set(input.intentIds ?? []),
245
+ };
246
+ categories.push({ ...category, linkedIntentIds });
247
+ return category;
248
+ }),
249
+ },
250
+ update: {
251
+ v1: implement(contract.intentCategories.update.v1).handler(async ({ input }) => {
252
+ const category = this.requireMockIntentCategory(input.environmentId, input.categoryId);
253
+ category.name = input.name ?? category.name;
254
+ category.color = input.color ?? category.color;
255
+ const { linkedIntentIds, ...updated } = category;
256
+ return updated;
257
+ }),
258
+ },
259
+ delete: {
260
+ v1: implement(contract.intentCategories.delete.v1).handler(async ({ input }) => {
261
+ const category = this.requireMockIntentCategory(input.environmentId, input.categoryId);
262
+ this.mockData.intentCategories.set(input.environmentId, (this.mockData.intentCategories.get(input.environmentId) ?? []).filter(({ id }) => id !== category.id));
263
+ this.removeCategoryFromIntents(input.environmentId, category.id, null);
264
+ return { success: true };
265
+ }),
266
+ },
267
+ link: {
268
+ v1: implement(contract.intentCategories.link.v1).handler(async ({ input }) => {
269
+ const category = this.requireMockIntentCategory(input.environmentId, input.categoryId);
270
+ this.requireMockIntents(input.environmentId, input.intentIds, "NOT_FOUND");
271
+ for (const intentId of input.intentIds) {
272
+ category.linkedIntentIds.add(intentId);
273
+ }
274
+ for (const intent of this.mockData.intents.get(input.environmentId) ?? []) {
275
+ const isLinked = intent.intentIds.some((intentId) => category.linkedIntentIds.has(intentId));
276
+ if (isLinked && !intent.categories.some(({ id }) => id === category.id)) {
277
+ intent.categories.push({ id: category.id, name: category.name, color: category.color });
278
+ }
279
+ }
280
+ return { success: true };
281
+ }),
282
+ },
283
+ unlink: {
284
+ v1: implement(contract.intentCategories.unlink.v1).handler(async ({ input }) => {
285
+ const category = this.requireMockIntentCategory(input.environmentId, input.categoryId);
286
+ for (const intentId of input.intentIds) {
287
+ category.linkedIntentIds.delete(intentId);
288
+ }
289
+ this.removeCategoryFromIntents(input.environmentId, category.id, category.linkedIntentIds);
290
+ return { success: true };
291
+ }),
292
+ },
293
+ },
222
294
  feedbacks: {
223
295
  list: {
224
296
  v1: implement(contract.feedbacks.list.v1).handler(async ({ input }) => {
@@ -423,6 +495,30 @@ export class MockApiServer {
423
495
  return environment;
424
496
  }),
425
497
  },
498
+ delete: {
499
+ v1: implement(contract.environments.delete.v1).handler(async ({ input }) => {
500
+ const environment = this.mockData.environments.get(input.environmentId);
501
+ if (!environment) {
502
+ throw new ORPCError("NOT_FOUND", {
503
+ message: "Environment not found",
504
+ });
505
+ }
506
+ const project = this.mockData.projects.get(environment.projectId);
507
+ if (project?.productionEnvironment?.id === input.environmentId) {
508
+ throw new ORPCError("BAD_REQUEST", {
509
+ message: "Cannot delete the production environment of the project",
510
+ });
511
+ }
512
+ this.mockData.environments.delete(input.environmentId);
513
+ if (project) {
514
+ this.mockData.projects.set(environment.projectId, {
515
+ ...project,
516
+ environments: project.environments.filter((env) => env.id !== input.environmentId),
517
+ });
518
+ }
519
+ return { success: true };
520
+ }),
521
+ },
426
522
  getLogs: {
427
523
  v1: implement(contract.environments.getLogs.v1).handler(async ({ input }) => {
428
524
  const environment = this.mockData.environments.get(input.environmentId);
@@ -926,6 +1022,38 @@ export class MockApiServer {
926
1022
  setIntents(environmentId, intents) {
927
1023
  this.mockData.intents.set(environmentId, intents);
928
1024
  }
1025
+ setIntentCategories(environmentId, categories) {
1026
+ this.mockData.intentCategories.set(environmentId, categories.map((category) => ({ ...category, linkedIntentIds: new Set(category.linkedIntentIds ?? []) })));
1027
+ }
1028
+ requireMockEnvironmentCategories(environmentId) {
1029
+ if (!this.mockData.environments.has(environmentId)) {
1030
+ throw new ORPCError("NOT_FOUND", { message: "Environment not found" });
1031
+ }
1032
+ const categories = this.mockData.intentCategories.get(environmentId) ?? [];
1033
+ this.mockData.intentCategories.set(environmentId, categories);
1034
+ return categories;
1035
+ }
1036
+ requireMockIntentCategory(environmentId, categoryId) {
1037
+ const category = this.requireMockEnvironmentCategories(environmentId).find(({ id }) => id === categoryId);
1038
+ if (!category) {
1039
+ throw new ORPCError("NOT_FOUND", { message: "Intent category not found" });
1040
+ }
1041
+ return category;
1042
+ }
1043
+ requireMockIntents(environmentId, intentIds, errorCode) {
1044
+ const knownIntentIds = new Set((this.mockData.intents.get(environmentId) ?? []).flatMap((intent) => intent.intentIds));
1045
+ if (intentIds.some((intentId) => !knownIntentIds.has(intentId))) {
1046
+ throw new ORPCError(errorCode, { message: "One or more intents not found" });
1047
+ }
1048
+ }
1049
+ removeCategoryFromIntents(environmentId, categoryId, remainingIntentIds) {
1050
+ for (const intent of this.mockData.intents.get(environmentId) ?? []) {
1051
+ const stillLinked = remainingIntentIds !== null && intent.intentIds.some((intentId) => remainingIntentIds.has(intentId));
1052
+ if (!stillLinked) {
1053
+ intent.categories = intent.categories.filter(({ id }) => id !== categoryId);
1054
+ }
1055
+ }
1056
+ }
929
1057
  setFeedbacks(environmentId, feedbacks) {
930
1058
  this.mockData.feedbacks.set(environmentId, feedbacks);
931
1059
  }