@ekzs/cli 0.2.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 (113) hide show
  1. package/README.md +148 -0
  2. package/dist/commands/agent.d.ts +31 -0
  3. package/dist/commands/agent.d.ts.map +1 -0
  4. package/dist/commands/agent.js +55 -0
  5. package/dist/commands/ask.d.ts +20 -0
  6. package/dist/commands/ask.d.ts.map +1 -0
  7. package/dist/commands/ask.js +154 -0
  8. package/dist/commands/doctor.d.ts +3 -0
  9. package/dist/commands/doctor.d.ts.map +1 -0
  10. package/dist/commands/doctor.js +44 -0
  11. package/dist/commands/health.d.ts +2 -0
  12. package/dist/commands/health.d.ts.map +1 -0
  13. package/dist/commands/health.js +28 -0
  14. package/dist/commands/local-agent.d.ts +19 -0
  15. package/dist/commands/local-agent.d.ts.map +1 -0
  16. package/dist/commands/local-agent.js +450 -0
  17. package/dist/commands/scan.d.ts +11 -0
  18. package/dist/commands/scan.d.ts.map +1 -0
  19. package/dist/commands/scan.js +119 -0
  20. package/dist/commands/webhook.d.ts +10 -0
  21. package/dist/commands/webhook.d.ts.map +1 -0
  22. package/dist/commands/webhook.js +42 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +185 -0
  26. package/dist/lib/banner.d.ts +10 -0
  27. package/dist/lib/banner.d.ts.map +1 -0
  28. package/dist/lib/banner.js +26 -0
  29. package/dist/lib/commands-i18n.d.ts +20 -0
  30. package/dist/lib/commands-i18n.d.ts.map +1 -0
  31. package/dist/lib/commands-i18n.js +157 -0
  32. package/dist/lib/composer-model.d.ts +10 -0
  33. package/dist/lib/composer-model.d.ts.map +1 -0
  34. package/dist/lib/composer-model.js +15 -0
  35. package/dist/lib/context.d.ts +12 -0
  36. package/dist/lib/context.d.ts.map +1 -0
  37. package/dist/lib/context.js +56 -0
  38. package/dist/lib/doctor-quiet.d.ts +11 -0
  39. package/dist/lib/doctor-quiet.d.ts.map +1 -0
  40. package/dist/lib/doctor-quiet.js +39 -0
  41. package/dist/lib/env.d.ts +18 -0
  42. package/dist/lib/env.d.ts.map +1 -0
  43. package/dist/lib/env.js +66 -0
  44. package/dist/lib/help.d.ts +10 -0
  45. package/dist/lib/help.d.ts.map +1 -0
  46. package/dist/lib/help.js +140 -0
  47. package/dist/lib/locale.d.ts +38 -0
  48. package/dist/lib/locale.d.ts.map +1 -0
  49. package/dist/lib/locale.js +189 -0
  50. package/dist/lib/mode.d.ts +11 -0
  51. package/dist/lib/mode.d.ts.map +1 -0
  52. package/dist/lib/mode.js +29 -0
  53. package/dist/lib/output.d.ts +7 -0
  54. package/dist/lib/output.d.ts.map +1 -0
  55. package/dist/lib/output.js +18 -0
  56. package/dist/lib/preferences.d.ts +9 -0
  57. package/dist/lib/preferences.d.ts.map +1 -0
  58. package/dist/lib/preferences.js +35 -0
  59. package/dist/lib/redact.d.ts +3 -0
  60. package/dist/lib/redact.d.ts.map +1 -0
  61. package/dist/lib/redact.js +32 -0
  62. package/dist/lib/scan-quiet.d.ts +4 -0
  63. package/dist/lib/scan-quiet.d.ts.map +1 -0
  64. package/dist/lib/scan-quiet.js +4 -0
  65. package/dist/lib/scope.d.ts +5 -0
  66. package/dist/lib/scope.d.ts.map +1 -0
  67. package/dist/lib/scope.js +61 -0
  68. package/dist/lib/session.d.ts +31 -0
  69. package/dist/lib/session.d.ts.map +1 -0
  70. package/dist/lib/session.js +101 -0
  71. package/dist/lib/shell.d.ts +18 -0
  72. package/dist/lib/shell.d.ts.map +1 -0
  73. package/dist/lib/shell.js +214 -0
  74. package/dist/lib/skill.d.ts +3 -0
  75. package/dist/lib/skill.d.ts.map +1 -0
  76. package/dist/lib/skill.js +2 -0
  77. package/dist/lib/skills.d.ts +16 -0
  78. package/dist/lib/skills.d.ts.map +1 -0
  79. package/dist/lib/skills.js +199 -0
  80. package/dist/lib/theme.d.ts +23 -0
  81. package/dist/lib/theme.d.ts.map +1 -0
  82. package/dist/lib/theme.js +40 -0
  83. package/dist/lib/ui/ascii-art.d.ts +10 -0
  84. package/dist/lib/ui/ascii-art.d.ts.map +1 -0
  85. package/dist/lib/ui/ascii-art.js +55 -0
  86. package/dist/lib/ui/layout.d.ts +19 -0
  87. package/dist/lib/ui/layout.d.ts.map +1 -0
  88. package/dist/lib/ui/layout.js +46 -0
  89. package/dist/lib/ui/logo.d.ts +3 -0
  90. package/dist/lib/ui/logo.d.ts.map +1 -0
  91. package/dist/lib/ui/logo.js +8 -0
  92. package/dist/lib/ui/prompt.d.ts +6 -0
  93. package/dist/lib/ui/prompt.d.ts.map +1 -0
  94. package/dist/lib/ui/prompt.js +75 -0
  95. package/dist/lib/ui/splash.d.ts +15 -0
  96. package/dist/lib/ui/splash.d.ts.map +1 -0
  97. package/dist/lib/ui/splash.js +121 -0
  98. package/package.json +48 -0
  99. package/skills/ekz-connect/SKILL.md +99 -0
  100. package/skills/ekz-data-layer-design/SKILL.md +199 -0
  101. package/skills/ekz-data-mongo/SKILL.md +341 -0
  102. package/skills/ekz-data-mysql/SKILL.md +245 -0
  103. package/skills/ekz-data-postgres/SKILL.md +257 -0
  104. package/skills/ekz-data-sqlite/SKILL.md +261 -0
  105. package/skills/ekz-ekwanza-provider-adapter/SKILL.md +91 -0
  106. package/skills/ekz-integration-playbook/SKILL.md +122 -0
  107. package/skills/ekz-one-time-product-payments/SKILL.md +91 -0
  108. package/skills/ekz-overage-billing/SKILL.md +68 -0
  109. package/skills/ekz-payment-core-architecture/SKILL.md +121 -0
  110. package/skills/ekz-sdk-cli/SKILL.md +82 -0
  111. package/skills/ekz-subscription-billing/SKILL.md +120 -0
  112. package/skills/ekz-ticket-invite-selling/SKILL.md +64 -0
  113. package/skills/ekz-webhook-normalization/SKILL.md +88 -0
