@kraken-ai/platform 0.0.4 → 0.0.6

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.
package/dist/cli.js CHANGED
@@ -1,39 +1,328 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import * as z12 from "zod";
4
+ import { execFile } from "child_process";
5
+ import { promisify } from "util";
6
+ import * as z11 from "zod";
7
+
8
+ // src/internal/secret-string.ts
9
+ import { inspect } from "util";
10
+ var SecretString = class {
11
+ #value;
12
+ constructor(value) {
13
+ this.#value = value;
14
+ }
15
+ unmasked() {
16
+ return this.#value;
17
+ }
18
+ toJSON() {
19
+ return "[REDACTED]";
20
+ }
21
+ toString() {
22
+ return "[REDACTED]";
23
+ }
24
+ [Symbol.toPrimitive]() {
25
+ return "[REDACTED]";
26
+ }
27
+ [inspect.custom]() {
28
+ return "SecretString([REDACTED])";
29
+ }
30
+ };
31
+ var unwrapSecret = (value) => value instanceof SecretString ? value.unmasked() : value;
32
+
33
+ // src/cli/api-keys.ts
34
+ var DURATION_RE = /^(\d+)(h|d|y)$/;
35
+ var parseDuration = (input) => {
36
+ const match = DURATION_RE.exec(input);
37
+ if (!match) return null;
38
+ const amount = Number(match[1]);
39
+ const unit = match[2];
40
+ const now = /* @__PURE__ */ new Date();
41
+ switch (unit) {
42
+ case "h":
43
+ return new Date(now.getTime() + amount * 36e5);
44
+ case "d":
45
+ return new Date(now.getTime() + amount * 864e5);
46
+ case "y":
47
+ return new Date(now.getTime() + amount * 365.25 * 864e5);
48
+ default:
49
+ return null;
50
+ }
51
+ };
52
+ var VALID_ENVIRONMENTS = /* @__PURE__ */ new Set(["dev", "staging", "prod"]);
53
+ var getAuthHeaders = (apiKey) => {
54
+ const secret = new SecretString(apiKey);
55
+ return { Authorization: `Bearer ${unwrapSecret(secret)}`, "Content-Type": "application/json" };
56
+ };
57
+ var collectErrorMessages = (err) => {
58
+ const parts = [];
59
+ let current = err;
60
+ while (current instanceof Error) {
61
+ if (current.message) parts.push(current.message);
62
+ current = current.cause;
63
+ }
64
+ return parts.join(": ");
65
+ };
66
+ var formatNetworkError = (err, baseUrl) => {
67
+ const msg = collectErrorMessages(err);
68
+ if (msg.includes("unknown scheme")) {
69
+ return `Invalid platform URL "${baseUrl}" \u2014 must start with https:// (or http:// for local dev).
70
+ Run \`kraken login\` to update your credentials.`;
71
+ }
72
+ if (msg.includes("ECONNREFUSED")) {
73
+ return `Could not connect to ${baseUrl} \u2014 is the platform running?
74
+ Check the URL or run \`kraken login\` to update it.`;
75
+ }
76
+ if (msg.includes("ENOTFOUND")) {
77
+ return `Could not resolve hostname for ${baseUrl} \u2014 check the URL.
78
+ Run \`kraken login\` to update your credentials.`;
79
+ }
80
+ if (msg.includes("abort") || msg.includes("TIMEOUT")) {
81
+ return `Request to ${baseUrl} timed out.
82
+ Check your network connection or try again.`;
83
+ }
84
+ return `Could not reach ${baseUrl}. Check that the platform is running and the URL is correct.
85
+ Run \`kraken login\` to update your credentials.`;
86
+ };
87
+ var normalizeBaseUrl = (baseUrl) => {
88
+ if (!/^https?:\/\//.test(baseUrl)) {
89
+ return `https://${baseUrl}`;
90
+ }
91
+ return baseUrl;
92
+ };
93
+ var fetchApi = async (deps, url, baseUrl, init) => {
94
+ const normalizedBase = normalizeBaseUrl(baseUrl);
95
+ const normalizedUrl = baseUrl !== normalizedBase ? url.replace(baseUrl, normalizedBase) : url;
96
+ try {
97
+ return await deps.fetch(normalizedUrl, init);
98
+ } catch (err) {
99
+ deps.log(`Error: ${formatNetworkError(err, normalizedBase)}`);
100
+ return null;
101
+ }
102
+ };
103
+ var handleCreate = async (parsed, deps) => {
104
+ const creds = deps.loadCredentials();
105
+ if (!creds?.apiKey || !creds?.baseUrl) {
106
+ deps.log("Error: not logged in. Run `kraken login` first.");
107
+ return 1;
108
+ }
109
+ const name = parsed.flags.name;
110
+ if (!name) {
111
+ deps.log(
112
+ "Error: --name is required.\n Usage: kraken api-keys create --name <name> [--env dev] [--expires 90d]"
113
+ );
114
+ return 1;
115
+ }
116
+ const environment = parsed.flags.env ?? "dev";
117
+ if (!VALID_ENVIRONMENTS.has(environment)) {
118
+ deps.log(`Error: invalid environment "${environment}". Must be one of: dev, staging, prod.`);
119
+ return 1;
120
+ }
121
+ let expiresAt;
122
+ if (parsed.flags.expires) {
123
+ const date = parseDuration(parsed.flags.expires);
124
+ if (!date) {
125
+ deps.log(
126
+ `Error: invalid duration "${parsed.flags.expires}". Use format like 90d, 24h, or 1y.`
127
+ );
128
+ return 1;
129
+ }
130
+ expiresAt = date.toISOString();
131
+ }
132
+ const res = await fetchApi(deps, `${creds.baseUrl}/api/v1/api-keys`, creds.baseUrl, {
133
+ method: "POST",
134
+ headers: getAuthHeaders(creds.apiKey),
135
+ body: JSON.stringify({ name, environment, expiresAt })
136
+ });
137
+ if (!res) return 1;
138
+ if (!res.ok) {
139
+ const body = await res.json();
140
+ deps.log(`Error: ${body.error ?? `request failed (${String(res.status)})`}`);
141
+ return 1;
142
+ }
143
+ const data = await res.json();
144
+ deps.log(`
145
+ API Key created successfully.
146
+ `);
147
+ deps.log(` Name: ${data.name}`);
148
+ deps.log(` Env: ${data.environment}`);
149
+ deps.log(` Key: ${data.key}`);
150
+ if (data.expiresAt) {
151
+ deps.log(` Expires: ${data.expiresAt}`);
152
+ }
153
+ deps.log(`
154
+ Copy this key now \u2014 it will not be shown again.
155
+ `);
156
+ return 0;
157
+ };
158
+ var handleList = async (parsed, deps) => {
159
+ const creds = deps.loadCredentials();
160
+ if (!creds?.apiKey || !creds?.baseUrl) {
161
+ deps.log("Error: not logged in. Run `kraken login` first.");
162
+ return 1;
163
+ }
164
+ const envFilter = parsed.flags.env;
165
+ const query = envFilter ? `?environment=${encodeURIComponent(envFilter)}` : "";
166
+ const res = await fetchApi(deps, `${creds.baseUrl}/api/v1/api-keys${query}`, creds.baseUrl, {
167
+ headers: getAuthHeaders(creds.apiKey)
168
+ });
169
+ if (!res) return 1;
170
+ if (!res.ok) {
171
+ const body = await res.json();
172
+ deps.log(`Error: ${body.error ?? `request failed (${String(res.status)})`}`);
173
+ return 1;
174
+ }
175
+ const keys = await res.json();
176
+ if (keys.length === 0) {
177
+ deps.log("No API keys found. Create one with: kraken api-keys create --name <name>");
178
+ return 0;
179
+ }
180
+ deps.log("\n API Keys:\n");
181
+ deps.log(
182
+ " ID Name Env Key Created Last Used Expires"
183
+ );
184
+ deps.log(` ${"\u2500".repeat(150)}`);
185
+ for (const k of keys) {
186
+ const expired = k.expired ? " (expired)" : "";
187
+ const lastUsed = k.lastUsed ? k.lastUsed.slice(0, 10) : "never";
188
+ const expires = k.expiresAt ? `${k.expiresAt.slice(0, 10)}${expired}` : "never";
189
+ deps.log(
190
+ ` ${k.id} ${k.name.padEnd(16)}${k.environment.padEnd(10)}${k.prefix.padEnd(23)}${k.createdAt.slice(0, 10).padEnd(20)}${lastUsed.padEnd(20)}${expires}`
191
+ );
192
+ }
193
+ deps.log("");
194
+ return 0;
195
+ };
196
+ var handleRevoke = async (parsed, deps) => {
197
+ const creds = deps.loadCredentials();
198
+ if (!creds?.apiKey || !creds?.baseUrl) {
199
+ deps.log("Error: not logged in. Run `kraken login` first.");
200
+ return 1;
201
+ }
202
+ const id = parsed.positional[1];
203
+ if (!id) {
204
+ deps.log("Error: key ID is required.\n Usage: kraken api-keys revoke <id>");
205
+ return 1;
206
+ }
207
+ const res = await fetchApi(
208
+ deps,
209
+ `${creds.baseUrl}/api/v1/api-keys/${encodeURIComponent(id)}`,
210
+ creds.baseUrl,
211
+ {
212
+ method: "DELETE",
213
+ headers: getAuthHeaders(creds.apiKey)
214
+ }
215
+ );
216
+ if (!res) return 1;
217
+ if (res.status === 404) {
218
+ deps.log("Error: API key not found.");
219
+ return 1;
220
+ }
221
+ if (!res.ok) {
222
+ const body = await res.json();
223
+ deps.log(`Error: ${body.error ?? `request failed (${String(res.status)})`}`);
224
+ return 1;
225
+ }
226
+ deps.log("API key revoked successfully.");
227
+ return 0;
228
+ };
229
+ var handleApiKeys = async (parsed, deps) => {
230
+ const subcommand = parsed.positional[0];
231
+ switch (subcommand) {
232
+ case "create":
233
+ return handleCreate(parsed, deps);
234
+ case "list":
235
+ return handleList(parsed, deps);
236
+ case "revoke":
237
+ return handleRevoke(parsed, deps);
238
+ default:
239
+ deps.log(
240
+ `kraken api-keys \u2014 Manage API keys
241
+
242
+ Commands:
243
+ create --name <name> [--env dev] [--expires 90d] Create a new API key
244
+ list [--env dev|staging|prod] List API keys
245
+ revoke <id> Revoke an API key
246
+ `
247
+ );
248
+ return 0;
249
+ }
250
+ };
5
251
 
6
252
  // src/cli/codegen.ts
7
253
  import fs from "fs";
8
254
  import path from "path";
9
255
 