@@ -0,0 +1,450 @@
1
+ import { resolve } from "path";
2
+ import { Agent, AgentBusyError, CursorAgentError } from "@cursor/sdk";
3
+ import { executeAskTurn } from "../commands/ask.js";
4
+ import { runDoctor } from "../commands/doctor.js";
5
+ import { runScan } from "../commands/scan.js";
6
+ import { inputPlaceholder, printHelp, printLangSwitch, printModeSwitch, printWelcome } from "../lib/banner.js";
7
+ import { loadProjectEnv } from "../lib/env.js";
8
+ import { resolveReplMetaCommand } from "../lib/commands-i18n.js";
9
+ import { buildAgentPrompt, buildBootstrapContext } from "../lib/context.js";
10
+ import { resolveComposerModel } from "../lib/composer-model.js";
11
+ import { parseLocale, uiStrings } from "../lib/locale.js";
12
+ import { modeRequiresCursorAgent, parseMode } from "../lib/mode.js";
13
+ import { loadPreferences, savePreferences } from "../lib/preferences.js";
14
+ import { clearSession, getSessionByIndex, loadSession, persistSession, saveNamedSession, } from "../lib/session.js";
15
+ import { fail, info, ok, warn } from "../lib/output.js";
16
+ import { toolDone, toolError, toolRunning, turnDivider } from "../lib/theme.js";
17
+ import { askWithPlaceholder, formatInputPrompt } from "../lib/ui/prompt.js";
18
+ function getApiKey(override, cwd) {
19
+ if (cwd)
20
+ loadProjectEnv(cwd);
21
+ else
22
+ loadProjectEnv(process.cwd());
23
+ const key = override ?? process.env.CURSOR_API_KEY?.trim();
24
+ if (!key) {
25
+ throw new Error("CURSOR_API_KEY required. Add it to .env.local or export it — https://cursor.com/dashboard/integrations");
26
+ }
27
+ return key;
28
+ }
29
+ async function disposeAgent(agent) {
30
+ const disposer = agent[Symbol.asyncDispose];
31
+ if (typeof disposer === "function")
32
+ await disposer.call(agent);
33
+ else
34
+ agent.close();
35
+ }
36
+ async function streamRun(agent, prompt, locale, opts) {
37
+ const t = uiStrings(locale);
38
+ const sendOptions = {
39
+ onDelta: ({ update }) => {
40
+ if (update.type === "text-delta" && update.text) {
41
+ process.stdout.write(update.text);
42
+ }
43
+ },
44
+ ...(opts?.force ? { local: { force: true } } : {}),
45
+ };
46
+ let run;
47
+ try {
48
+ run = await agent.send(prompt, sendOptions);
49
+ }
50
+ catch (err) {
51
+ if (err instanceof AgentBusyError) {
52
+ warn(t.busyRun);
53
+ run = await agent.send(prompt, { ...sendOptions, local: { force: true } });
54
+ }
55
+ else {
56
+ throw err;
57
+ }
58
+ }
59
+ for await (const event of run.stream()) {
60
+ if (event.type === "tool_call" && event.status === "running") {
61
+ process.stdout.write(toolRunning(event.name));
62
+ }
63
+ if (event.type === "tool_call" && event.status === "completed") {
64
+ process.stdout.write(toolDone(event.name));
65
+ }
66
+ if (event.type === "tool_call" && event.status === "error") {
67
+ process.stdout.write(toolError(event.name));
68
+ }
69
+ }
70
+ const result = await run.wait();
71
+ if (result.status === "error") {
72
+ fail(`${t.agentFailed} (${result.id})`);
73
+ process.exitCode = 2;
74
+ }
75
+ return { status: result.status, result: result.result };
76
+ }
77
+ async function createOrResumeAgent(opts) {
78
+ const cwd = resolve(opts.cwd);
79
+ const apiKey = getApiKey(opts.apiKey, cwd);
80
+ const model = resolveComposerModel();
81
+ const usePlan = opts.mode === "plan" || Boolean(opts.plan);
82
+ if (opts.fresh)
83
+ clearSession(cwd);
84
+ const agentId = opts.resumeAgentId ??
85
+ (!opts.fresh && opts.resume ? loadSession(cwd)?.agentId : undefined);
86
+ if (agentId) {
87
+ const agent = await Agent.resume(agentId, {
88
+ apiKey,
89
+ model: { id: model.id, params: model.params },
90
+ local: { cwd, settingSources: ["project"] },
91
+ });
92
+ return { agent, resumed: true };
93
+ }
94
+ const agent = await Agent.create({
95
+ apiKey,
96
+ name: "Ekz Connect — e-Kwanza payments only",
97
+ model: { id: model.id, params: model.params },
98
+ local: { cwd, settingSources: ["project"] },
99
+ ...(usePlan ? { mode: "plan" } : {}),
100
+ });
101
+ return { agent, resumed: false };
102
+ }
103
+ async function recreateAgentForMode(cwd, current, mode, apiKey) {
104
+ if (!modeRequiresCursorAgent(mode))
105
+ return null;
106
+ if (current)
107
+ await disposeAgent(current);
108
+ const { agent } = await createOrResumeAgent({
109
+ cwd,
110
+ task: "",
111
+ fresh: true,
112
+ mode,
113
+ apiKey,
114
+ });
115
+ await persistAgent(cwd, agent);
116
+ return agent;
117
+ }
118
+ async function persistAgent(cwd, agent) {
119
+ const model = resolveComposerModel();
120
+ persistSession(cwd, {
121
+ agentId: agent.agentId,
122
+ cwd: resolve(cwd),
123
+ model: model.label,
124
+ });
125
+ }
126
+ function handleMetaCommand(line, locale, cwd, currentMode) {
127
+ const parts = line.trim().split(/\s+/);
128
+ const cmd = resolveReplMetaCommand(parts[0] ?? "");
129
+ if (cmd === "help") {
130
+ printHelp(locale, currentMode);
131
+ return { kind: "handled" };
132
+ }
133
+ if (cmd === "lang") {
134
+ const next = parseLocale(parts[1]);
135
+ if (!next) {
136
+ warn(uiStrings(locale).langInvalid);
137
+ return { kind: "handled" };
138
+ }
139
+ savePreferences(cwd, { locale: next });
140
+ printLangSwitch(next);
141
+ return { kind: "locale", locale: next };
142
+ }
143
+ if (cmd === "mode") {
144
+ const next = parseMode(parts[1]);
145
+ if (!next) {
146
+ warn(locale === "pt"
147
+ ? "Modo inválido. Usa: /mode agent|plan|ask"
148
+ : locale === "zh"
149
+ ? "无效模式。请使用: /mode agent|plan|ask"
150
+ : "Invalid mode. Use: /mode agent|plan|ask");
151
+ return { kind: "handled" };
152
+ }
153
+ savePreferences(cwd, { mode: next });
154
+ return { kind: "mode", mode: next };
155
+ }
156
+ if (cmd === "doctor") {
157
+ void runDoctor(cwd).then(() => console.log(""));
158
+ return { kind: "handled" };
159
+ }
160
+ if (cmd === "scan") {
161
+ void runScan(cwd).then(() => console.log(""));
162
+ return { kind: "handled" };
163
+ }
164
+ if (cmd === "resume") {
165
+ const idx = parts[1] ? Number(parts[1]) : 0;
166
+ const session = idx > 0 ? getSessionByIndex(cwd, idx) : loadSession(cwd);
167
+ if (!session) {
168
+ warn(locale === "pt" ? "Nenhuma sessão para retomar." : "No session to resume.");
169
+ return { kind: "handled" };
170
+ }
171
+ return { kind: "resume", agentId: session.agentId, name: session.name };
172
+ }
173
+ if (cmd === "save") {
174
+ const name = parts.slice(1).join(" ").trim();
175
+ if (!name) {
176
+ warn(locale === "pt" ? "Usa /guardar <nome>" : locale === "zh" ? "使用 /save <名称>" : "Use /save <name>");
177
+ return { kind: "handled" };
178
+ }
179
+ return { kind: "save", name };
180
+ }
181
+ return null;
182
+ }
183
+ async function resumeAgentInRepl(cwd, current, agentId, locale) {
184
+ if (current)
185
+ await disposeAgent(current);
186
+ const apiKey = getApiKey(undefined, cwd);
187
+ const model = resolveComposerModel();
188
+ const agent = await Agent.resume(agentId, {
189
+ apiKey,
190
+ model: { id: model.id, params: model.params },
191
+ local: { cwd: resolve(cwd), settingSources: ["project"] },
192
+ });
193
+ await persistAgent(cwd, agent);
194
+ ok(locale === "pt" ? "Sessão retomada." : "Session resumed.");
195
+ return agent;
196
+ }
197
+ export async function runLocalAgent(opts) {
198
+ const cwd = resolve(opts.cwd);
199
+ loadProjectEnv(cwd);
200
+ const model = resolveComposerModel();
201
+ const prefs = loadPreferences(cwd, opts.locale, opts.mode, opts.plan);
202
+ const mode = prefs.mode;
203
+ if (mode === "ask") {
204
+ printWelcome({ cwd, locale: prefs.locale, mode: "ask" });
205
+ if (opts.task.trim()) {
206
+ info(`${uiStrings(prefs.locale).workingOn}: ${opts.task.slice(0, 120)}${opts.task.length > 120 ? "…" : ""}\n`);
207
+ await executeAskTurn({
208
+ cwd,
209
+ question: opts.task.trim(),
210
+ apiKey: opts.askApiKey,
211
+ apiUrl: opts.askApiUrl,
212
+ offline: opts.askOffline,
213
+ quiet: true,
214
+ });
215
+ console.log("");
216
+ }
217
+ if (opts.interactive !== false) {
218
+ await runRepl({
219
+ agent: null,
220
+ cwd,
221
+ locale: prefs.locale,
222
+ mode: "ask",
223
+ apiKey: opts.apiKey,
224
+ askApiKey: opts.askApiKey,
225
+ askApiUrl: opts.askApiUrl,
226
+ askOffline: opts.askOffline,
227
+ });
228
+ }
229
+ return;
230
+ }
231
+ let agent;
232
+ let resumed = false;
233
+ try {
234
+ ({ agent, resumed } = await createOrResumeAgent({ ...opts, mode }));
235
+ }
236
+ catch (err) {
237
+ if (err instanceof CursorAgentError) {
238
+ fail(`Agent startup failed: ${err.message}`);
239
+ process.exitCode = 1;
240
+ return;
241
+ }
242
+ throw err;
243
+ }
244
+ printWelcome({
245
+ cwd,
246
+ model: model.label,
247
+ locale: prefs.locale,
248
+ mode,
249
+ resumed,
250
+ agentId: agent.agentId,
251
+ });
252
+ await persistAgent(cwd, agent);
253
+ savePreferences(cwd, { mode });
254
+ const bootstrap = await buildBootstrapContext(cwd);
255
+ const prompt = buildAgentPrompt(opts.task, bootstrap, prefs.locale);
256
+ const t = uiStrings(prefs.locale);
257
+ try {
258
+ info(`${t.workingOn}: ${opts.task.slice(0, 120)}${opts.task.length > 120 ? "…" : ""}\n`);
259
+ const outcome = await streamRun(agent, prompt, prefs.locale);
260
+ console.log("\n");
261
+ ok(`${t.done} (${outcome.status})`);
262
+ if (opts.interactive !== false) {
263
+ await runRepl({
264
+ agent,
265
+ cwd,
266
+ locale: prefs.locale,
267
+ mode,
268
+ apiKey: opts.apiKey,
269
+ askApiKey: opts.askApiKey,
270
+ askApiUrl: opts.askApiUrl,
271
+ askOffline: opts.askOffline,
272
+ });
273
+ }
274
+ else {
275
+ info(t.resumeHint);
276
+ }
277
+ }
278
+ finally {
279
+ await disposeAgent(agent);
280
+ }
281
+ }
282
+ export async function runInteractiveAgent(opts) {
283
+ const cwd = resolve(opts.cwd);
284
+ loadProjectEnv(cwd);
285
+ const model = resolveComposerModel();
286
+ const prefs = loadPreferences(cwd, opts.locale, opts.mode, opts.plan);
287
+ const mode = prefs.mode;
288
+ if (mode === "ask") {
289
+ printWelcome({ cwd, locale: prefs.locale, mode: "ask" });
290
+ await runRepl({
291
+ agent: null,
292
+ cwd,
293
+ locale: prefs.locale,
294
+ mode: "ask",
295
+ apiKey: opts.apiKey,
296
+ askApiKey: opts.askApiKey,
297
+ askApiUrl: opts.askApiUrl,
298
+ askOffline: opts.askOffline,
299
+ });
300
+ return;
301
+ }
302
+ let agent;
303
+ let resumed = false;
304
+ try {
305
+ ({ agent, resumed } = await createOrResumeAgent({ ...opts, task: "", mode }));
306
+ }
307
+ catch (err) {
308
+ if (err instanceof CursorAgentError) {
309
+ fail(`Agent startup failed: ${err.message}`);
310
+ process.exitCode = 1;
311
+ return;
312
+ }
313
+ throw err;
314
+ }
315
+ printWelcome({
316
+ cwd,
317
+ model: model.label,
318
+ locale: prefs.locale,
319
+ mode,
320
+ resumed,
321
+ agentId: agent.agentId,
322
+ });
323
+ await persistAgent(cwd, agent);
324
+ savePreferences(cwd, { mode });
325
+ try {
326
+ await runRepl({
327
+ agent,
328
+ cwd,
329
+ locale: prefs.locale,
330
+ mode,
331
+ apiKey: opts.apiKey,
332
+ askApiKey: opts.askApiKey,
333
+ askApiUrl: opts.askApiUrl,
334
+ askOffline: opts.askOffline,
335
+ });
336
+ }
337
+ finally {
338
+ await disposeAgent(agent);
339
+ }
340
+ }
341
+ async function runRepl(opts) {
342
+ let currentLocale = opts.locale;
343
+ let currentMode = opts.mode;
344
+ let currentAgent = opts.agent;
345
+ let turn = 0;
346
+ const t = () => uiStrings(currentLocale);
347
+ while (true) {
348
+ const line = (await askWithPlaceholder({
349
+ prompt: formatInputPrompt(),
350
+ placeholder: inputPlaceholder(currentLocale, turn),
351
+ })).trim();
352
+ if (!line || line === "exit" || line === "quit" || line === "sair" || line === "退出")
353
+ break;
354
+ const meta = handleMetaCommand(line, currentLocale, opts.cwd, currentMode);
355
+ if (meta?.kind === "handled")
356
+ continue;
357
+ if (meta?.kind === "locale") {
358
+ currentLocale = meta.locale;
359
+ continue;
360
+ }
361
+ if (meta?.kind === "mode") {
362
+ currentMode = meta.mode;
363
+ printModeSwitch(currentLocale, currentMode);
364
+ try {
365
+ if (currentMode === "ask") {
366
+ if (currentAgent) {
367
+ await disposeAgent(currentAgent);
368
+ currentAgent = null;
369
+ }
370
+ }
371
+ else {
372
+ currentAgent = await recreateAgentForMode(opts.cwd, currentAgent, currentMode, opts.apiKey);
373
+ }
374
+ }
375
+ catch (err) {
376
+ fail(err instanceof Error ? err.message : String(err));
377
+ }
378
+ continue;
379
+ }
380
+ if (meta?.kind === "resume") {
381
+ if (currentMode === "ask") {
382
+ warn(currentLocale === "pt" ? "Sessões Cursor só em modo agent/plan." : "Cursor sessions only in agent/plan mode.");
383
+ continue;
384
+ }
385
+ try {
386
+ if (!currentAgent) {
387
+ fail(currentLocale === "pt" ? "Sem agente activo." : "No active agent.");
388
+ continue;
389
+ }
390
+ currentAgent = await resumeAgentInRepl(opts.cwd, currentAgent, meta.agentId, currentLocale);
391
+ }
392
+ catch (err) {
393
+ fail(err instanceof Error ? err.message : String(err));
394
+ }
395
+ continue;
396
+ }
397
+ if (meta?.kind === "save") {
398
+ if (!currentAgent) {
399
+ warn(currentLocale === "pt" ? "Guardar sessão só em modo agent/plan." : "Save session only in agent/plan mode.");
400
+ continue;
401
+ }
402
+ const model = resolveComposerModel();
403
+ saveNamedSession(opts.cwd, {
404
+ agentId: currentAgent.agentId,
405
+ cwd: resolve(opts.cwd),
406
+ model: model.label,
407
+ name: meta.name,
408
+ });
409
+ ok(currentLocale === "pt" ? `Sessão guardada: ${meta.name}` : `Session saved: ${meta.name}`);
410
+ continue;
411
+ }
412
+ console.log("");
413
+ process.stdout.write(turnDivider());
414
+ if (currentMode === "ask") {
415
+ await executeAskTurn({
416
+ cwd: opts.cwd,
417
+ question: line,
418
+ apiKey: opts.askApiKey,
419
+ apiUrl: opts.askApiUrl,
420
+ offline: opts.askOffline,
421
+ quiet: true,
422
+ });
423
+ console.log("\n");
424
+ turn++;
425
+ continue;
426
+ }
427
+ if (!currentAgent) {
428
+ fail(currentLocale === "pt" ? "Sem agente activo." : "No active agent.");
429
+ continue;
430
+ }
431
+ await persistAgent(opts.cwd, currentAgent);
432
+ const ctx = await buildBootstrapContext(opts.cwd);
433
+ const prompt = buildAgentPrompt(line, ctx, currentLocale);
434
+ try {
435
+ await streamRun(currentAgent, prompt, currentLocale);
436
+ }
437
+ catch (err) {
438
+ if (err instanceof CursorAgentError) {
439
+ fail(err.message);
440
+ turn++;
441
+ continue;
442
+ }
443
+ throw err;
444
+ }
445
+ console.log("\n");
446
+ await persistAgent(opts.cwd, currentAgent);
447
+ turn++;
448
+ }
449
+ ok(t().goodbye);
450
+ }
@@ -0,0 +1,11 @@
1
+ export type ScanFinding = {
2
+ file: string;
3
+ line: number;
4
+ rule: string;
5
+ message: string;
6
+ severity: "error" | "warn";
7
+ };
8
+ export declare function runScan(cwd: string, options?: {
9
+ quiet?: boolean;
10
+ }): Promise<ScanFinding[]>;
11
+ //# sourceMappingURL=scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC;CAC5B,CAAC;AA2EF,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA6ChG"}
@@ -0,0 +1,119 @@
1
+ import { readdirSync, readFileSync, statSync } from "fs";
2
+ import { join, relative } from "path";
3
+ import { fail, heading, info, ok, warn } from "../lib/output.js";
4
+ const SKIP_DIRS = new Set([
5
+ "node_modules",
6
+ ".git",
7
+ ".next",
8
+ "dist",
9
+ "build",
10
+ ".turbo",
11
+ "coverage",
12
+ ]);
13
+ const RULES = [
14
+ {
15
+ id: "deprecated-rail",
16
+ pattern: /paymentMethodKind\s*[:=]\s*["']reference["']/,
17
+ message: 'Use rail "emis_ref" or "ticket" instead of deprecated "reference"',
18
+ severity: "warn",
19
+ },
20
+ {
21
+ id: "missing-signature",
22
+ pattern: /operationCode|"code"/,
23
+ message: "Ticket webhook handler detected — ensure x-signature HMAC is verified",
24
+ severity: "warn",
25
+ ignore: /verifyTicketSignature|x-signature|validateTicketSignature/,
26
+ },
27
+ {
28
+ id: "hardcoded-secret",
29
+ pattern: /EKWANZA_(CLIENT_SECRET|API_KEY)\s*=\s*["'][^"']+["']/,
30
+ message: "Never hardcode e-Kwanza secrets in source files",
31
+ severity: "error",
32
+ },
33
+ {
34
+ id: "wrong-prefix-gpo",
35
+ pattern: /EKWANZA_PAYMENT_METHOD_GPO\s*=\s*["'](?!GPO_)/,
36
+ message: "GPO payment method should use GPO_ prefix",
37
+ severity: "warn",
38
+ },
39
+ {
40
+ id: "legacy-webhook-status",
41
+ pattern: /operationStatus\s*===\s*["']1["']/,
42
+ message: "operationStatus is numeric (1 = paid), not string",
43
+ severity: "warn",
44
+ },
45
+ ];
46
+ const EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".env", ".env.local"]);
47
+ function walk(dir, root, files = []) {
48
+ let entries;
49
+ try {
50
+ entries = readdirSync(dir);
51
+ }
52
+ catch {
53
+ return files;
54
+ }
55
+ for (const name of entries) {
56
+ if (SKIP_DIRS.has(name))
57
+ continue;
58
+ const full = join(dir, name);
59
+ try {
60
+ const st = statSync(full);
61
+ if (st.isDirectory())
62
+ walk(full, root, files);
63
+ else if (EXTENSIONS.has(name.slice(name.lastIndexOf(".")) || name))
64
+ files.push(full);
65
+ }
66
+ catch {
67
+ /* skip unreadable */
68
+ }
69
+ }
70
+ return files;
71
+ }
72
+ export async function runScan(cwd, options) {
73
+ if (!options?.quiet)
74
+ heading("Ekz scan");
75
+ const files = walk(cwd, cwd);
76
+ if (!options?.quiet)
77
+ info(`Scanning ${files.length} files…`);
78
+ const findings = [];
79
+ for (const file of files) {
80
+ let content;
81
+ try {
82
+ content = readFileSync(file, "utf8");
83
+ }
84
+ catch {
85
+ continue;
86
+ }
87
+ const rel = relative(cwd, file);
88
+ const lines = content.split("\n");
89
+ for (const rule of RULES) {
90
+ if (rule.ignore && rule.ignore.test(content))
91
+ continue;
92
+ lines.forEach((line, i) => {
93
+ if (rule.pattern.test(line)) {
94
+ findings.push({
95
+ file: rel,
96
+ line: i + 1,
97
+ rule: rule.id,
98
+ message: rule.message,
99
+ severity: rule.severity,
100
+ });
101
+ }
102
+ });
103
+ }
104
+ }
105
+ if (findings.length === 0) {
106
+ if (!options?.quiet)
107
+ ok("No integration issues detected");
108
+ }
109
+ else if (!options?.quiet) {
110
+ for (const f of findings) {
111
+ const fn = f.severity === "error" ? fail : warn;
112
+ fn(`${f.file}:${f.line} [${f.rule}] ${f.message}`);
113
+ }
114
+ const errors = findings.filter((f) => f.severity === "error").length;
115
+ if (errors > 0)
116
+ process.exitCode = 1;
117
+ }
118
+ return findings;
119
+ }
@@ -0,0 +1,10 @@
1
+ type Rail = "gpo" | "ticket";
2
+ export declare function runWebhookTest(url: string, rail: Rail, asJson: boolean): Promise<{
3
+ url: string;
4
+ rail: Rail;
5
+ status: number;
6
+ response: unknown;
7
+ normalized: import("@ekzs/connect").EkzNormalizedWebhook;
8
+ }>;
9
+ export {};
10
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/commands/webhook.ts"],"names":[],"mappings":"AAOA,KAAK,IAAI,GAAG,KAAK,GAAG,QAAQ,CAAC;AAE7B,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO;;;;;;GA4C5E"}
@@ -0,0 +1,42 @@
1
+ import { SAMPLE_GPO_WEBHOOK, SAMPLE_TICKET_WEBHOOK, normalizeWebhookPayload, } from "@ekzs/connect";
2
+ import { heading, info, json, ok } from "../lib/output.js";
3
+ export async function runWebhookTest(url, rail, asJson) {
4
+ const payload = rail === "gpo" ? SAMPLE_GPO_WEBHOOK : SAMPLE_TICKET_WEBHOOK;
5
+ heading(`Webhook test → ${url}`);
6
+ info(`Rail: ${rail}`);
7
+ const normalized = normalizeWebhookPayload(payload);
8
+ info(`Normalized locally: status=${normalized.status}, rail=${normalized.rail}`);
9
+ let response;
10
+ try {
11
+ response = await fetch(url, {
12
+ method: "POST",
13
+ headers: { "Content-Type": "application/json" },
14
+ body: JSON.stringify(payload),
15
+ });
16
+ }
17
+ catch (err) {
18
+ throw new Error(`Request failed: ${err instanceof Error ? err.message : String(err)}`);
19
+ }
20
+ const text = await response.text();
21
+ let body = text;
22
+ try {
23
+ body = JSON.parse(text);
24
+ }
25
+ catch {
26
+ /* keep text */
27
+ }
28
+ const result = {
29
+ url,
30
+ rail,
31
+ status: response.status,
32
+ response: body,
33
+ normalized,
34
+ };
35
+ if (asJson) {
36
+ json(result);
37
+ return result;
38
+ }
39
+ ok(`HTTP ${response.status}`);
40
+ info(typeof body === "string" ? body : JSON.stringify(body));
41
+ return result;
42
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}