10
- // src/cli/log.ts
11
- var supportsColor = !("NO_COLOR" in process.env) && process.env.FORCE_COLOR !== "0" && (process.stderr.isTTY ?? false);
12
- var code = (open, close) => {
13
- const openStr = `\x1B[${open}m`;
14
- const closeStr = `\x1B[${close}m`;
15
- return (s) => supportsColor ? `${openStr}${s}${closeStr}` : s;
256
+ // ../../../private-packages/platform-core/dist/qualified-name.js
257
+ var parse = (qualified) => {
258
+ const slashIndex = qualified.indexOf("/");
259
+ if (slashIndex === -1) {
260
+ throw new Error(`Expected qualified name with "/" separator, got: "${qualified}"`);
261
+ }
262
+ if (qualified.indexOf("/", slashIndex + 1) !== -1) {
263
+ throw new Error(`Expected exactly one "/" in qualified name, got: "${qualified}"`);
264
+ }
265
+ const repoName = qualified.slice(0, slashIndex);
266
+ const primitiveName = qualified.slice(slashIndex + 1);
267
+ if (!repoName || !primitiveName) {
268
+ throw new Error(`Both repo and primitive segments must be non-empty, got: "${qualified}"`);
269
+ }
270
+ return { repoName, primitiveName };
16
271
  };
17
- var bold = code(1, 22);
18
- var dim = code(2, 22);
19
- var red = code(31, 39);
20
- var yellow = code(33, 39);
21
- var green = code(32, 39);
22
- var cyan = code(36, 39);
23
- var warn = (msg) => {
24
- process.stderr.write(` ${dim("[kraken-ai:")} ${yellow("warn")}${dim("]")} ${msg}
25
- `);
272
+ var isQualified = (name) => {
273
+ const slashIndex = name.indexOf("/");
274
+ if (slashIndex === -1 || slashIndex === 0 || slashIndex === name.length - 1)
275
+ return false;
276
+ return name.indexOf("/", slashIndex + 1) === -1;
26
277
  };
27
278
 
28
- // src/cli/validate.ts
29
- var ENTITY_NAME_REGEX = /^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$/;
30
- var isValidEntityName = (name) => name.length === 1 ? /^[a-z0-9]$/.test(name) : ENTITY_NAME_REGEX.test(name);
279
+ // ../../../private-packages/platform-core/dist/types/environment.js
280
+ import * as z from "zod";
281
+ var environmentSchema = z.enum(["dev", "staging", "prod"]);
282
+
283
+ // ../../../private-packages/platform-core/dist/types/identity.js
284
+ import * as z2 from "zod";
285
+ var jitPolicySchema = z2.enum(["auto-approve", "policy-based", "require-approval"]);
286
+ var identityConfigSchema = z2.object({
287
+ basePermissions: z2.array(z2.string()),
288
+ requestablePermissions: z2.array(z2.string()).optional(),
289
+ jitPolicy: jitPolicySchema.optional(),
290
+ maxJitDurationMinutes: z2.number().int().positive().optional()
291
+ });
292
+
293
+ // ../../../private-packages/platform-core/dist/types/notifications.js
294
+ import * as z3 from "zod";
295
+ var notificationConfigSchema = z3.object({
296
+ slack: z3.string().optional(),
297
+ onSuccess: z3.boolean().optional(),
298
+ onFailure: z3.boolean().optional(),
299
+ onTimeout: z3.boolean().optional()
300
+ });
301
+
302
+ // ../../../private-packages/platform-core/dist/types/platform-agent.js
303
+ import * as z7 from "zod";
304
+
305
+ // ../../../private-packages/platform-core/dist/validate.js
306
+ var PRIMITIVE_NAME_REGEX = /^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$/;
307
+ var isValidPrimitiveName = (name) => name.length === 1 ? /^[a-z0-9]$/.test(name) : PRIMITIVE_NAME_REGEX.test(name);
31
308
  var SKILL_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,62}[a-zA-Z0-9]$/;
32
309
  var isValidSkillName = (basename) => basename.length === 1 ? /^[a-zA-Z0-9]$/.test(basename) : SKILL_NAME_REGEX.test(basename);
33
310
  var isValidSkillId = (id) => {
34
311
  const base = id.endsWith(".md") ? id.slice(0, -3) : id;
35
312
  return isValidSkillName(base);
36
313
  };
314
+ var isValidQualifiedName = (name) => {
315
+ if (!isQualified(name))
316
+ return false;
317
+ const { repoName, primitiveName } = parse(name);
318
+ return isValidPrimitiveName(repoName) && isValidPrimitiveName(primitiveName);
319
+ };
320
+ var isValidQualifiedSkillId = (id) => {
321
+ if (!isQualified(id))
322
+ return false;
323
+ const { repoName, primitiveName } = parse(id);
324
+ return isValidPrimitiveName(repoName) && isValidSkillId(primitiveName);
325
+ };
37
326
  var TOOL_NAME_REGEX = /^[a-z][a-z0-9_-]{0,62}[a-z0-9]$/;
38
327
  var isValidToolName = (name) => name.length === 1 ? /^[a-z]$/.test(name) : TOOL_NAME_REGEX.test(name);
39
328
  var PROPERTY_NAME_REGEX = /^[a-zA-Z_$][a-zA-Z0-9_$]{0,254}$/;
@@ -47,38 +336,168 @@ var FORBIDDEN_PROPS = /* @__PURE__ */ new Set([
47
336
  "__lookupSetter__"
48
337
  ]);
49
338
  var isValidPropertyName = (name) => PROPERTY_NAME_REGEX.test(name) && !FORBIDDEN_PROPS.has(name);
339
+
340
+ // ../../../private-packages/platform-core/dist/types/resources.js
341
+ import * as z4 from "zod";
342
+ var resourceLimitsSchema = z4.object({
343
+ maxTokens: z4.number().int().positive().optional(),
344
+ maxCostUsd: z4.number().positive().optional(),
345
+ timeoutSeconds: z4.number().int().positive().optional()
346
+ });
347
+ var retryPolicySchema = z4.object({
348
+ maxAttempts: z4.number().int().min(1).optional(),
349
+ backoffSeconds: z4.number().positive().optional()
350
+ });
351
+ var concurrencyPolicySchema = z4.object({
352
+ maxParallelRuns: z4.number().int().min(1).optional()
353
+ });
354
+
355
+ // ../../../private-packages/platform-core/dist/types/team.js
356
+ import * as z5 from "zod";
357
+ var teamConfigSchema = z5.object({
358
+ members: z5.array(z5.string()).min(1),
359
+ maxConcurrentWorkers: z5.number().int().min(1).optional(),
360
+ maxTokenBudgetPerWorker: z5.number().int().positive().optional(),
361
+ maxDurationPerWorker: z5.number().positive().optional()
362
+ });
363
+
364
+ // ../../../private-packages/platform-core/dist/types/trigger.js
365
+ import * as z6 from "zod";
366
+ var cronTriggerSchema = z6.object({
367
+ type: z6.literal("cron"),
368
+ expression: z6.string(),
369
+ timezone: z6.string().optional()
370
+ });
371
+ var webhookTriggerSchema = z6.object({
372
+ type: z6.literal("webhook"),
373
+ path: z6.string().startsWith("/"),
374
+ method: z6.enum(["POST", "GET"]).optional()
375
+ });
376
+ var eventTriggerSchema = z6.object({
377
+ type: z6.literal("event"),
378
+ source: z6.string(),
379
+ event: z6.string()
380
+ });
381
+ var apiTriggerSchema = z6.object({ type: z6.literal("api") });
382
+ var manualTriggerSchema = z6.object({ type: z6.literal("manual") });
383
+ var triggerConfigSchema = z6.discriminatedUnion("type", [
384
+ cronTriggerSchema,
385
+ webhookTriggerSchema,
386
+ eventTriggerSchema,
387
+ apiTriggerSchema,
388
+ manualTriggerSchema
389
+ ]);
390
+
391
+ // ../../../private-packages/platform-core/dist/types/platform-agent.js
392
+ var thinkingLevelSchema = z7.enum(["low", "medium", "high"]);
393
+ var logLevelSchema = z7.enum(["silent", "debug", "info", "warn", "error"]);
394
+ var agentDefinitionSchema = z7.object({
395
+ name: z7.string().min(1),
396
+ model: z7.string().min(1),
397
+ instructions: z7.string().min(1),
398
+ description: z7.string().optional(),
399
+ skills: z7.array(z7.string()).optional(),
400
+ temperature: z7.number().min(0).max(2).optional(),
401
+ allowTemperatureOverride: z7.boolean().optional(),
402
+ maxOutputTokens: z7.number().int().positive().optional(),
403
+ thinkingLevel: thinkingLevelSchema.optional(),
404
+ logLevel: logLevelSchema.optional()
405
+ }).strict();
406
+ var platformAgentConfigSchema = z7.object({
407
+ agent: agentDefinitionSchema,
408
+ connectors: z7.array(z7.string()).optional(),
409
+ triggers: z7.array(triggerConfigSchema),
410
+ identity: identityConfigSchema.optional(),
411
+ resources: resourceLimitsSchema.optional(),
412
+ retries: retryPolicySchema.optional(),
413
+ concurrency: concurrencyPolicySchema.optional(),
414
+ team: teamConfigSchema.optional(),
415
+ notifications: notificationConfigSchema.optional(),
416
+ environment: environmentSchema.optional(),
417
+ actions: z7.array(z7.string()).optional()
418
+ }).strict().superRefine((data, ctx) => {
419
+ for (const member of data.team?.members ?? []) {
420
+ const valid = isQualified(member) ? isValidQualifiedName(member) : isValidPrimitiveName(member);
421
+ if (!valid) {
422
+ ctx.addIssue({
423
+ code: z7.ZodIssueCode.custom,
424
+ path: ["team", "members"],
425
+ message: `Invalid team member name: "${member}"`
426
+ });
427
+ }
428
+ }
429
+ for (const connector of data.connectors ?? []) {
430
+ const valid = isQualified(connector) ? isValidQualifiedName(connector) : isValidPrimitiveName(connector);
431
+ if (!valid) {
432
+ ctx.addIssue({
433
+ code: z7.ZodIssueCode.custom,
434
+ path: ["connectors"],
435
+ message: `Invalid connector name: "${connector}"`
436
+ });
437
+ }
438
+ }
439
+ for (const skill of data.agent.skills ?? []) {
440
+ const valid = isQualified(skill) ? isValidQualifiedSkillId(skill) : isValidSkillId(skill);
441
+ if (!valid) {
442
+ ctx.addIssue({
443
+ code: z7.ZodIssueCode.custom,
444
+ path: ["agent", "skills"],
445
+ message: `Invalid skill ID: "${skill}"`
446
+ });
447
+ }
448
+ }
449
+ for (const action of data.actions ?? []) {
450
+ const valid = isQualified(action) ? isValidQualifiedName(action) : isValidPrimitiveName(action);
451
+ if (!valid) {
452
+ ctx.addIssue({
453
+ code: z7.ZodIssueCode.custom,
454
+ path: ["actions"],
455
+ message: `Invalid action name: "${action}"`
456
+ });
457
+ }
458
+ }
459
+ });
460
+
461
+ // ../../../private-packages/platform-core/dist/types/skill.js
462
+ import * as z8 from "zod";
463
+ var platformSkillInputSchema = z8.object({
464
+ name: z8.string().min(1),
465
+ description: z8.string().optional()
466
+ }).strict();
467
+
468
+ // src/cli/validate.ts
50
469
  var MAX_SCHEMA_DEPTH = 20;
51
470
  var MAX_OBJECT_BREADTH = 200;
52
471
  var validateSchemaBundle = (bundle) => {
53
472
  for (const agent of bundle.agents) {
54
- assertValidEntity("agent id", agent.id);
473
+ assertValidPrimitive("agent id", agent.id);
55
474
  validateJsonSchemaProperties("agent input schema", agent.input, 0);
56
475
  validateJsonSchemaProperties("agent output schema", agent.output, 0);
57
476
  if (agent.actions) {
58
477
  for (const [actionName, actionSchema] of Object.entries(agent.actions)) {
59
- assertValidEntity("agent action name", actionName);
478
+ assertValidPrimitive("agent action name", actionName);
60
479
  validateJsonSchemaProperties("agent action schema", actionSchema, 0);
61
480
  }
62
481
  }
63
482
  }
64
483
  for (const query of bundle.queries) {
65
- assertValidEntity("query name", query.name);
484
+ assertValidPrimitive("query name", query.name);
66
485
  validateJsonSchemaProperties("query params schema", query.params, 0);
67
486
  for (const col of query.columns) {
68
487
  assertValidProperty("query column name", col.name);
69
488
  }
70
489
  }
71
490
  for (const connector of bundle.connectors) {
72
- assertValidEntity("connector id", connector.id);
491
+ assertValidPrimitive("connector id", connector.id);
73
492
  for (const tool of connector.tools) {
74
493
  assertValidTool("connector tool name", tool.name);
75
494
  validateJsonSchemaProperties("connector tool parameters", tool.parameters, 0);
76
495
  }
77
496
  }
78
497
  for (const pipeline of bundle.pipelines) {
79
- assertValidEntity("pipeline name", pipeline.pipeline);
498
+ assertValidPrimitive("pipeline name", pipeline.pipeline);
80
499
  for (const query of pipeline.queries) {
81
- assertValidEntity("pipeline query name", query.name);
500
+ assertValidPrimitive("pipeline query name", query.name);
82
501
  validatePipelineSchema("pipeline query params", query.params, 0);
83
502
  validatePipelineSchema("pipeline query returns", query.returns, 0);
84
503
  }
@@ -86,32 +505,34 @@ var validateSchemaBundle = (bundle) => {
86
505
  for (const skill of bundle.skills) {
87
506
  assertValidSkill("skill id", skill.id);
88
507
  }
508
+ for (const action of bundle.actions) {
509
+ assertValidPrimitive("action id", action.id);
510
+ validateJsonSchemaProperties("action schema", action.schema, 0);
511
+ }
89
512
  };
90
- var assertValidEntity = (entityType, value) => {
91
- if (!isValidEntityName(value)) {
513
+ var assertValidPrimitive = (label, value) => {
514
+ if (!isValidPrimitiveName(value) && !isValidQualifiedName(value)) {
92
515
  throw new Error(
93
- `Invalid remote schema: ${entityType} "${value}". Must match ${ENTITY_NAME_REGEX}`
516
+ `Invalid remote schema: ${label} "${value}". Must match ${PRIMITIVE_NAME_REGEX} or repo/primitive format`
94
517
  );
95
518
  }
96
519
  };
97
- var assertValidTool = (entityType, value) => {
520
+ var assertValidTool = (label, value) => {
98
521
  if (!isValidToolName(value)) {
99
- throw new Error(
100
- `Invalid remote schema: ${entityType} "${value}". Must match ${TOOL_NAME_REGEX}`
101
- );
522
+ throw new Error(`Invalid remote schema: ${label} "${value}". Must match ${TOOL_NAME_REGEX}`);
102
523
  }
103
524
  };
104
- var assertValidProperty = (entityType, value) => {
525
+ var assertValidProperty = (label, value) => {
105
526
  if (!isValidPropertyName(value)) {
106
527
  throw new Error(
107
- `Invalid remote schema: ${entityType} "${value}". Must match ${PROPERTY_NAME_REGEX} and not be a forbidden property`
528
+ `Invalid remote schema: ${label} "${value}". Must match ${PROPERTY_NAME_REGEX} and not be a forbidden property`
108
529
  );
109
530
  }
110
531
  };
111
- var assertValidSkill = (entityType, value) => {
112
- if (!isValidSkillId(value)) {
532
+ var assertValidSkill = (label, value) => {
533
+ if (!isValidSkillId(value) && !isValidQualifiedSkillId(value)) {
113
534
  throw new Error(
114
- `Invalid remote schema: ${entityType} "${value}". Must match ${SKILL_NAME_REGEX} (with optional .md extension)`
535
+ `Invalid remote schema: ${label} "${value}". Must match ${SKILL_NAME_REGEX} (with optional .md extension) or repo/skill format`
115
536
  );
116
537
  }
117
538
  };
@@ -197,14 +618,10 @@ var generateTypes = (schemas, projectRoot) => {
197
618
  if (!fs.existsSync(outDir)) {
198
619
  fs.mkdirSync(outDir, { recursive: true });
199
620
  }
200
- const stalePipelinesTs = path.join(outDir, "pipelines.ts");
201
- if (fs.existsSync(stalePipelinesTs)) {
202
- fs.rmSync(stalePipelinesTs);
203
- warn(
204
- "Migrated: removed .kraken-ai/pipelines.ts. Pipeline schemas now in pipelines.json + pipelines.d.ts."
205
- );
206
- }
207
- fs.writeFileSync(path.join(outDir, "agents.d.ts"), generateAgentsDts(schemas.agents));
621
+ fs.writeFileSync(
622
+ path.join(outDir, "agents.d.ts"),
623
+ generateAgentsDts(schemas.agents, schemas.actions)
624
+ );
208
625
  fs.writeFileSync(path.join(outDir, "queries.d.ts"), generateQueriesDts(schemas.queries));
209
626
  const connectorsDtsPath = path.join(outDir, "connectors.d.ts");
210
627
  if (schemas.connectors.length > 0) {
@@ -212,13 +629,20 @@ var generateTypes = (schemas, projectRoot) => {
212
629
  } else if (fs.existsSync(connectorsDtsPath)) {
213
630
  fs.rmSync(connectorsDtsPath);
214
631
  }
632
+ const actionsDtsPath = path.join(outDir, "actions.d.ts");
633
+ if (schemas.actions.length > 0) {
634
+ fs.writeFileSync(actionsDtsPath, generateActionsDts(schemas.actions));
635
+ } else if (fs.existsSync(actionsDtsPath)) {
636
+ fs.rmSync(actionsDtsPath);
637
+ }
215
638
  fs.writeFileSync(path.join(outDir, "pipelines.json"), generatePipelinesJson(schemas.pipelines));
216
639
  fs.writeFileSync(path.join(outDir, "pipelines.d.ts"), generatePipelinesDts(schemas.pipelines));
217
640
  const platformTypesDtsPath = path.join(outDir, "platform-types.d.ts");
218
641
  const platformTypesDts = generatePlatformTypesDts(
219
642
  schemas.agents,
220
643
  schemas.connectors,
221
- schemas.skills
644
+ schemas.skills,
645
+ schemas.actions
222
646
  );
223
647
  if (platformTypesDts) {
224
648
  fs.writeFileSync(platformTypesDtsPath, platformTypesDts);
@@ -229,21 +653,25 @@ var generateTypes = (schemas, projectRoot) => {
229
653
  path.join(outDir, "index.d.ts"),
230
654
  generateIndexDts(
231
655
  schemas.connectors.length > 0,
656
+ schemas.actions.length > 0,
232
657
  schemas.pipelines.length > 0,
233
658
  platformTypesDts != null
234
659
  )
235
660
  );
236
661
  fs.writeFileSync(path.join(outDir, "manifest.json"), JSON.stringify(schemas, null, 2));
237
662
  };
238
- var generateAgentsDts = (agents) => {
663
+ var generateAgentsDts = (agents, actions) => {
664
+ const actionsByName = new Map(actions.map((a) => [a.name, a]));
239
665
  const entries = agents.map((a) => {
240
666
  const input = jsonSchemaToTsType(a.input);
241
667
  const output = jsonSchemaToTsType(a.output);
242
- const actions = generateActionsType(a.actions);
668
+ const hasInlineActions = a.actions && Object.keys(a.actions).length > 0;
669
+ const agentActions = hasInlineActions ? a.actions : buildAgentActionsFromRefs(a, actionsByName);
670
+ const actionsType = generateActionsType(agentActions);
243
671
  return ` "${a.id}": {
244
672
  input: ${input}
245
673
  output: ${output}
246
- actions: ${actions}
674
+ actions: ${actionsType}
247
675
  }`;
248
676
  }).join("\n");
249
677
  return [
@@ -258,6 +686,19 @@ var generateAgentsDts = (agents) => {
258
686
  ``
259
687
  ].join("\n");
260
688
  };
689
+ var buildAgentActionsFromRefs = (agent, actionsByName) => {
690
+ const actionNames = agent.config?.actions;
691
+ if (!actionNames || actionNames.length === 0) return void 0;
692
+ const result = {};
693
+ for (const name of actionNames) {
694
+ const bareName = name.includes("/") ? name.slice(name.indexOf("/") + 1) : name;
695
+ const actionSchema = actionsByName.get(bareName) ?? actionsByName.get(name);
696
+ if (actionSchema) {
697
+ result[name] = actionSchema.schema;
698
+ }
699
+ }
700
+ return Object.keys(result).length > 0 ? result : void 0;
701
+ };
261
702
  var generateActionsType = (actions) => {
262
703
  if (!actions || Object.keys(actions).length === 0) {
263
704
  return "Record<string, never>";
@@ -265,6 +706,20 @@ var generateActionsType = (actions) => {
265
706
  const entries = Object.entries(actions).map(([name, schema]) => `"${name}": ${jsonSchemaToTsType(schema)}`).join("; ");
266
707
  return `{ ${entries} }`;
267
708
  };
709
+ var generateActionsDts = (actions) => {
710
+ const entries = actions.map((a) => ` "${a.name}": ${jsonSchemaToTsType(a.schema)}`).join("\n");
711
+ return [
712
+ `// Auto-generated by \`kraken generate\` \u2014 do not edit manually`,
713
+ `export {};`,
714
+ ``,
715
+ `declare module "@kraken-ai/platform" {`,
716
+ ` interface ActionRegistry {`,
717
+ entries,
718
+ ` }`,
719
+ `}`,
720
+ ``
721
+ ].join("\n");
722
+ };
268
723
  var generateQueriesDts = (queries) => {
269
724
  const entries = queries.map((q) => {
270
725
  const params = jsonSchemaToTsType(q.params);
@@ -301,7 +756,7 @@ ${entries}
301
756
  }
302
757
  `;
303
758
  };
304
- var generateIndexDts = (hasConnectors, hasPipelines, hasPlatformTypes) => {
759
+ var generateIndexDts = (hasConnectors, hasActions, hasPipelines, hasPlatformTypes) => {
305
760
  let content = `// Auto-generated by \`kraken generate\` \u2014 do not edit manually
306
761
 
307
762
  /// <reference path="./agents.d.ts" />
@@ -309,6 +764,10 @@ export type { QueryRegistry } from "./queries"
309
764
  `;
310
765
  if (hasConnectors) {
311
766
  content += `export type { ConnectorId, ConnectorTools } from "./connectors"
767
+ `;
768
+ }
769
+ if (hasActions) {
770
+ content += `/// <reference path="./actions.d.ts" />
312
771
  `;
313
772
  }
314
773
  if (hasPipelines) {
@@ -321,7 +780,7 @@ export type { QueryRegistry } from "./queries"
321
780
  }
322
781
  return content;
323
782
  };
324
- var generatePlatformTypesDts = (agents, connectors, skills) => {
783
+ var generatePlatformTypesDts = (agents, connectors, skills, actions) => {
325
784
  const entries = [];
326
785
  if (agents.length > 0) {
327
786
  const ids = agents.map((a) => `"${a.id}"`).join(" | ");
@@ -335,6 +794,10 @@ var generatePlatformTypesDts = (agents, connectors, skills) => {
335
794
  const ids = skills.map((s) => `"${s.id}"`).join(" | ");
336
795
  entries.push(` skillId: ${ids};`);
337
796
  }
797
+ if (actions.length > 0) {
798
+ const ids = actions.map((a) => `"${a.name}"`).join(" | ");
799
+ entries.push(` actionId: ${ids};`);
800
+ }
338
801
  if (entries.length === 0) return null;
339
802
  return [
340
803
  `// Auto-generated by \`kraken generate\` \u2014 do not edit manually`,
@@ -514,168 +977,127 @@ var clearCredentials = () => {
514
977
 
515
978
  // src/cli/discover.ts
516
979
  import fs3 from "fs";
980
+ import { createRequire } from "module";
517
981
  import path3 from "path";
518
982
  import { pathToFileURL } from "url";
519
- import * as z10 from "zod";
520
-
521
- // src/agents/types/action.ts
522
- import * as z from "zod";
523
- var actionVariantConfigSchema = z.object({
524
- schema: z.record(z.string(), z.unknown()),
525
- webhook: z.string().url().optional(),
526
- hasHandler: z.boolean()
527
- });
528
- var actionsConfigSchema = z.object({
529
- variants: z.record(z.string(), actionVariantConfigSchema)
530
- }).strict();
531
-
532
- // src/agents/types/environment.ts
533
- import * as z2 from "zod";
534
- var environmentSchema = z2.enum(["dev", "staging", "prod"]);
535
-
536
- // src/agents/types/identity.ts
537
- import * as z3 from "zod";
538
- var jitPolicySchema = z3.enum(["auto-approve", "policy-based", "require-approval"]);
539
- var identityConfigSchema = z3.object({
540
- basePermissions: z3.array(z3.string()),
541
- requestablePermissions: z3.array(z3.string()).optional(),
542
- jitPolicy: jitPolicySchema.optional(),
543
- maxJitDurationMinutes: z3.number().int().positive().optional()
544
- });
545
-
546
- // src/agents/types/notifications.ts
547
- import * as z4 from "zod";
548
- var notificationConfigSchema = z4.object({
549
- slack: z4.string().optional(),
550
- onSuccess: z4.boolean().optional(),
551
- onFailure: z4.boolean().optional(),
552
- onTimeout: z4.boolean().optional()
553
- });
983
+ import * as z9 from "zod";
554
984
 
555
- // src/agents/types/platform-agent.ts
556
- import * as z8 from "zod";
985
+ // src/cli/log.ts
986
+ var supportsColor = !("NO_COLOR" in process.env) && process.env.FORCE_COLOR !== "0" && (process.stderr.isTTY ?? false);
987
+ var code = (open, close) => {
988
+ const openStr = `\x1B[${open}m`;
989
+ const closeStr = `\x1B[${close}m`;
990
+ return (s) => supportsColor ? `${openStr}${s}${closeStr}` : s;
991
+ };
992
+ var bold = code(1, 22);
993
+ var dim = code(2, 22);
994
+ var red = code(31, 39);
995
+ var yellow = code(33, 39);
996
+ var green = code(32, 39);
997
+ var cyan = code(36, 39);
998
+ var warn = (msg) => {
999
+ process.stderr.write(` ${dim("[kraken-ai:")} ${yellow("warn")}${dim("]")} ${msg}
1000
+ `);
1001
+ };
557
1002
 
558
- // src/agents/types/resources.ts
559
- import * as z5 from "zod";
560
- var resourceLimitsSchema = z5.object({
561
- maxTokens: z5.number().int().positive().optional(),
562
- maxCostUsd: z5.number().positive().optional(),
563
- timeoutSeconds: z5.number().int().positive().optional()
1003
+ // src/cli/discover.ts
1004
+ var MANIFEST_START = "---KRAKEN-MANIFEST-START---";
1005
+ var MANIFEST_END = "---KRAKEN-MANIFEST-END---";
1006
+ var skillEntrySchema = z9.object({
1007
+ name: z9.string().min(1),
1008
+ path: z9.string().min(1),
1009
+ content: z9.string()
564
1010
  });
565
- var retryPolicySchema = z5.object({
566
- maxAttempts: z5.number().int().min(1).optional(),
567
- backoffSeconds: z5.number().positive().optional()
1011
+ var toolSpecSchema = z9.object({
1012
+ name: z9.string(),
1013
+ description: z9.string(),
1014
+ parameters: z9.record(z9.string(), z9.unknown()).default({}),
1015
+ annotations: z9.object({
1016
+ readOnlyHint: z9.boolean().optional(),
1017
+ destructiveHint: z9.boolean().optional(),
1018
+ idempotentHint: z9.boolean().optional()
1019
+ }).optional()
568
1020
  });
569
- var concurrencyPolicySchema = z5.object({
570
- maxParallelRuns: z5.number().int().min(1).optional()
1021
+ var resourceSpecSchema = z9.object({
1022
+ name: z9.string(),
1023
+ uri: z9.string(),
1024
+ description: z9.string(),
1025
+ mimeType: z9.string().optional()
571
1026
  });
572
-
573
- // src/agents/types/team.ts
574
- import * as z6 from "zod";
575
- var teamConfigSchema = z6.object({
576
- members: z6.array(z6.string()).min(1),
577
- maxConcurrentWorkers: z6.number().int().min(1).optional(),
578
- maxTokenBudgetPerWorker: z6.number().int().positive().optional(),
579
- maxDurationPerWorker: z6.number().positive().optional()
1027
+ var promptSpecSchema = z9.object({
1028
+ name: z9.string(),
1029
+ description: z9.string(),
1030
+ arguments: z9.array(
1031
+ z9.object({
1032
+ name: z9.string(),
1033
+ description: z9.string().optional(),
1034
+ required: z9.boolean().optional()
1035
+ })
1036
+ ).optional()
580
1037
  });
581
-
582
- // src/agents/types/trigger.ts
583
- import * as z7 from "zod";
584
- var cronTriggerSchema = z7.object({
585
- type: z7.literal("cron"),
586
- expression: z7.string(),
587
- timezone: z7.string().optional()
588
- });
589
- var webhookTriggerSchema = z7.object({
590
- type: z7.literal("webhook"),
591
- path: z7.string().startsWith("/"),
592
- method: z7.enum(["POST", "GET"]).optional()
593
- });
594
- var eventTriggerSchema = z7.object({
595
- type: z7.literal("event"),
596
- source: z7.string(),
597
- event: z7.string()
1038
+ var connectorEntrySchema = z9.object({
1039
+ name: z9.string().min(1),
1040
+ path: z9.string().min(1),
1041
+ toolSpecs: z9.array(toolSpecSchema).default([]),
1042
+ resourceSpecs: z9.array(resourceSpecSchema).default([]),
1043
+ promptSpecs: z9.array(promptSpecSchema).default([])
598
1044
  });
599
- var apiTriggerSchema = z7.object({ type: z7.literal("api") });
600
- var manualTriggerSchema = z7.object({ type: z7.literal("manual") });
601
- var triggerConfigSchema = z7.discriminatedUnion("type", [
602
- cronTriggerSchema,
603
- webhookTriggerSchema,
604
- eventTriggerSchema,
605
- apiTriggerSchema,
606
- manualTriggerSchema
607
- ]);
608
-
609
- // src/agents/types/platform-agent.ts
610
- var thinkingLevelSchema = z8.enum(["low", "medium", "high"]);
611
- var logLevelSchema = z8.enum(["silent", "debug", "info", "warn", "error"]);
612
- var agentDefinitionSchema = z8.object({
613
- name: z8.string().min(1),
614
- model: z8.string().min(1),
615
- instructions: z8.string().min(1),
616
- description: z8.string().optional(),
617
- skills: z8.array(z8.string()).optional(),
618
- temperature: z8.number().min(0).max(2).optional(),
619
- allowTemperatureOverride: z8.boolean().optional(),
620
- maxOutputTokens: z8.number().int().positive().optional(),
621
- thinkingLevel: thinkingLevelSchema.optional(),
622
- logLevel: logLevelSchema.optional()
623
- }).strict();
624
- var platformAgentConfigSchema = z8.object({
625
- agent: agentDefinitionSchema,
626
- connectors: z8.array(z8.string()).optional(),
627
- triggers: z8.array(triggerConfigSchema),
628
- identity: identityConfigSchema.optional(),
629
- resources: resourceLimitsSchema.optional(),
630
- retries: retryPolicySchema.optional(),
631
- concurrency: concurrencyPolicySchema.optional(),
632
- team: teamConfigSchema.optional(),
633
- notifications: notificationConfigSchema.optional(),
634
- environment: environmentSchema.optional(),
635
- actions: actionsConfigSchema.optional()
636
- }).strict();
637
-
638
- // src/agents/types/skill.ts
639
- import * as z9 from "zod";
640
- var platformSkillInputSchema = z9.object({
1045
+ var agentEntrySchema = z9.object({
641
1046
  name: z9.string().min(1),
642
- description: z9.string().optional()
643
- }).strict();
644
-
645
- // src/cli/discover.ts
646
- var MANIFEST_START = "---KRAKEN-MANIFEST-START---";
647
- var MANIFEST_END = "---KRAKEN-MANIFEST-END---";
648
- var skillEntrySchema = z10.object({
649
- name: z10.string().min(1),
650
- path: z10.string().min(1),
651
- content: z10.string()
1047
+ entryPoint: z9.string().min(1),
1048
+ config: platformAgentConfigSchema
652
1049
  });
653
- var connectorEntrySchema = z10.object({
654
- name: z10.string().min(1),
655
- path: z10.string().min(1)
1050
+ var actionConfigSchema = z9.object({
1051
+ schema: z9.record(z9.string(), z9.unknown()),
1052
+ webhook: z9.string().url().optional(),
1053
+ hasHandler: z9.boolean()
656
1054
  });
657
- var agentEntrySchema = z10.object({
658
- name: z10.string().min(1),
659
- entryPoint: z10.string().min(1),
660
- config: platformAgentConfigSchema
1055
+ var actionEntrySchema = z9.object({
1056
+ name: z9.string().min(1),
1057
+ entryPoint: z9.string().min(1),
1058
+ config: actionConfigSchema
661
1059
  });
662
- var projectManifestSchema = z10.object({
663
- agents: z10.array(agentEntrySchema),
664
- skills: z10.array(skillEntrySchema),
665
- connectors: z10.array(connectorEntrySchema)
1060
+ var projectManifestSchema = z9.object({
1061
+ agents: z9.array(agentEntrySchema),
1062
+ skills: z9.array(skillEntrySchema),
1063
+ connectors: z9.array(connectorEntrySchema),
1064
+ actions: z9.array(actionEntrySchema).default([])
666
1065
  });
667
1066
  var isTsxAvailable = () => process.env.NODE_OPTIONS?.includes("tsx") === true;
1067
+ var tsxLoaded = false;
1068
+ var ensureTsxLoader = async (projectRoot) => {
1069
+ if (isTsxAvailable() || tsxLoaded) return true;
1070
+ if (!fs3.existsSync(path3.join(projectRoot, "node_modules", "tsx"))) return false;
1071
+ try {
1072
+ const require2 = createRequire(path3.join(projectRoot, "package.json"));
1073
+ const tsxEsmPath = require2.resolve("tsx/esm");
1074
+ await import(pathToFileURL(tsxEsmPath).href);
1075
+ tsxLoaded = true;
1076
+ return true;
1077
+ } catch {
1078
+ return false;
1079
+ }
1080
+ };
668
1081
  var isPlatformAgentExport = (value) => value != null && typeof value === "object" && value.__type === "PlatformAgent" && "config" in value;
669
1082
  var discoverAgents = async (projectRoot) => {
670
1083
  const distAgentsDir = path3.join(projectRoot, "dist", "agents");
671
1084
  const useDistAgents = fs3.existsSync(distAgentsDir) && fs3.statSync(distAgentsDir).isDirectory();
672
- const agentsDir = useDistAgents ? distAgentsDir : path3.join(projectRoot, "agents");
673
- const agentsDirPrefix = useDistAgents ? "dist/agents" : "agents";
1085
+ const agentsDir = useDistAgents ? distAgentsDir : path3.join(projectRoot, "src", "agents");
1086
+ const agentsDirPrefix = useDistAgents ? "dist/agents" : "src/agents";
674
1087
  if (!fs3.existsSync(agentsDir) || !fs3.statSync(agentsDir).isDirectory()) {
675
1088
  return [];
676
1089
  }
677
- const tsxEnabled = isTsxAvailable();
678
- const files = fs3.readdirSync(agentsDir).filter((f) => f.endsWith(".js") || f.endsWith(".mjs") || tsxEnabled && f.endsWith(".ts")).sort();
1090
+ const tsxEnabled = await ensureTsxLoader(projectRoot);
1091
+ const allFiles = fs3.readdirSync(agentsDir);
1092
+ const files = allFiles.filter((f) => f.endsWith(".js") || f.endsWith(".mjs") || tsxEnabled && f.endsWith(".ts")).sort();
1093
+ if (!tsxEnabled) {
1094
+ const skippedTs = allFiles.filter((f) => f.endsWith(".ts"));
1095
+ if (skippedTs.length > 0) {
1096
+ warn(
1097
+ `Found ${String(skippedTs.length)} .ts agent file(s) but tsx is not available. Install tsx as a devDependency or build your project first.`
1098
+ );
1099
+ }
1100
+ }
679
1101
  const seen = /* @__PURE__ */ new Map();
680
1102
  for (const file of files) {
681
1103
  const filePath = path3.join(agentsDir, file);
@@ -698,7 +1120,7 @@ var discoverAgents = async (projectRoot) => {
698
1120
  };
699
1121
  var MAX_SKILL_SIZE = 100 * 1024;
700
1122
  var discoverSkills = (projectRoot) => {
701
- const skillsDir = path3.join(projectRoot, "skills");
1123
+ const skillsDir = path3.join(projectRoot, "src", "skills");
702
1124
  if (!fs3.existsSync(skillsDir) || !fs3.statSync(skillsDir).isDirectory()) {
703
1125
  return [];
704
1126
  }
@@ -706,41 +1128,111 @@ var discoverSkills = (projectRoot) => {
706
1128
  const skills = [];
707
1129
  for (const file of files) {
708
1130
  if (!file.endsWith(".md")) {
709
- warn(`Skipping skills/${file}: skill files must have a .md extension.`);
1131
+ warn(`Skipping src/skills/${file}: skill files must have a .md extension.`);
710
1132
  continue;
711
1133
  }
712
1134
  const basename = file.replace(/\.md$/, "");
713
1135
  if (!isValidSkillName(basename)) {
714
1136
  warn(
715
- `Skipping skills/${file}: name must match [a-zA-Z0-9-] (letters, digits, hyphens only).`
1137
+ `Skipping src/skills/${file}: name must match [a-zA-Z0-9-] (letters, digits, hyphens only).`
716
1138
  );
717
1139
  continue;
718
1140
  }
719
1141
  const filePath = path3.join(skillsDir, file);
720
1142
  const stat = fs3.statSync(filePath);
721
1143
  if (stat.size > MAX_SKILL_SIZE) {
722
- warn(`Skipping skills/${file}: exceeds ${MAX_SKILL_SIZE} byte limit.`);
1144
+ warn(`Skipping src/skills/${file}: exceeds ${MAX_SKILL_SIZE} byte limit.`);
723
1145
  continue;
724
1146
  }
725
1147
  const content = fs3.readFileSync(filePath, "utf-8");
726
- skills.push({ name: file, path: `skills/${file}`, content });
1148
+ skills.push({ name: file, path: `src/skills/${file}`, content });
727
1149
  }
728
1150
  return skills;
729
1151
  };
730
- var discoverConnectors = (projectRoot) => {
1152
+ var isPlatformConnectorExport = (value) => value != null && typeof value === "object" && value.__type === "PlatformConnector";
1153
+ var serializeToolParameters = (input) => {
1154
+ try {
1155
+ return z9.toJSONSchema(input, { target: "draft-2020-12" });
1156
+ } catch {
1157
+ warn("Failed to serialize tool parameters via z.toJSONSchema(); falling back to {}");
1158
+ return {};
1159
+ }
1160
+ };
1161
+ var extractConnectorSpecs = (conn) => {
1162
+ const toolSpecs = [];
1163
+ if (conn.tools) {
1164
+ for (const [name, tool] of Object.entries(conn.tools)) {
1165
+ const spec = {
1166
+ name,
1167
+ description: tool.description,
1168
+ parameters: serializeToolParameters(tool.input)
1169
+ };
1170
+ if (tool.annotations) {
1171
+ const { readOnlyHint, destructiveHint, idempotentHint } = tool.annotations;
1172
+ if (readOnlyHint !== void 0 || destructiveHint !== void 0 || idempotentHint !== void 0) {
1173
+ spec.annotations = {
1174
+ ...readOnlyHint !== void 0 ? { readOnlyHint } : {},
1175
+ ...destructiveHint !== void 0 ? { destructiveHint } : {},
1176
+ ...idempotentHint !== void 0 ? { idempotentHint } : {}
1177
+ };
1178
+ }
1179
+ }
1180
+ toolSpecs.push(spec);
1181
+ }
1182
+ }
1183
+ const resourceSpecs = [];
1184
+ if (conn.resources) {
1185
+ for (const [name, resource] of Object.entries(conn.resources)) {
1186
+ const spec = {
1187
+ name,
1188
+ uri: resource.uri,
1189
+ description: resource.description
1190
+ };
1191
+ if (resource.mimeType) {
1192
+ spec.mimeType = resource.mimeType;
1193
+ }
1194
+ resourceSpecs.push(spec);
1195
+ }
1196
+ }
1197
+ const promptSpecs = [];
1198
+ if (conn.prompts) {
1199
+ for (const [name, prompt] of Object.entries(conn.prompts)) {
1200
+ const spec = {
1201
+ name,
1202
+ description: prompt.description
1203
+ };
1204
+ if (prompt.arguments) {
1205
+ spec.arguments = [...prompt.arguments];
1206
+ }
1207
+ promptSpecs.push(spec);
1208
+ }
1209
+ }
1210
+ return { toolSpecs, resourceSpecs, promptSpecs };
1211
+ };
1212
+ var findConnectorEntryPoint = (connDir, tsxEnabled) => {
1213
+ const candidates = ["index.js", "index.mjs"];
1214
+ if (tsxEnabled) candidates.push("index.ts");
1215
+ for (const candidate of candidates) {
1216
+ const filePath = path3.join(connDir, candidate);
1217
+ if (fs3.existsSync(filePath)) return filePath;
1218
+ }
1219
+ return void 0;
1220
+ };
1221
+ var discoverConnectors = async (projectRoot) => {
731
1222
  const distConnectorsDir = path3.join(projectRoot, "dist", "connectors");
732
1223
  const useDistConnectors = fs3.existsSync(distConnectorsDir) && fs3.statSync(distConnectorsDir).isDirectory();
733
- const connectorsDir = useDistConnectors ? distConnectorsDir : path3.join(projectRoot, "connectors");
734
- const connectorsDirPrefix = useDistConnectors ? "dist/connectors" : "connectors";
1224
+ const connectorsDir = useDistConnectors ? distConnectorsDir : path3.join(projectRoot, "src", "connectors");
1225
+ const connectorsDirPrefix = useDistConnectors ? "dist/connectors" : "src/connectors";
735
1226
  if (!fs3.existsSync(connectorsDir) || !fs3.statSync(connectorsDir).isDirectory()) {
736
1227
  return [];
737
1228
  }
1229
+ const tsxEnabled = await ensureTsxLoader(projectRoot);
738
1230
  const entries = fs3.readdirSync(connectorsDir, { withFileTypes: true });
739
1231
  const connectors = [];
740
1232
  for (const entry of entries) {
741
1233
  if (!entry.isDirectory()) continue;
742
1234
  const name = entry.name;
743
- if (!isValidEntityName(name)) {
1235
+ if (!isValidPrimitiveName(name)) {
744
1236
  warn(
745
1237
  `Skipping ${connectorsDirPrefix}/${name}: name must match [a-z0-9-] (lowercase, digits, hyphens only).`
746
1238
  );
@@ -755,23 +1247,109 @@ var discoverConnectors = (projectRoot) => {
755
1247
  if (!hasPackageJson && !hasDockerfile && !hasIndexJs && !hasIndexMjs && !hasIndexTs) {
756
1248
  continue;
757
1249
  }
758
- connectors.push({ name, path: `${connectorsDirPrefix}/${name}/` });
1250
+ const connectorPath = `${connectorsDirPrefix}/${name}/`;
1251
+ const entryPoint = findConnectorEntryPoint(connDir, tsxEnabled);
1252
+ if (!entryPoint) {
1253
+ connectors.push({
1254
+ name,
1255
+ path: connectorPath,
1256
+ toolSpecs: [],
1257
+ resourceSpecs: [],
1258
+ promptSpecs: []
1259
+ });
1260
+ continue;
1261
+ }
1262
+ try {
1263
+ const mod = await import(pathToFileURL(entryPoint).href);
1264
+ const exported = mod.default;
1265
+ if (isPlatformConnectorExport(exported)) {
1266
+ const specs = extractConnectorSpecs(exported);
1267
+ connectors.push({ name, path: connectorPath, ...specs });
1268
+ } else {
1269
+ connectors.push({
1270
+ name,
1271
+ path: connectorPath,
1272
+ toolSpecs: [],
1273
+ resourceSpecs: [],
1274
+ promptSpecs: []
1275
+ });
1276
+ }
1277
+ } catch (err) {
1278
+ warn(
1279
+ `Skipping introspection for ${connectorsDirPrefix}/${name}: ${err instanceof Error ? err.message : String(err)}`
1280
+ );
1281
+ connectors.push({
1282
+ name,
1283
+ path: connectorPath,
1284
+ toolSpecs: [],
1285
+ resourceSpecs: [],
1286
+ promptSpecs: []
1287
+ });
1288
+ }
759
1289
  }
760
1290
  return connectors.sort((a, b) => a.name.localeCompare(b.name));
761
1291
  };
1292
+ var isPlatformActionExport = (value) => value != null && typeof value === "object" && value.__type === "PlatformAction" && "name" in value && "config" in value;
1293
+ var discoverActions = async (projectRoot) => {
1294
+ const distActionsDir = path3.join(projectRoot, "dist", "actions");
1295
+ const useDistActions = fs3.existsSync(distActionsDir) && fs3.statSync(distActionsDir).isDirectory();
1296
+ const actionsDir = useDistActions ? distActionsDir : path3.join(projectRoot, "src", "actions");
1297
+ const actionsDirPrefix = useDistActions ? "dist/actions" : "src/actions";
1298
+ if (!fs3.existsSync(actionsDir) || !fs3.statSync(actionsDir).isDirectory()) {
1299
+ return [];
1300
+ }
1301
+ const tsxEnabled = await ensureTsxLoader(projectRoot);
1302
+ const allFiles = fs3.readdirSync(actionsDir);
1303
+ const files = allFiles.filter((f) => f.endsWith(".js") || f.endsWith(".mjs") || tsxEnabled && f.endsWith(".ts")).sort();
1304
+ if (!tsxEnabled) {
1305
+ const skippedTs = allFiles.filter((f) => f.endsWith(".ts"));
1306
+ if (skippedTs.length > 0) {
1307
+ warn(
1308
+ `Found ${String(skippedTs.length)} .ts action file(s) but tsx is not available. Install tsx as a devDependency or build your project first.`
1309
+ );
1310
+ }
1311
+ }
1312
+ const seen = /* @__PURE__ */ new Map();
1313
+ for (const file of files) {
1314
+ const filePath = path3.join(actionsDir, file);
1315
+ try {
1316
+ const mod = await import(pathToFileURL(filePath).href);
1317
+ const exported = mod.default;
1318
+ if (isPlatformActionExport(exported) && !seen.has(exported.name)) {
1319
+ if (!isValidPrimitiveName(exported.name)) {
1320
+ warn(
1321
+ `Skipping ${actionsDirPrefix}/${file}: action name "${exported.name}" must match [a-z0-9-] (lowercase, digits, hyphens only).`
1322
+ );
1323
+ continue;
1324
+ }
1325
+ seen.set(exported.name, {
1326
+ name: exported.name,
1327
+ entryPoint: `${actionsDirPrefix}/${file}`,
1328
+ config: exported.config
1329
+ });
1330
+ }
1331
+ } catch (err) {
1332
+ warn(
1333
+ `Skipping ${actionsDirPrefix}/${file}: ${err instanceof Error ? err.message : String(err)}`
1334
+ );
1335
+ }
1336
+ }
1337
+ return [...seen.values()];
1338
+ };
762
1339
  var discoverProject = async (projectRoot) => {
763
1340
  const discovered = await discoverAgents(projectRoot);
764
1341
  const agents = [];
765
1342
  for (const { config, entryPoint } of discovered) {
766
1343
  const name = config.agent.name;
767
- if (!isValidEntityName(name)) {
768
- throw new Error(`Invalid agent name: "${name}". Names must match ${ENTITY_NAME_REGEX}`);
1344
+ if (!isValidPrimitiveName(name)) {
1345
+ throw new Error(`Invalid agent name: "${name}". Names must match ${PRIMITIVE_NAME_REGEX}`);
769
1346
  }
770
1347
  agents.push({ name, entryPoint, config });
771
1348
  }
772
1349
  const skills = discoverSkills(projectRoot);
773
- const connectors = discoverConnectors(projectRoot);
774
- return { agents, skills, connectors };
1350
+ const connectors = await discoverConnectors(projectRoot);
1351
+ const actions = await discoverActions(projectRoot);
1352
+ return { agents, skills, connectors, actions };
775
1353
  };
776
1354
  var emitManifest = (manifest) => {
777
1355
  process.stdout.write(`${MANIFEST_START}
@@ -840,52 +1418,58 @@ var pick = async (label, items) => {
840
1418
  };
841
1419
 
842
1420
  // src/platform/types.ts
843
- import * as z11 from "zod";
844
- var jsonSchemaValue = z11.record(z11.string(), z11.unknown());
845
- var agentSchemaValidator = z11.object({
846
- id: z11.string(),
847
- name: z11.string(),
1421
+ import * as z10 from "zod";
1422
+ var jsonSchemaValue = z10.record(z10.string(), z10.unknown());
1423
+ var agentSchemaValidator = z10.object({
1424
+ id: z10.string(),
1425
+ name: z10.string(),
848
1426
  input: jsonSchemaValue,
849
1427
  output: jsonSchemaValue,
850
- actions: z11.record(z11.string(), jsonSchemaValue).optional()
1428
+ actions: z10.record(z10.string(), jsonSchemaValue).optional()
851
1429
  }).passthrough();
852
- var queryColumnValidator = z11.object({
853
- name: z11.string(),
854
- type: z11.string(),
855
- description: z11.string().optional()
1430
+ var queryColumnValidator = z10.object({
1431
+ name: z10.string(),
1432
+ type: z10.string(),
1433
+ description: z10.string().optional()
856
1434
  }).passthrough();
857
- var querySchemaValidator = z11.object({
858
- name: z11.string(),
1435
+ var querySchemaValidator = z10.object({
1436
+ name: z10.string(),
859
1437
  params: jsonSchemaValue,
860
- columns: z11.array(queryColumnValidator)
1438
+ columns: z10.array(queryColumnValidator)
861
1439
  }).passthrough();
862
- var connectorToolValidator = z11.object({
863
- name: z11.string(),
864
- description: z11.string(),
1440
+ var connectorToolValidator = z10.object({
1441
+ name: z10.string(),
1442
+ description: z10.string(),
865
1443
  parameters: jsonSchemaValue
866
1444
  }).passthrough();
867
- var connectorSchemaValidator = z11.object({
868
- id: z11.string(),
869
- tools: z11.array(connectorToolValidator)
1445
+ var connectorSchemaValidator = z10.object({
1446
+ id: z10.string(),
1447
+ tools: z10.array(connectorToolValidator)
870
1448
  }).passthrough();
871
- var skillSchemaValidator = z11.object({
872
- id: z11.string(),
873
- name: z11.string(),
874
- description: z11.string().optional()
1449
+ var skillSchemaValidator = z10.object({
1450
+ id: z10.string(),
1451
+ name: z10.string(),
1452
+ description: z10.string().optional()
875
1453
  }).passthrough();
876
- var pipelineQuerySchemaValidator = z11.object({
877
- name: z11.string(),
878
- description: z11.string(),
1454
+ var actionSchemaValidator = z10.object({
1455
+ id: z10.string(),
1456
+ name: z10.string(),
1457
+ schema: z10.record(z10.string(), z10.unknown()),
1458
+ hasHandler: z10.boolean()
1459
+ }).passthrough();
1460
+ var pipelineQuerySchemaValidator = z10.object({
1461
+ name: z10.string(),
1462
+ description: z10.string(),
879
1463
  params: jsonSchemaValue,
880
1464
  returns: jsonSchemaValue
881
1465
  }).passthrough();
882
- var pipelineSchemaValidator = z11.object({
883
- pipeline: z11.string(),
884
- queries: z11.array(pipelineQuerySchemaValidator)
1466
+ var pipelineSchemaValidator = z10.object({
1467
+ pipeline: z10.string(),
1468
+ queries: z10.array(pipelineQuerySchemaValidator)
885
1469
  }).passthrough();
886
1470
 
887
1471
  // src/cli.ts
888
- var COMMANDS = /* @__PURE__ */ new Set(["login", "generate", "logout", "discover", "init", "dev"]);
1472
+ var COMMANDS = /* @__PURE__ */ new Set(["login", "generate", "logout", "discover", "dev", "api-keys"]);
889
1473
  var parseArgs = (args) => {
890
1474
  const command = args[0];
891
1475
  if (!command || command === "--help" || !COMMANDS.has(command)) {
@@ -915,16 +1499,15 @@ var parseArgs = (args) => {
915
1499
  var runCommand = async (parsed, deps) => {
916
1500
  switch (parsed.command) {
917
1501
  case "login":
918
- await handleLogin(parsed, deps);
919
- return 0;
1502
+ return handleLogin(parsed, deps);
920
1503
  case "generate":
921
1504
  return handleGenerate(parsed, deps);
922
1505
  case "dev":
923
1506
  return handleDev(parsed, deps);
1507
+ case "api-keys":
1508
+ return handleApiKeys(parsed, deps);
924
1509
  case "discover":
925
1510
  return handleDiscover(parsed, deps);
926
- case "init":
927
- return handleInit(parsed, deps);
928
1511
  case "logout":
929
1512
  handleLogout(deps);
930
1513
  return 0;
@@ -935,29 +1518,131 @@ var runCommand = async (parsed, deps) => {
935
1518
  return 0;
936
1519
  }
937
1520
  };
1521
+ var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
1522
+ var LOGIN_TIMEOUT_MS = 16 * 60 * 1e3;
1523
+ var MAX_TRANSIENT_FAILURES = 3;
938
1524
  var handleLogin = async (parsed, deps) => {
939
- const url = parsed.flags.url ?? await deps.prompt("Platform URL: ");
940
- const key = parsed.flags.key ?? await deps.prompt("API Key: ");
941
- if (!url || !key) {
942
- deps.log("Error: both URL and API key are required.");
943
- return;
1525
+ const baseUrl = parsed.flags.url ?? await deps.prompt("Platform URL: ");
1526
+ if (!baseUrl || !/^https?:\/\//.test(baseUrl)) {
1527
+ deps.log("Error: a valid HTTP(S) URL is required.");
1528
+ return 1;
944
1529
  }
945
- deps.saveCredentials({ baseUrl: url, apiKey: key });
946
- deps.log(`Credentials saved. Connected to ${url}`);
1530
+ let deviceCodeRes;
1531
+ try {
1532
+ const res = await deps.fetch(`${baseUrl}/api/cli/device-code`, { method: "POST" });
1533
+ if (!res.ok) {
1534
+ deps.log(`Error: device code request failed (HTTP ${String(res.status)})`);
1535
+ return 1;
1536
+ }
1537
+ deviceCodeRes = await res.json();
1538
+ } catch (err) {
1539
+ deps.log(
1540
+ `Error: could not reach ${baseUrl} \u2014 ${err instanceof Error ? err.message : String(err)}`
1541
+ );
1542
+ return 1;
1543
+ }
1544
+ const { deviceCode, verificationUri, userCode } = deviceCodeRes;
1545
+ if (!deviceCode || !verificationUri || !userCode || deviceCodeRes.interval == null) {
1546
+ deps.log("Error: invalid device code response from server.");
1547
+ return 1;
1548
+ }
1549
+ deps.log(`
1550
+ Your device code: ${bold(userCode)}
1551
+ `);
1552
+ deps.log(` Opening ${verificationUri}`);
1553
+ deps.log(` ${dim("If the browser doesn't open, visit the URL manually.")}
1554
+ `);
1555
+ void deps.openUrl(verificationUri).catch(() => {
1556
+ });
1557
+ let interval = deviceCodeRes.interval;
1558
+ const deadline = Date.now() + LOGIN_TIMEOUT_MS;
1559
+ let transientFailures = 0;
1560
+ while (Date.now() < deadline) {
1561
+ await sleep(interval * 1e3);
1562
+ let tokenRes;
1563
+ try {
1564
+ const res = await deps.fetch(`${baseUrl}/api/cli/token`, {
1565
+ method: "POST",
1566
+ headers: { "Content-Type": "application/json" },
1567
+ body: JSON.stringify({ deviceCode, grant_type: DEVICE_CODE_GRANT_TYPE })
1568
+ });
1569
+ if (res.status >= 500) {
1570
+ transientFailures++;
1571
+ if (transientFailures >= MAX_TRANSIENT_FAILURES) {
1572
+ deps.log("Error: server returned repeated errors. Please try again later.");
1573
+ return 1;
1574
+ }
1575
+ continue;
1576
+ }
1577
+ const contentType = res.headers.get("content-type") ?? "";
1578
+ if (!contentType.includes("json")) {
1579
+ transientFailures++;
1580
+ if (transientFailures >= MAX_TRANSIENT_FAILURES) {
1581
+ deps.log("Error: server returned unexpected response format.");
1582
+ return 1;
1583
+ }
1584
+ continue;
1585
+ }
1586
+ tokenRes = await res.json();
1587
+ transientFailures = 0;
1588
+ } catch {
1589
+ transientFailures++;
1590
+ if (transientFailures >= MAX_TRANSIENT_FAILURES) {
1591
+ deps.log("Error: could not reach server. Please check your network and try again.");
1592
+ return 1;
1593
+ }
1594
+ continue;
1595
+ }
1596
+ if (tokenRes.error === "authorization_pending") {
1597
+ continue;
1598
+ }
1599
+ if (tokenRes.error === "slow_down") {
1600
+ interval += 5;
1601
+ continue;
1602
+ }
1603
+ if (tokenRes.error === "access_denied") {
1604
+ deps.log("Authorization denied.");
1605
+ return 1;
1606
+ }
1607
+ if (tokenRes.error === "expired_token") {
1608
+ deps.log("Device code expired. Please run `kraken login` again.");
1609
+ return 1;
1610
+ }
1611
+ if (tokenRes.accessToken) {
1612
+ deps.saveCredentials({ apiKey: tokenRes.accessToken, baseUrl });
1613
+ const who = tokenRes.user?.email ? ` as ${tokenRes.user.email}` : "";
1614
+ deps.log(`Logged in${who}. Credentials saved.`);
1615
+ return 0;
1616
+ }
1617
+ deps.log(`Error: ${tokenRes.error ?? "unknown error"}`);
1618
+ return 1;
1619
+ }
1620
+ deps.log("Error: login timed out. Please try again.");
1621
+ return 1;
947
1622
  };
1623
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
948
1624
  var emptySchemaBundle = {
949
1625
  agents: [],
950
1626
  queries: [],
951
1627
  connectors: [],
952
1628
  pipelines: [],
953
- skills: []
1629
+ skills: [],
1630
+ actions: []
954
1631
  };
955
1632
  var fetchRemoteSchemas = async (creds, deps) => {
956
- const headers = { Authorization: `Bearer ${creds.apiKey}` };
1633
+ const apiKey = new SecretString(creds.apiKey);
1634
+ const headers = { Authorization: `Bearer ${unwrapSecret(apiKey)}` };
957
1635
  const controller = new AbortController();
958
1636
  const timeout = setTimeout(() => controller.abort(), 1e4);
959
1637
  try {
960
- const endpoints = ["agents", "queries", "connectors", "pipelines", "skills"];
1638
+ const endpoints = [
1639
+ "agents",
1640
+ "queries",
1641
+ "connectors",
1642
+ "pipelines",
1643
+ "skills",
1644
+ "actions"
1645
+ ];
961
1646
  const responses = await Promise.all(
962
1647
  endpoints.map(
963
1648
  (ep) => deps.fetch(`${creds.baseUrl}/api/v1/schema/${ep}`, { headers, signal: controller.signal }).then(async (res) => {
@@ -968,26 +1653,27 @@ var fetchRemoteSchemas = async (creds, deps) => {
968
1653
  }).catch(() => null)
969
1654
  )
970
1655
  );
971
- const [agentsRes, queriesRes, connectorsRes, pipelinesRes, skillsRes] = responses;
1656
+ const [agentsRes, queriesRes, connectorsRes, pipelinesRes, skillsRes, actionsRes] = responses;
972
1657
  const allFailed = responses.every((r) => !r?.ok);
973
1658
  if (allFailed) {
974
1659
  warn(`Could not reach platform at ${creds.baseUrl}. Generating from local definitions only.`);
975
- return emptySchemaBundle;
1660
+ return { bundle: emptySchemaBundle, reachable: false };
976
1661
  }
977
- const agents = parseEndpointData(agentsRes, z12.array(agentSchemaValidator), "agents");
978
- const queries = parseEndpointData(queriesRes, z12.array(querySchemaValidator), "queries");
1662
+ const agents = parseEndpointData(agentsRes, z11.array(agentSchemaValidator), "agents");
1663
+ const queries = parseEndpointData(queriesRes, z11.array(querySchemaValidator), "queries");
979
1664
  const connectors = parseEndpointData(
980
1665
  connectorsRes,
981
- z12.array(connectorSchemaValidator),
1666
+ z11.array(connectorSchemaValidator),
982
1667
  "connectors"
983
1668
  );
984
1669
  const pipelines = parseEndpointData(
985
1670
  pipelinesRes,
986
- z12.array(pipelineSchemaValidator),
1671
+ z11.array(pipelineSchemaValidator),
987
1672
  "pipelines"
988
1673
  );
989
- const skills = parseEndpointData(skillsRes, z12.array(skillSchemaValidator), "skills");
990
- return { agents, queries, connectors, pipelines, skills };
1674
+ const skills = parseEndpointData(skillsRes, z11.array(skillSchemaValidator), "skills");
1675
+ const actions = parseEndpointData(actionsRes, z11.array(actionSchemaValidator), "actions");
1676
+ return { bundle: { agents, queries, connectors, pipelines, skills, actions }, reachable: true };
991
1677
  } finally {
992
1678
  clearTimeout(timeout);
993
1679
  }
@@ -998,24 +1684,85 @@ var parseEndpointData = (res, validator, endpointName) => {
998
1684
  const result = validator.safeParse(data);
999
1685
  if (!result.success) {
1000
1686
  throw new Error(
1001
- `Invalid remote schema from ${endpointName} endpoint: ${z12.prettifyError(result.error)}`
1687
+ `Invalid remote schema from ${endpointName} endpoint: ${z11.prettifyError(result.error)}`
1002
1688
  );
1003
1689
  }
1004
1690
  return result.data;
1005
1691
  };
1006
- var mergeLocalAndRemote = (local, remote) => {
1007
- const remoteAgentIds = new Set(remote.agents.map((a) => a.id));
1008
- const remoteConnectorIds = new Set(remote.connectors.map((c) => c.id));
1009
- const remoteSkillIds = new Set(remote.skills.map((s) => s.id));
1010
- const localAgents = local.agents.filter((a) => !remoteAgentIds.has(a.name)).map((a) => ({ id: a.name, name: a.name, input: {}, output: {} }));
1011
- const localSkills = local.skills.filter((s) => !remoteSkillIds.has(s.name)).map((s) => ({ id: s.name, name: s.name }));
1012
- const localConnectors = local.connectors.filter((c) => !remoteConnectorIds.has(c.name)).map((c) => ({ id: c.name, tools: [] }));
1692
+ var execFileAsync = promisify(execFile);
1693
+ var parseGitRemoteFullName = (url) => {
1694
+ const match = url.match(/[:/]([^/:]+\/[^/.]+?)(?:\.git)?\s*$/);
1695
+ return match?.[1] ?? null;
1696
+ };
1697
+ var getGitRemoteFullName = async (cwd) => {
1698
+ try {
1699
+ const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], { cwd });
1700
+ return parseGitRemoteFullName(stdout.trim());
1701
+ } catch {
1702
+ return null;
1703
+ }
1704
+ };
1705
+ var resolveRepoName = async (fullName, creds, deps) => {
1706
+ const apiKey = new SecretString(creds.apiKey);
1707
+ try {
1708
+ const res = await deps.fetch(
1709
+ `${creds.baseUrl}/api/v1/schema/repo-name?fullName=${encodeURIComponent(fullName)}`,
1710
+ { headers: { Authorization: `Bearer ${unwrapSecret(apiKey)}` } }
1711
+ );
1712
+ if (!res.ok) return null;
1713
+ const data = await res.json();
1714
+ return data.name;
1715
+ } catch {
1716
+ return null;
1717
+ }
1718
+ };
1719
+ var mergeLocalAndRemote = (local, remote, repoName) => {
1720
+ const localAgentNames = new Set(local.agents.map((a) => a.name));
1721
+ const localConnectorNames = new Set(local.connectors.map((c) => c.name));
1722
+ const localSkillNames = new Set(local.skills.map((s) => s.name));
1723
+ const localActionNames = new Set(local.actions.map((a) => a.name));
1724
+ const localAgents = local.agents.map((a) => ({
1725
+ id: a.name,
1726
+ name: a.name,
1727
+ input: {},
1728
+ output: {},
1729
+ config: a.config
1730
+ }));
1731
+ const localSkills = local.skills.map((s) => ({
1732
+ id: s.name,
1733
+ name: s.name
1734
+ }));
1735
+ const localConnectors = local.connectors.map((c) => ({
1736
+ id: c.name,
1737
+ tools: []
1738
+ }));
1739
+ const localActions = local.actions.map((a) => ({
1740
+ id: a.name,
1741
+ name: a.name,
1742
+ schema: a.config.schema,
1743
+ hasHandler: a.config.hasHandler
1744
+ }));
1745
+ const isSameRepoLocal = (remoteId, localNames) => {
1746
+ const slashIdx = remoteId.indexOf("/");
1747
+ if (slashIdx === -1) return localNames.has(remoteId);
1748
+ const remoteRepo = remoteId.slice(0, slashIdx);
1749
+ const bareName = remoteId.slice(slashIdx + 1);
1750
+ if (!localNames.has(bareName)) return false;
1751
+ return repoName !== null && remoteRepo === repoName;
1752
+ };
1753
+ const remoteOnlyAgents = remote.agents.filter((a) => !isSameRepoLocal(a.id, localAgentNames));
1754
+ const remoteOnlyConnectors = remote.connectors.filter(
1755
+ (c) => !isSameRepoLocal(c.id, localConnectorNames)
1756
+ );
1757
+ const remoteOnlySkills = remote.skills.filter((s) => !isSameRepoLocal(s.id, localSkillNames));
1758
+ const remoteOnlyActions = remote.actions.filter((a) => !isSameRepoLocal(a.id, localActionNames));
1013
1759
  return {
1014
- agents: [...remote.agents, ...localAgents],
1760
+ agents: [...localAgents, ...remoteOnlyAgents],
1015
1761
  queries: remote.queries,
1016
- connectors: [...remote.connectors, ...localConnectors],
1762
+ connectors: [...localConnectors, ...remoteOnlyConnectors],
1017
1763
  pipelines: remote.pipelines,
1018
- skills: [...remote.skills, ...localSkills]
1764
+ skills: [...localSkills, ...remoteOnlySkills],
1765
+ actions: [...localActions, ...remoteOnlyActions]
1019
1766
  };
1020
1767
  };
1021
1768
  var handleGenerate = async (parsed, deps) => {
@@ -1029,22 +1776,30 @@ var handleGenerate = async (parsed, deps) => {
1029
1776
  }
1030
1777
  const creds = deps.loadCredentials();
1031
1778
  let remote;
1779
+ let remoteReachable = false;
1780
+ let repoName = null;
1032
1781
  if (creds?.apiKey && creds?.baseUrl) {
1033
1782
  try {
1034
- remote = await fetchRemoteSchemas(creds, deps);
1783
+ const result = await fetchRemoteSchemas(creds, deps);
1784
+ remote = result.bundle;
1785
+ remoteReachable = result.reachable;
1035
1786
  } catch (err) {
1036
1787
  deps.log(
1037
1788
  `Error fetching remote schemas: ${err instanceof Error ? err.message : String(err)}`
1038
1789
  );
1039
1790
  return 1;
1040
1791
  }
1792
+ const fullName = await getGitRemoteFullName(outDir);
1793
+ if (fullName) {
1794
+ repoName = await resolveRepoName(fullName, creds, deps);
1795
+ }
1041
1796
  } else {
1042
1797
  warn(
1043
1798
  "Not logged in. Generating from local definitions only. Run `kraken login` to sync with the platform."
1044
1799
  );
1045
1800
  remote = emptySchemaBundle;
1046
1801
  }
1047
- const bundle = mergeLocalAndRemote(manifest, remote);
1802
+ const bundle = mergeLocalAndRemote(manifest, remote, repoName);
1048
1803
  if ("check" in parsed.flags) {
1049
1804
  const { mkdtempSync, readFileSync, rmSync } = await import("fs");
1050
1805
  const { join } = await import("path");
@@ -1061,11 +1816,10 @@ var handleGenerate = async (parsed, deps) => {
1061
1816
  const files = [
1062
1817
  "pipelines.json",
1063
1818
  "pipelines.d.ts",
1064
- "pipelines.ts",
1065
- // stale file detection — should not exist after migration
1066
1819
  "agents.d.ts",
1067
1820
  "queries.d.ts",
1068
1821
  "connectors.d.ts",
1822
+ "actions.d.ts",
1069
1823
  "platform-types.d.ts",
1070
1824
  "index.d.ts",
1071
1825
  "manifest.json"
@@ -1097,9 +1851,49 @@ var handleGenerate = async (parsed, deps) => {
1097
1851
  deps.log(`Error generating types: ${err instanceof Error ? err.message : String(err)}`);
1098
1852
  return 1;
1099
1853
  }
1100
- const localCount = manifest.agents.length + manifest.skills.length + manifest.connectors.length;
1101
- const remoteCount = remote.agents.length + remote.connectors.length + remote.skills.length;
1102
- deps.log(`Generated types for ${localCount} local + ${remoteCount} remote entities.`);
1854
+ const localNames = {
1855
+ agents: new Set(manifest.agents.map((a) => a.name)),
1856
+ connectors: new Set(manifest.connectors.map((c) => c.name)),
1857
+ skills: new Set(manifest.skills.map((s) => s.name)),
1858
+ actions: new Set(manifest.actions.map((a) => a.name))
1859
+ };
1860
+ const matchesLocalSummary = (remoteId, names) => {
1861
+ const slashIdx = remoteId.indexOf("/");
1862
+ if (slashIdx !== -1) {
1863
+ return names.has(remoteId.slice(slashIdx + 1));
1864
+ }
1865
+ return names.has(remoteId);
1866
+ };
1867
+ const remoteOnlyAgents = remote.agents.filter(
1868
+ (a) => !matchesLocalSummary(a.id, localNames.agents)
1869
+ );
1870
+ const remoteOnlyConnectors = remote.connectors.filter(
1871
+ (c) => !matchesLocalSummary(c.id, localNames.connectors)
1872
+ );
1873
+ const remoteOnlySkills = remote.skills.filter(
1874
+ (s) => !matchesLocalSummary(s.id, localNames.skills)
1875
+ );
1876
+ const remoteOnlyActions = remote.actions.filter(
1877
+ (a) => !matchesLocalSummary(a.id, localNames.actions)
1878
+ );
1879
+ const lines = [];
1880
+ lines.push(` ${dim("[kraken-ai]")} ${bold("Generated types:")}`);
1881
+ for (const a of manifest.agents) lines.push(` agent ${a.name}`);
1882
+ for (const a of remoteOnlyAgents) lines.push(` agent ${a.id} (remote)`);
1883
+ for (const c of manifest.connectors) lines.push(` connector ${c.name}`);
1884
+ for (const c of remoteOnlyConnectors) lines.push(` connector ${c.id} (remote)`);
1885
+ for (const s of manifest.skills) lines.push(` skill ${s.name}`);
1886
+ for (const s of remoteOnlySkills) lines.push(` skill ${s.id} (remote)`);
1887
+ for (const a of manifest.actions) lines.push(` action ${a.name}`);
1888
+ for (const a of remoteOnlyActions) lines.push(` action ${a.id} (remote)`);
1889
+ if (bundle.queries.length > 0) lines.push(` queries ${String(bundle.queries.length)}`);
1890
+ if (bundle.pipelines.length > 0) lines.push(` pipelines ${String(bundle.pipelines.length)}`);
1891
+ const hasRemote = remoteOnlyAgents.length + remoteOnlyConnectors.length + remoteOnlySkills.length + remoteOnlyActions.length > 0;
1892
+ if (!hasRemote && remoteReachable) {
1893
+ lines.push("");
1894
+ lines.push(" All platform entities are defined locally. No remote-only dependencies.");
1895
+ }
1896
+ deps.log(lines.join("\n"));
1103
1897
  return 0;
1104
1898
  };
1105
1899
  var safeReadFile = (readFileSync, filePath) => {
@@ -1123,7 +1917,7 @@ var buildDevBootstrap = (fileUrl, file) => [
1123
1917
  ].join("\n");
1124
1918
  var discoverAgentFiles = async () => {
1125
1919
  const { readdirSync, existsSync } = await import("fs");
1126
- const agentsDir = "agents";
1920
+ const agentsDir = "src/agents";
1127
1921
  if (!existsSync(agentsDir)) return [];
1128
1922
  return readdirSync(agentsDir).filter((f) => f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".mjs")).sort();
1129
1923
  };
@@ -1132,7 +1926,9 @@ var handleDev = async (parsed, deps) => {
1132
1926
  if (!name) {
1133
1927
  const agents = await discoverAgentFiles();
1134
1928
  if (agents.length === 0) {
1135
- deps.log("No agent files found in agents/. Create one or pass a name: kraken dev <agent>");
1929
+ deps.log(
1930
+ "No agent files found in src/agents/. Create one or pass a name: kraken dev <agent>"
1931
+ );
1136
1932
  return 1;
1137
1933
  }
1138
1934
  if (agents.length === 1) {
@@ -1144,7 +1940,7 @@ var handleDev = async (parsed, deps) => {
1144
1940
  }
1145
1941
  }
1146
1942
  if (!name) return 1;
1147
- const file = name.includes("/") ? name : `agents/${name}`;
1943
+ const file = name.includes("/") ? name : `src/agents/${name}`;
1148
1944
  const { spawn } = await import("child_process");
1149
1945
  const { resolve } = await import("path");
1150
1946
  const { pathToFileURL: pathToFileURL2 } = await import("url");
@@ -1161,9 +1957,11 @@ var handleDev = async (parsed, deps) => {
1161
1957
  var handleDiscover = async (parsed, deps) => {
1162
1958
  const dir = parsed.flags.dir ?? process.cwd();
1163
1959
  const manifest = await deps.discoverProject(dir);
1164
- const total = manifest.agents.length + manifest.skills.length + manifest.connectors.length;
1960
+ const total = manifest.agents.length + manifest.skills.length + manifest.connectors.length + manifest.actions.length;
1165
1961
  if (total === 0) {
1166
- deps.log("No entities found in agents/, skills/, or connectors/ directories.");
1962
+ deps.log(
1963
+ "No entities found in src/agents/, src/skills/, src/connectors/, or src/actions/ directories."
1964
+ );
1167
1965
  return 1;
1168
1966
  }
1169
1967
  if ("emit" in parsed.flags) {
@@ -1173,89 +1971,6 @@ var handleDiscover = async (parsed, deps) => {
1173
1971
  }
1174
1972
  return 0;
1175
1973
  };
1176
- var handleInit = async (parsed, deps) => {
1177
- const name = parsed.flags.name ?? await deps.prompt("Project name: ");
1178
- if (!name) {
1179
- deps.log("Error: project name is required.");
1180
- return 1;
1181
- }
1182
- const { writeFileSync, mkdirSync } = await import("fs");
1183
- const { join } = await import("path");
1184
- const root = join(process.cwd(), name);
1185
- mkdirSync(join(root, "agents"), { recursive: true });
1186
- mkdirSync(join(root, "skills"), { recursive: true });
1187
- mkdirSync(join(root, "connectors"), { recursive: true });
1188
- writeFileSync(
1189
- join(root, "package.json"),
1190
- JSON.stringify(
1191
- {
1192
- name,
1193
- private: true,
1194
- type: "module",
1195
- scripts: { build: "tsc" },
1196
- dependencies: {
1197
- "kraken-ai": "latest",
1198
- "@kraken-ai/platform": "latest"
1199
- },
1200
- devDependencies: {
1201
- typescript: "^5"
1202
- }
1203
- },
1204
- null,
1205
- 2
1206
- )
1207
- );
1208
- writeFileSync(
1209
- join(root, "tsconfig.json"),
1210
- JSON.stringify(
1211
- {
1212
- compilerOptions: {
1213
- target: "ES2022",
1214
- module: "NodeNext",
1215
- moduleResolution: "nodenext",
1216
- resolveJsonModule: true,
1217
- strict: true,
1218
- outDir: "dist",
1219
- rootDir: ".",
1220
- declaration: true,
1221
- esModuleInterop: true,
1222
- skipLibCheck: true
1223
- },
1224
- include: ["agents", "skills", "connectors"]
1225
- },
1226
- null,
1227
- 2
1228
- )
1229
- );
1230
- writeFileSync(
1231
- join(root, "agents", "my-agent.ts"),
1232
- `import { definePlatformAgent } from "@kraken-ai/platform";
1233
-
1234
- export default definePlatformAgent({
1235
- agent: {
1236
- name: "my-agent",
1237
- model: "google/gemini-2.5-flash-preview-05-20",
1238
- instructions: "You are a helpful assistant.",
1239
- },
1240
- triggers: [{ type: "manual" }],
1241
- });
1242
- `
1243
- );
1244
- writeFileSync(join(root, ".gitignore"), `node_modules/
1245
- dist/
1246
- .kraken-ai/
1247
- data/
1248
- .env
1249
- `);
1250
- deps.log(`Created project at ${root}/`);
1251
- deps.log(`
1252
- cd ${name}
1253
- pnpm install
1254
- kraken login
1255
- kraken generate
1256
- `);
1257
- return 0;
1258
- };
1259
1974
  var handleLogout = (deps) => {
1260
1975
  deps.clearCredentials();
1261
1976
  deps.log("Credentials cleared.");
@@ -1267,23 +1982,20 @@ kraken \u2014 Kraken AI Platform CLI
1267
1982
 
1268
1983
  Commands:
1269
1984
  dev Run an agent locally with an interactive REPL
1270
- [agent] Agent filename, e.g. researcher.ts (optional \u2014 picks from agents/ if omitted)
1985
+ [agent] Agent filename, e.g. researcher.ts (optional \u2014 picks from src/agents/ if omitted)
1271
1986
 
1272
- login Store API credentials
1987
+ login Authenticate via browser-based device authorization
1273
1988
  --url <platform-url> Platform instance URL
1274
- --key <api-key> API key
1989
+
1990
+ api-keys Manage API keys for server-side SDK authentication
1991
+ create --name <name> [--expires 90d] Create a new API key
1992
+ list List all API keys
1993
+ revoke <id> Revoke an API key
1275
1994
 
1276
1995
  generate Discover local entities and fetch remote schemas to generate types
1277
1996
  --dir <path> Project root / output directory (default: cwd)
1278
1997
  --check Check if types are up-to-date (exit 1 if stale)
1279
1998
 
1280
- discover Scan agents/, skills/, connectors/ and output project manifest
1281
- --dir <path> Project root (default: cwd)
1282
- --emit Output with sentinel markers (for orchestrator)
1283
-
1284
- init Create a new Kraken project
1285
- --name <project-name> Project name
1286
-
1287
1999
  logout Clear stored credentials
1288
2000
 
1289
2001
  Environment variables:
@@ -1302,6 +2014,21 @@ var readlinePrompt = async (question) => {
1302
2014
  });
1303
2015
  });
1304
2016
  };
2017
+ var openUrlImpl = async (url) => {
2018
+ if (!/^https?:\/\//.test(url)) return;
2019
+ const { platform } = await import("os");
2020
+ try {
2021
+ const p = platform();
2022
+ if (p === "darwin") {
2023
+ await execFileAsync("open", [url]);
2024
+ } else if (p === "win32") {
2025
+ await execFileAsync("cmd", ["/c", "start", "", url]);
2026
+ } else {
2027
+ await execFileAsync("xdg-open", [url]);
2028
+ }
2029
+ } catch {
2030
+ }
2031
+ };
1305
2032
  var main = async () => {
1306
2033
  const parsed = parseArgs(process.argv.slice(2));
1307
2034
  const exitCode = await runCommand(parsed, {
@@ -1312,7 +2039,8 @@ var main = async () => {
1312
2039
  discoverProject,
1313
2040
  fetch: globalThis.fetch,
1314
2041
  prompt: readlinePrompt,
1315
- log: console.log
2042
+ log: console.log,
2043
+ openUrl: openUrlImpl
1316
2044
  });
1317
2045
  if (exitCode !== 0) process.exit(exitCode);
1318
2046
  };