@kraken-ai/platform 0.0.4 → 0.0.7

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,169 @@ 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
+ fast: z7.boolean().optional(),
415
+ team: teamConfigSchema.optional(),
416
+ notifications: notificationConfigSchema.optional(),
417
+ environment: environmentSchema.optional(),
418
+ actions: z7.array(z7.string()).optional()
419
+ }).strict().superRefine((data, ctx) => {
420
+ for (const member of data.team?.members ?? []) {
421
+ const valid = isQualified(member) ? isValidQualifiedName(member) : isValidPrimitiveName(member);
422
+ if (!valid) {
423
+ ctx.addIssue({
424
+ code: z7.ZodIssueCode.custom,
425
+ path: ["team", "members"],
426
+ message: `Invalid team member name: "${member}"`
427
+ });
428
+ }
429
+ }
430
+ for (const connector of data.connectors ?? []) {
431
+ const valid = isQualified(connector) ? isValidQualifiedName(connector) : isValidPrimitiveName(connector);
432
+ if (!valid) {
433
+ ctx.addIssue({
434
+ code: z7.ZodIssueCode.custom,
435
+ path: ["connectors"],
436
+ message: `Invalid connector name: "${connector}"`
437
+ });
438
+ }
439
+ }
440
+ for (const skill of data.agent.skills ?? []) {
441
+ const valid = isQualified(skill) ? isValidQualifiedSkillId(skill) : isValidSkillId(skill);
442
+ if (!valid) {
443
+ ctx.addIssue({
444
+ code: z7.ZodIssueCode.custom,
445
+ path: ["agent", "skills"],
446
+ message: `Invalid skill ID: "${skill}"`
447
+ });
448
+ }
449
+ }
450
+ for (const action of data.actions ?? []) {
451
+ const valid = isQualified(action) ? isValidQualifiedName(action) : isValidPrimitiveName(action);
452
+ if (!valid) {
453
+ ctx.addIssue({
454
+ code: z7.ZodIssueCode.custom,
455
+ path: ["actions"],
456
+ message: `Invalid action name: "${action}"`
457
+ });
458
+ }
459
+ }
460
+ });
461
+
462
+ // ../../../private-packages/platform-core/dist/types/skill.js
463
+ import * as z8 from "zod";
464
+ var platformSkillInputSchema = z8.object({
465
+ name: z8.string().min(1),
466
+ description: z8.string().optional()
467
+ }).strict();
468
+
469
+ // src/cli/validate.ts
50
470
  var MAX_SCHEMA_DEPTH = 20;
51
471
  var MAX_OBJECT_BREADTH = 200;
52
472
  var validateSchemaBundle = (bundle) => {
53
473
  for (const agent of bundle.agents) {
54
- assertValidEntity("agent id", agent.id);
474
+ assertValidPrimitive("agent id", agent.id);
55
475
  validateJsonSchemaProperties("agent input schema", agent.input, 0);
56
476
  validateJsonSchemaProperties("agent output schema", agent.output, 0);
57
477
  if (agent.actions) {
58
478
  for (const [actionName, actionSchema] of Object.entries(agent.actions)) {
59
- assertValidEntity("agent action name", actionName);
479
+ assertValidPrimitive("agent action name", actionName);
60
480
  validateJsonSchemaProperties("agent action schema", actionSchema, 0);
61
481
  }
62
482
  }
63
483
  }
64
484
  for (const query of bundle.queries) {
65
- assertValidEntity("query name", query.name);
485
+ assertValidPrimitive("query name", query.name);
66
486
  validateJsonSchemaProperties("query params schema", query.params, 0);
67
487
  for (const col of query.columns) {
68
488
  assertValidProperty("query column name", col.name);
69
489
  }
70
490
  }
71
491
  for (const connector of bundle.connectors) {
72
- assertValidEntity("connector id", connector.id);
492
+ assertValidPrimitive("connector id", connector.id);
73
493
  for (const tool of connector.tools) {
74
494
  assertValidTool("connector tool name", tool.name);
75
495
  validateJsonSchemaProperties("connector tool parameters", tool.parameters, 0);
76
496
  }
77
497
  }
78
498
  for (const pipeline of bundle.pipelines) {
79
- assertValidEntity("pipeline name", pipeline.pipeline);
499
+ assertValidPrimitive("pipeline name", pipeline.pipeline);
80
500
  for (const query of pipeline.queries) {
81
- assertValidEntity("pipeline query name", query.name);
501
+ assertValidPrimitive("pipeline query name", query.name);
82
502
  validatePipelineSchema("pipeline query params", query.params, 0);
83
503
  validatePipelineSchema("pipeline query returns", query.returns, 0);
84
504
  }
@@ -86,32 +506,34 @@ var validateSchemaBundle = (bundle) => {
86
506
  for (const skill of bundle.skills) {
87
507
  assertValidSkill("skill id", skill.id);
88
508
  }
509
+ for (const action of bundle.actions) {
510
+ assertValidPrimitive("action id", action.id);
511
+ validateJsonSchemaProperties("action schema", action.schema, 0);
512
+ }
89
513
  };
90
- var assertValidEntity = (entityType, value) => {
91
- if (!isValidEntityName(value)) {
514
+ var assertValidPrimitive = (label, value) => {
515
+ if (!isValidPrimitiveName(value) && !isValidQualifiedName(value)) {
92
516
  throw new Error(
93
- `Invalid remote schema: ${entityType} "${value}". Must match ${ENTITY_NAME_REGEX}`
517
+ `Invalid remote schema: ${label} "${value}". Must match ${PRIMITIVE_NAME_REGEX} or repo/primitive format`
94
518
  );
95
519
  }
96
520
  };
97
- var assertValidTool = (entityType, value) => {
521
+ var assertValidTool = (label, value) => {
98
522
  if (!isValidToolName(value)) {
99
- throw new Error(
100
- `Invalid remote schema: ${entityType} "${value}". Must match ${TOOL_NAME_REGEX}`
101
- );
523
+ throw new Error(`Invalid remote schema: ${label} "${value}". Must match ${TOOL_NAME_REGEX}`);
102
524
  }
103
525
  };
104
- var assertValidProperty = (entityType, value) => {
526
+ var assertValidProperty = (label, value) => {
105
527
  if (!isValidPropertyName(value)) {
106
528
  throw new Error(
107
- `Invalid remote schema: ${entityType} "${value}". Must match ${PROPERTY_NAME_REGEX} and not be a forbidden property`
529
+ `Invalid remote schema: ${label} "${value}". Must match ${PROPERTY_NAME_REGEX} and not be a forbidden property`
108
530
  );
109
531
  }
110
532
  };
111
- var assertValidSkill = (entityType, value) => {
112
- if (!isValidSkillId(value)) {
533
+ var assertValidSkill = (label, value) => {
534
+ if (!isValidSkillId(value) && !isValidQualifiedSkillId(value)) {
113
535
  throw new Error(
114
- `Invalid remote schema: ${entityType} "${value}". Must match ${SKILL_NAME_REGEX} (with optional .md extension)`
536
+ `Invalid remote schema: ${label} "${value}". Must match ${SKILL_NAME_REGEX} (with optional .md extension) or repo/skill format`
115
537
  );
116
538
  }
117
539
  };
@@ -197,14 +619,10 @@ var generateTypes = (schemas, projectRoot) => {
197
619
  if (!fs.existsSync(outDir)) {
198
620
  fs.mkdirSync(outDir, { recursive: true });
199
621
  }
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));
622
+ fs.writeFileSync(
623
+ path.join(outDir, "agents.d.ts"),
624
+ generateAgentsDts(schemas.agents, schemas.actions)
625
+ );
208
626
  fs.writeFileSync(path.join(outDir, "queries.d.ts"), generateQueriesDts(schemas.queries));
209
627
  const connectorsDtsPath = path.join(outDir, "connectors.d.ts");
210
628
  if (schemas.connectors.length > 0) {
@@ -212,13 +630,20 @@ var generateTypes = (schemas, projectRoot) => {
212
630
  } else if (fs.existsSync(connectorsDtsPath)) {
213
631
  fs.rmSync(connectorsDtsPath);
214
632
  }
633
+ const actionsDtsPath = path.join(outDir, "actions.d.ts");
634
+ if (schemas.actions.length > 0) {
635
+ fs.writeFileSync(actionsDtsPath, generateActionsDts(schemas.actions));
636
+ } else if (fs.existsSync(actionsDtsPath)) {
637
+ fs.rmSync(actionsDtsPath);
638
+ }
215
639
  fs.writeFileSync(path.join(outDir, "pipelines.json"), generatePipelinesJson(schemas.pipelines));
216
640
  fs.writeFileSync(path.join(outDir, "pipelines.d.ts"), generatePipelinesDts(schemas.pipelines));
217
641
  const platformTypesDtsPath = path.join(outDir, "platform-types.d.ts");
218
642
  const platformTypesDts = generatePlatformTypesDts(
219
643
  schemas.agents,
220
644
  schemas.connectors,
221
- schemas.skills
645
+ schemas.skills,
646
+ schemas.actions
222
647
  );
223
648
  if (platformTypesDts) {
224
649
  fs.writeFileSync(platformTypesDtsPath, platformTypesDts);
@@ -229,21 +654,25 @@ var generateTypes = (schemas, projectRoot) => {
229
654
  path.join(outDir, "index.d.ts"),
230
655
  generateIndexDts(
231
656
  schemas.connectors.length > 0,
657
+ schemas.actions.length > 0,
232
658
  schemas.pipelines.length > 0,
233
659
  platformTypesDts != null
234
660
  )
235
661
  );
236
662
  fs.writeFileSync(path.join(outDir, "manifest.json"), JSON.stringify(schemas, null, 2));
237
663
  };
238
- var generateAgentsDts = (agents) => {
664
+ var generateAgentsDts = (agents, actions) => {
665
+ const actionsByName = new Map(actions.map((a) => [a.name, a]));
239
666
  const entries = agents.map((a) => {
240
667
  const input = jsonSchemaToTsType(a.input);
241
668
  const output = jsonSchemaToTsType(a.output);
242
- const actions = generateActionsType(a.actions);
669
+ const hasInlineActions = a.actions && Object.keys(a.actions).length > 0;
670
+ const agentActions = hasInlineActions ? a.actions : buildAgentActionsFromRefs(a, actionsByName);
671
+ const actionsType = generateActionsType(agentActions);
243
672
  return ` "${a.id}": {
244
673
  input: ${input}
245
674
  output: ${output}
246
- actions: ${actions}
675
+ actions: ${actionsType}
247
676
  }`;
248
677
  }).join("\n");
249
678
  return [
@@ -258,6 +687,19 @@ var generateAgentsDts = (agents) => {
258
687
  ``
259
688
  ].join("\n");
260
689
  };
690
+ var buildAgentActionsFromRefs = (agent, actionsByName) => {
691
+ const actionNames = agent.config?.actions;
692
+ if (!actionNames || actionNames.length === 0) return void 0;
693
+ const result = {};
694
+ for (const name of actionNames) {
695
+ const bareName = name.includes("/") ? name.slice(name.indexOf("/") + 1) : name;
696
+ const actionSchema = actionsByName.get(bareName) ?? actionsByName.get(name);
697
+ if (actionSchema) {
698
+ result[name] = actionSchema.schema;
699
+ }
700
+ }
701
+ return Object.keys(result).length > 0 ? result : void 0;
702
+ };
261
703
  var generateActionsType = (actions) => {
262
704
  if (!actions || Object.keys(actions).length === 0) {
263
705
  return "Record<string, never>";
@@ -265,6 +707,20 @@ var generateActionsType = (actions) => {
265
707
  const entries = Object.entries(actions).map(([name, schema]) => `"${name}": ${jsonSchemaToTsType(schema)}`).join("; ");
266
708
  return `{ ${entries} }`;
267
709
  };
710
+ var generateActionsDts = (actions) => {
711
+ const entries = actions.map((a) => ` "${a.name}": ${jsonSchemaToTsType(a.schema)}`).join("\n");
712
+ return [
713
+ `// Auto-generated by \`kraken generate\` \u2014 do not edit manually`,
714
+ `export {};`,
715
+ ``,
716
+ `declare module "@kraken-ai/platform" {`,
717
+ ` interface ActionRegistry {`,
718
+ entries,
719
+ ` }`,
720
+ `}`,
721
+ ``
722
+ ].join("\n");
723
+ };
268
724
  var generateQueriesDts = (queries) => {
269
725
  const entries = queries.map((q) => {
270
726
  const params = jsonSchemaToTsType(q.params);
@@ -301,7 +757,7 @@ ${entries}
301
757
  }
302
758
  `;
303
759
  };
304
- var generateIndexDts = (hasConnectors, hasPipelines, hasPlatformTypes) => {
760
+ var generateIndexDts = (hasConnectors, hasActions, hasPipelines, hasPlatformTypes) => {
305
761
  let content = `// Auto-generated by \`kraken generate\` \u2014 do not edit manually
306
762
 
307
763
  /// <reference path="./agents.d.ts" />
@@ -309,6 +765,10 @@ export type { QueryRegistry } from "./queries"
309
765
  `;
310
766
  if (hasConnectors) {
311
767
  content += `export type { ConnectorId, ConnectorTools } from "./connectors"
768
+ `;
769
+ }
770
+ if (hasActions) {
771
+ content += `/// <reference path="./actions.d.ts" />
312
772
  `;
313
773
  }
314
774
  if (hasPipelines) {
@@ -321,7 +781,7 @@ export type { QueryRegistry } from "./queries"
321
781
  }
322
782
  return content;
323
783
  };
324
- var generatePlatformTypesDts = (agents, connectors, skills) => {
784
+ var generatePlatformTypesDts = (agents, connectors, skills, actions) => {
325
785
  const entries = [];
326
786
  if (agents.length > 0) {
327
787
  const ids = agents.map((a) => `"${a.id}"`).join(" | ");
@@ -335,6 +795,10 @@ var generatePlatformTypesDts = (agents, connectors, skills) => {
335
795
  const ids = skills.map((s) => `"${s.id}"`).join(" | ");
336
796
  entries.push(` skillId: ${ids};`);
337
797
  }
798
+ if (actions.length > 0) {
799
+ const ids = actions.map((a) => `"${a.name}"`).join(" | ");
800
+ entries.push(` actionId: ${ids};`);
801
+ }
338
802
  if (entries.length === 0) return null;
339
803
  return [
340
804
  `// Auto-generated by \`kraken generate\` \u2014 do not edit manually`,
@@ -514,168 +978,127 @@ var clearCredentials = () => {
514
978
 
515
979
  // src/cli/discover.ts
516
980
  import fs3 from "fs";
981
+ import { createRequire } from "module";
517
982
  import path3 from "path";
518
983
  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
- });
984
+ import * as z9 from "zod";
554
985
 
555
- // src/agents/types/platform-agent.ts
556
- import * as z8 from "zod";
986
+ // src/cli/log.ts
987
+ var supportsColor = !("NO_COLOR" in process.env) && process.env.FORCE_COLOR !== "0" && (process.stderr.isTTY ?? false);
988
+ var code = (open, close) => {
989
+ const openStr = `\x1B[${open}m`;
990
+ const closeStr = `\x1B[${close}m`;
991
+ return (s) => supportsColor ? `${openStr}${s}${closeStr}` : s;
992
+ };
993
+ var bold = code(1, 22);
994
+ var dim = code(2, 22);
995
+ var red = code(31, 39);
996
+ var yellow = code(33, 39);
997
+ var green = code(32, 39);
998
+ var cyan = code(36, 39);
999
+ var warn = (msg) => {
1000
+ process.stderr.write(` ${dim("[kraken-ai:")} ${yellow("warn")}${dim("]")} ${msg}
1001
+ `);
1002
+ };
557
1003
 
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()
1004
+ // src/cli/discover.ts
1005
+ var MANIFEST_START = "---KRAKEN-MANIFEST-START---";
1006
+ var MANIFEST_END = "---KRAKEN-MANIFEST-END---";
1007
+ var skillEntrySchema = z9.object({
1008
+ name: z9.string().min(1),
1009
+ path: z9.string().min(1),
1010
+ content: z9.string()
564
1011
  });
565
- var retryPolicySchema = z5.object({
566
- maxAttempts: z5.number().int().min(1).optional(),
567
- backoffSeconds: z5.number().positive().optional()
1012
+ var toolSpecSchema = z9.object({
1013
+ name: z9.string(),
1014
+ description: z9.string(),
1015
+ parameters: z9.record(z9.string(), z9.unknown()).default({}),
1016
+ annotations: z9.object({
1017
+ readOnlyHint: z9.boolean().optional(),
1018
+ destructiveHint: z9.boolean().optional(),
1019
+ idempotentHint: z9.boolean().optional()
1020
+ }).optional()
568
1021
  });
569
- var concurrencyPolicySchema = z5.object({
570
- maxParallelRuns: z5.number().int().min(1).optional()
1022
+ var resourceSpecSchema = z9.object({
1023
+ name: z9.string(),
1024
+ uri: z9.string(),
1025
+ description: z9.string(),
1026
+ mimeType: z9.string().optional()
571
1027
  });
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()
1028
+ var promptSpecSchema = z9.object({
1029
+ name: z9.string(),
1030
+ description: z9.string(),
1031
+ arguments: z9.array(
1032
+ z9.object({
1033
+ name: z9.string(),
1034
+ description: z9.string().optional(),
1035
+ required: z9.boolean().optional()
1036
+ })
1037
+ ).optional()
580
1038
  });
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()
1039
+ var connectorEntrySchema = z9.object({
1040
+ name: z9.string().min(1),
1041
+ path: z9.string().min(1),
1042
+ toolSpecs: z9.array(toolSpecSchema).default([]),
1043
+ resourceSpecs: z9.array(resourceSpecSchema).default([]),
1044
+ promptSpecs: z9.array(promptSpecSchema).default([])
598
1045
  });
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({
1046
+ var agentEntrySchema = z9.object({
641
1047
  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()
1048
+ entryPoint: z9.string().min(1),
1049
+ config: platformAgentConfigSchema
652
1050
  });
653
- var connectorEntrySchema = z10.object({
654
- name: z10.string().min(1),
655
- path: z10.string().min(1)
1051
+ var actionConfigSchema = z9.object({
1052
+ schema: z9.record(z9.string(), z9.unknown()),
1053
+ webhook: z9.string().url().optional(),
1054
+ hasHandler: z9.boolean()
656
1055
  });
657
- var agentEntrySchema = z10.object({
658
- name: z10.string().min(1),
659
- entryPoint: z10.string().min(1),
660
- config: platformAgentConfigSchema
1056
+ var actionEntrySchema = z9.object({
1057
+ name: z9.string().min(1),
1058
+ entryPoint: z9.string().min(1),
1059
+ config: actionConfigSchema
661
1060
  });
662
- var projectManifestSchema = z10.object({
663
- agents: z10.array(agentEntrySchema),
664
- skills: z10.array(skillEntrySchema),
665
- connectors: z10.array(connectorEntrySchema)
1061
+ var projectManifestSchema = z9.object({
1062
+ agents: z9.array(agentEntrySchema),
1063
+ skills: z9.array(skillEntrySchema),
1064
+ connectors: z9.array(connectorEntrySchema),
1065
+ actions: z9.array(actionEntrySchema).default([])
666
1066
  });
667
1067
  var isTsxAvailable = () => process.env.NODE_OPTIONS?.includes("tsx") === true;
1068
+ var tsxLoaded = false;
1069
+ var ensureTsxLoader = async (projectRoot) => {
1070
+ if (isTsxAvailable() || tsxLoaded) return true;
1071
+ if (!fs3.existsSync(path3.join(projectRoot, "node_modules", "tsx"))) return false;
1072
+ try {
1073
+ const require2 = createRequire(path3.join(projectRoot, "package.json"));
1074
+ const tsxEsmPath = require2.resolve("tsx/esm");
1075
+ await import(pathToFileURL(tsxEsmPath).href);
1076
+ tsxLoaded = true;
1077
+ return true;
1078
+ } catch {
1079
+ return false;
1080
+ }
1081
+ };
668
1082
  var isPlatformAgentExport = (value) => value != null && typeof value === "object" && value.__type === "PlatformAgent" && "config" in value;
669
1083
  var discoverAgents = async (projectRoot) => {
670
1084
  const distAgentsDir = path3.join(projectRoot, "dist", "agents");
671
1085
  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";
1086
+ const agentsDir = useDistAgents ? distAgentsDir : path3.join(projectRoot, "src", "agents");
1087
+ const agentsDirPrefix = useDistAgents ? "dist/agents" : "src/agents";
674
1088
  if (!fs3.existsSync(agentsDir) || !fs3.statSync(agentsDir).isDirectory()) {
675
1089
  return [];
676
1090
  }
677
- const tsxEnabled = isTsxAvailable();
678
- const files = fs3.readdirSync(agentsDir).filter((f) => f.endsWith(".js") || f.endsWith(".mjs") || tsxEnabled && f.endsWith(".ts")).sort();
1091
+ const tsxEnabled = await ensureTsxLoader(projectRoot);
1092
+ const allFiles = fs3.readdirSync(agentsDir);
1093
+ const files = allFiles.filter((f) => f.endsWith(".js") || f.endsWith(".mjs") || tsxEnabled && f.endsWith(".ts")).sort();
1094
+ if (!tsxEnabled) {
1095
+ const skippedTs = allFiles.filter((f) => f.endsWith(".ts"));
1096
+ if (skippedTs.length > 0) {
1097
+ warn(
1098
+ `Found ${String(skippedTs.length)} .ts agent file(s) but tsx is not available. Install tsx as a devDependency or build your project first.`
1099
+ );
1100
+ }
1101
+ }
679
1102
  const seen = /* @__PURE__ */ new Map();
680
1103
  for (const file of files) {
681
1104
  const filePath = path3.join(agentsDir, file);
@@ -698,7 +1121,7 @@ var discoverAgents = async (projectRoot) => {
698
1121
  };
699
1122
  var MAX_SKILL_SIZE = 100 * 1024;
700
1123
  var discoverSkills = (projectRoot) => {
701
- const skillsDir = path3.join(projectRoot, "skills");
1124
+ const skillsDir = path3.join(projectRoot, "src", "skills");
702
1125
  if (!fs3.existsSync(skillsDir) || !fs3.statSync(skillsDir).isDirectory()) {
703
1126
  return [];
704
1127
  }
@@ -706,41 +1129,111 @@ var discoverSkills = (projectRoot) => {
706
1129
  const skills = [];
707
1130
  for (const file of files) {
708
1131
  if (!file.endsWith(".md")) {
709
- warn(`Skipping skills/${file}: skill files must have a .md extension.`);
1132
+ warn(`Skipping src/skills/${file}: skill files must have a .md extension.`);
710
1133
  continue;
711
1134
  }
712
1135
  const basename = file.replace(/\.md$/, "");
713
1136
  if (!isValidSkillName(basename)) {
714
1137
  warn(
715
- `Skipping skills/${file}: name must match [a-zA-Z0-9-] (letters, digits, hyphens only).`
1138
+ `Skipping src/skills/${file}: name must match [a-zA-Z0-9-] (letters, digits, hyphens only).`
716
1139
  );
717
1140
  continue;
718
1141
  }
719
1142
  const filePath = path3.join(skillsDir, file);
720
1143
  const stat = fs3.statSync(filePath);
721
1144
  if (stat.size > MAX_SKILL_SIZE) {
722
- warn(`Skipping skills/${file}: exceeds ${MAX_SKILL_SIZE} byte limit.`);
1145
+ warn(`Skipping src/skills/${file}: exceeds ${MAX_SKILL_SIZE} byte limit.`);
723
1146
  continue;
724
1147
  }
725
1148
  const content = fs3.readFileSync(filePath, "utf-8");
726
- skills.push({ name: file, path: `skills/${file}`, content });
1149
+ skills.push({ name: file, path: `src/skills/${file}`, content });
727
1150
  }
728
1151
  return skills;
729
1152
  };
730
- var discoverConnectors = (projectRoot) => {
1153
+ var isPlatformConnectorExport = (value) => value != null && typeof value === "object" && value.__type === "PlatformConnector";
1154
+ var serializeToolParameters = (input) => {
1155
+ try {
1156
+ return z9.toJSONSchema(input, { target: "draft-2020-12" });
1157
+ } catch {
1158
+ warn("Failed to serialize tool parameters via z.toJSONSchema(); falling back to {}");
1159
+ return {};
1160
+ }
1161
+ };
1162
+ var extractConnectorSpecs = (conn) => {
1163
+ const toolSpecs = [];
1164
+ if (conn.tools) {
1165
+ for (const [name, tool] of Object.entries(conn.tools)) {
1166
+ const spec = {
1167
+ name,
1168
+ description: tool.description,
1169
+ parameters: serializeToolParameters(tool.input)
1170
+ };
1171
+ if (tool.annotations) {
1172
+ const { readOnlyHint, destructiveHint, idempotentHint } = tool.annotations;
1173
+ if (readOnlyHint !== void 0 || destructiveHint !== void 0 || idempotentHint !== void 0) {
1174
+ spec.annotations = {
1175
+ ...readOnlyHint !== void 0 ? { readOnlyHint } : {},
1176
+ ...destructiveHint !== void 0 ? { destructiveHint } : {},
1177
+ ...idempotentHint !== void 0 ? { idempotentHint } : {}
1178
+ };
1179
+ }
1180
+ }
1181
+ toolSpecs.push(spec);
1182
+ }
1183
+ }
1184
+ const resourceSpecs = [];
1185
+ if (conn.resources) {
1186
+ for (const [name, resource] of Object.entries(conn.resources)) {
1187
+ const spec = {
1188
+ name,
1189
+ uri: resource.uri,
1190
+ description: resource.description
1191
+ };
1192
+ if (resource.mimeType) {
1193
+ spec.mimeType = resource.mimeType;
1194
+ }
1195
+ resourceSpecs.push(spec);
1196
+ }
1197
+ }
1198
+ const promptSpecs = [];
1199
+ if (conn.prompts) {
1200
+ for (const [name, prompt] of Object.entries(conn.prompts)) {
1201
+ const spec = {
1202
+ name,
1203
+ description: prompt.description
1204
+ };
1205
+ if (prompt.arguments) {
1206
+ spec.arguments = [...prompt.arguments];
1207
+ }
1208
+ promptSpecs.push(spec);
1209
+ }
1210
+ }
1211
+ return { toolSpecs, resourceSpecs, promptSpecs };
1212
+ };
1213
+ var findConnectorEntryPoint = (connDir, tsxEnabled) => {
1214
+ const candidates = ["index.js", "index.mjs"];
1215
+ if (tsxEnabled) candidates.push("index.ts");
1216
+ for (const candidate of candidates) {
1217
+ const filePath = path3.join(connDir, candidate);
1218
+ if (fs3.existsSync(filePath)) return filePath;
1219
+ }
1220
+ return void 0;
1221
+ };
1222
+ var discoverConnectors = async (projectRoot) => {
731
1223
  const distConnectorsDir = path3.join(projectRoot, "dist", "connectors");
732
1224
  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";
1225
+ const connectorsDir = useDistConnectors ? distConnectorsDir : path3.join(projectRoot, "src", "connectors");
1226
+ const connectorsDirPrefix = useDistConnectors ? "dist/connectors" : "src/connectors";
735
1227
  if (!fs3.existsSync(connectorsDir) || !fs3.statSync(connectorsDir).isDirectory()) {
736
1228
  return [];
737
1229
  }
1230
+ const tsxEnabled = await ensureTsxLoader(projectRoot);
738
1231
  const entries = fs3.readdirSync(connectorsDir, { withFileTypes: true });
739
1232
  const connectors = [];
740
1233
  for (const entry of entries) {
741
1234
  if (!entry.isDirectory()) continue;
742
1235
  const name = entry.name;
743
- if (!isValidEntityName(name)) {
1236
+ if (!isValidPrimitiveName(name)) {
744
1237
  warn(
745
1238
  `Skipping ${connectorsDirPrefix}/${name}: name must match [a-z0-9-] (lowercase, digits, hyphens only).`
746
1239
  );
@@ -755,23 +1248,109 @@ var discoverConnectors = (projectRoot) => {
755
1248
  if (!hasPackageJson && !hasDockerfile && !hasIndexJs && !hasIndexMjs && !hasIndexTs) {
756
1249
  continue;
757
1250
  }
758
- connectors.push({ name, path: `${connectorsDirPrefix}/${name}/` });
1251
+ const connectorPath = `${connectorsDirPrefix}/${name}/`;
1252
+ const entryPoint = findConnectorEntryPoint(connDir, tsxEnabled);
1253
+ if (!entryPoint) {
1254
+ connectors.push({
1255
+ name,
1256
+ path: connectorPath,
1257
+ toolSpecs: [],
1258
+ resourceSpecs: [],
1259
+ promptSpecs: []
1260
+ });
1261
+ continue;
1262
+ }
1263
+ try {
1264
+ const mod = await import(pathToFileURL(entryPoint).href);
1265
+ const exported = mod.default;
1266
+ if (isPlatformConnectorExport(exported)) {
1267
+ const specs = extractConnectorSpecs(exported);
1268
+ connectors.push({ name, path: connectorPath, ...specs });
1269
+ } else {
1270
+ connectors.push({
1271
+ name,
1272
+ path: connectorPath,
1273
+ toolSpecs: [],
1274
+ resourceSpecs: [],
1275
+ promptSpecs: []
1276
+ });
1277
+ }
1278
+ } catch (err) {
1279
+ warn(
1280
+ `Skipping introspection for ${connectorsDirPrefix}/${name}: ${err instanceof Error ? err.message : String(err)}`
1281
+ );
1282
+ connectors.push({
1283
+ name,
1284
+ path: connectorPath,
1285
+ toolSpecs: [],
1286
+ resourceSpecs: [],
1287
+ promptSpecs: []
1288
+ });
1289
+ }
759
1290
  }
760
1291
  return connectors.sort((a, b) => a.name.localeCompare(b.name));
761
1292
  };
1293
+ var isPlatformActionExport = (value) => value != null && typeof value === "object" && value.__type === "PlatformAction" && "name" in value && "config" in value;
1294
+ var discoverActions = async (projectRoot) => {
1295
+ const distActionsDir = path3.join(projectRoot, "dist", "actions");
1296
+ const useDistActions = fs3.existsSync(distActionsDir) && fs3.statSync(distActionsDir).isDirectory();
1297
+ const actionsDir = useDistActions ? distActionsDir : path3.join(projectRoot, "src", "actions");
1298
+ const actionsDirPrefix = useDistActions ? "dist/actions" : "src/actions";
1299
+ if (!fs3.existsSync(actionsDir) || !fs3.statSync(actionsDir).isDirectory()) {
1300
+ return [];
1301
+ }
1302
+ const tsxEnabled = await ensureTsxLoader(projectRoot);
1303
+ const allFiles = fs3.readdirSync(actionsDir);
1304
+ const files = allFiles.filter((f) => f.endsWith(".js") || f.endsWith(".mjs") || tsxEnabled && f.endsWith(".ts")).sort();
1305
+ if (!tsxEnabled) {
1306
+ const skippedTs = allFiles.filter((f) => f.endsWith(".ts"));
1307
+ if (skippedTs.length > 0) {
1308
+ warn(
1309
+ `Found ${String(skippedTs.length)} .ts action file(s) but tsx is not available. Install tsx as a devDependency or build your project first.`
1310
+ );
1311
+ }
1312
+ }
1313
+ const seen = /* @__PURE__ */ new Map();
1314
+ for (const file of files) {
1315
+ const filePath = path3.join(actionsDir, file);
1316
+ try {
1317
+ const mod = await import(pathToFileURL(filePath).href);
1318
+ const exported = mod.default;
1319
+ if (isPlatformActionExport(exported) && !seen.has(exported.name)) {
1320
+ if (!isValidPrimitiveName(exported.name)) {
1321
+ warn(
1322
+ `Skipping ${actionsDirPrefix}/${file}: action name "${exported.name}" must match [a-z0-9-] (lowercase, digits, hyphens only).`
1323
+ );
1324
+ continue;
1325
+ }
1326
+ seen.set(exported.name, {
1327
+ name: exported.name,
1328
+ entryPoint: `${actionsDirPrefix}/${file}`,
1329
+ config: exported.config
1330
+ });
1331
+ }
1332
+ } catch (err) {
1333
+ warn(
1334
+ `Skipping ${actionsDirPrefix}/${file}: ${err instanceof Error ? err.message : String(err)}`
1335
+ );
1336
+ }
1337
+ }
1338
+ return [...seen.values()];
1339
+ };
762
1340
  var discoverProject = async (projectRoot) => {
763
1341
  const discovered = await discoverAgents(projectRoot);
764
1342
  const agents = [];
765
1343
  for (const { config, entryPoint } of discovered) {
766
1344
  const name = config.agent.name;
767
- if (!isValidEntityName(name)) {
768
- throw new Error(`Invalid agent name: "${name}". Names must match ${ENTITY_NAME_REGEX}`);
1345
+ if (!isValidPrimitiveName(name)) {
1346
+ throw new Error(`Invalid agent name: "${name}". Names must match ${PRIMITIVE_NAME_REGEX}`);
769
1347
  }
770
1348
  agents.push({ name, entryPoint, config });
771
1349
  }
772
1350
  const skills = discoverSkills(projectRoot);
773
- const connectors = discoverConnectors(projectRoot);
774
- return { agents, skills, connectors };
1351
+ const connectors = await discoverConnectors(projectRoot);
1352
+ const actions = await discoverActions(projectRoot);
1353
+ return { agents, skills, connectors, actions };
775
1354
  };
776
1355
  var emitManifest = (manifest) => {
777
1356
  process.stdout.write(`${MANIFEST_START}
@@ -840,52 +1419,58 @@ var pick = async (label, items) => {
840
1419
  };
841
1420
 
842
1421
  // 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(),
1422
+ import * as z10 from "zod";
1423
+ var jsonSchemaValue = z10.record(z10.string(), z10.unknown());
1424
+ var agentSchemaValidator = z10.object({
1425
+ id: z10.string(),
1426
+ name: z10.string(),
848
1427
  input: jsonSchemaValue,
849
1428
  output: jsonSchemaValue,
850
- actions: z11.record(z11.string(), jsonSchemaValue).optional()
1429
+ actions: z10.record(z10.string(), jsonSchemaValue).optional()
851
1430
  }).passthrough();
852
- var queryColumnValidator = z11.object({
853
- name: z11.string(),
854
- type: z11.string(),
855
- description: z11.string().optional()
1431
+ var queryColumnValidator = z10.object({
1432
+ name: z10.string(),
1433
+ type: z10.string(),
1434
+ description: z10.string().optional()
856
1435
  }).passthrough();
857
- var querySchemaValidator = z11.object({
858
- name: z11.string(),
1436
+ var querySchemaValidator = z10.object({
1437
+ name: z10.string(),
859
1438
  params: jsonSchemaValue,
860
- columns: z11.array(queryColumnValidator)
1439
+ columns: z10.array(queryColumnValidator)
861
1440
  }).passthrough();
862
- var connectorToolValidator = z11.object({
863
- name: z11.string(),
864
- description: z11.string(),
1441
+ var connectorToolValidator = z10.object({
1442
+ name: z10.string(),
1443
+ description: z10.string(),
865
1444
  parameters: jsonSchemaValue
866
1445
  }).passthrough();
867
- var connectorSchemaValidator = z11.object({
868
- id: z11.string(),
869
- tools: z11.array(connectorToolValidator)
1446
+ var connectorSchemaValidator = z10.object({
1447
+ id: z10.string(),
1448
+ tools: z10.array(connectorToolValidator)
870
1449
  }).passthrough();
871
- var skillSchemaValidator = z11.object({
872
- id: z11.string(),
873
- name: z11.string(),
874
- description: z11.string().optional()
1450
+ var skillSchemaValidator = z10.object({
1451
+ id: z10.string(),
1452
+ name: z10.string(),
1453
+ description: z10.string().optional()
875
1454
  }).passthrough();
876
- var pipelineQuerySchemaValidator = z11.object({
877
- name: z11.string(),
878
- description: z11.string(),
1455
+ var actionSchemaValidator = z10.object({
1456
+ id: z10.string(),
1457
+ name: z10.string(),
1458
+ schema: z10.record(z10.string(), z10.unknown()),
1459
+ hasHandler: z10.boolean()
1460
+ }).passthrough();
1461
+ var pipelineQuerySchemaValidator = z10.object({
1462
+ name: z10.string(),
1463
+ description: z10.string(),
879
1464
  params: jsonSchemaValue,
880
1465
  returns: jsonSchemaValue
881
1466
  }).passthrough();
882
- var pipelineSchemaValidator = z11.object({
883
- pipeline: z11.string(),
884
- queries: z11.array(pipelineQuerySchemaValidator)
1467
+ var pipelineSchemaValidator = z10.object({
1468
+ pipeline: z10.string(),
1469
+ queries: z10.array(pipelineQuerySchemaValidator)
885
1470
  }).passthrough();
886
1471
 
887
1472
  // src/cli.ts
888
- var COMMANDS = /* @__PURE__ */ new Set(["login", "generate", "logout", "discover", "init", "dev"]);
1473
+ var COMMANDS = /* @__PURE__ */ new Set(["login", "generate", "logout", "discover", "dev", "api-keys"]);
889
1474
  var parseArgs = (args) => {
890
1475
  const command = args[0];
891
1476
  if (!command || command === "--help" || !COMMANDS.has(command)) {
@@ -915,16 +1500,15 @@ var parseArgs = (args) => {
915
1500
  var runCommand = async (parsed, deps) => {
916
1501
  switch (parsed.command) {
917
1502
  case "login":
918
- await handleLogin(parsed, deps);
919
- return 0;
1503
+ return handleLogin(parsed, deps);
920
1504
  case "generate":
921
1505
  return handleGenerate(parsed, deps);
922
1506
  case "dev":
923
1507
  return handleDev(parsed, deps);
1508
+ case "api-keys":
1509
+ return handleApiKeys(parsed, deps);
924
1510
  case "discover":
925
1511
  return handleDiscover(parsed, deps);
926
- case "init":
927
- return handleInit(parsed, deps);
928
1512
  case "logout":
929
1513
  handleLogout(deps);
930
1514
  return 0;
@@ -935,29 +1519,131 @@ var runCommand = async (parsed, deps) => {
935
1519
  return 0;
936
1520
  }
937
1521
  };
1522
+ var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
1523
+ var LOGIN_TIMEOUT_MS = 16 * 60 * 1e3;
1524
+ var MAX_TRANSIENT_FAILURES = 3;
938
1525
  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;
1526
+ const baseUrl = parsed.flags.url ?? await deps.prompt("Platform URL: ");
1527
+ if (!baseUrl || !/^https?:\/\//.test(baseUrl)) {
1528
+ deps.log("Error: a valid HTTP(S) URL is required.");
1529
+ return 1;
944
1530
  }
945
- deps.saveCredentials({ baseUrl: url, apiKey: key });
946
- deps.log(`Credentials saved. Connected to ${url}`);
1531
+ let deviceCodeRes;
1532
+ try {
1533
+ const res = await deps.fetch(`${baseUrl}/api/cli/device-code`, { method: "POST" });
1534
+ if (!res.ok) {
1535
+ deps.log(`Error: device code request failed (HTTP ${String(res.status)})`);
1536
+ return 1;
1537
+ }
1538
+ deviceCodeRes = await res.json();
1539
+ } catch (err) {
1540
+ deps.log(
1541
+ `Error: could not reach ${baseUrl} \u2014 ${err instanceof Error ? err.message : String(err)}`
1542
+ );
1543
+ return 1;
1544
+ }
1545
+ const { deviceCode, verificationUri, userCode } = deviceCodeRes;
1546
+ if (!deviceCode || !verificationUri || !userCode || deviceCodeRes.interval == null) {
1547
+ deps.log("Error: invalid device code response from server.");
1548
+ return 1;
1549
+ }
1550
+ deps.log(`
1551
+ Your device code: ${bold(userCode)}
1552
+ `);
1553
+ deps.log(` Opening ${verificationUri}`);
1554
+ deps.log(` ${dim("If the browser doesn't open, visit the URL manually.")}
1555
+ `);
1556
+ void deps.openUrl(verificationUri).catch(() => {
1557
+ });
1558
+ let interval = deviceCodeRes.interval;
1559
+ const deadline = Date.now() + LOGIN_TIMEOUT_MS;
1560
+ let transientFailures = 0;
1561
+ while (Date.now() < deadline) {
1562
+ await sleep(interval * 1e3);
1563
+ let tokenRes;
1564
+ try {
1565
+ const res = await deps.fetch(`${baseUrl}/api/cli/token`, {
1566
+ method: "POST",
1567
+ headers: { "Content-Type": "application/json" },
1568
+ body: JSON.stringify({ deviceCode, grant_type: DEVICE_CODE_GRANT_TYPE })
1569
+ });
1570
+ if (res.status >= 500) {
1571
+ transientFailures++;
1572
+ if (transientFailures >= MAX_TRANSIENT_FAILURES) {
1573
+ deps.log("Error: server returned repeated errors. Please try again later.");
1574
+ return 1;
1575
+ }
1576
+ continue;
1577
+ }
1578
+ const contentType = res.headers.get("content-type") ?? "";
1579
+ if (!contentType.includes("json")) {
1580
+ transientFailures++;
1581
+ if (transientFailures >= MAX_TRANSIENT_FAILURES) {
1582
+ deps.log("Error: server returned unexpected response format.");
1583
+ return 1;
1584
+ }
1585
+ continue;
1586
+ }
1587
+ tokenRes = await res.json();
1588
+ transientFailures = 0;
1589
+ } catch {
1590
+ transientFailures++;
1591
+ if (transientFailures >= MAX_TRANSIENT_FAILURES) {
1592
+ deps.log("Error: could not reach server. Please check your network and try again.");
1593
+ return 1;
1594
+ }
1595
+ continue;
1596
+ }
1597
+ if (tokenRes.error === "authorization_pending") {
1598
+ continue;
1599
+ }
1600
+ if (tokenRes.error === "slow_down") {
1601
+ interval += 5;
1602
+ continue;
1603
+ }
1604
+ if (tokenRes.error === "access_denied") {
1605
+ deps.log("Authorization denied.");
1606
+ return 1;
1607
+ }
1608
+ if (tokenRes.error === "expired_token") {
1609
+ deps.log("Device code expired. Please run `kraken login` again.");
1610
+ return 1;
1611
+ }
1612
+ if (tokenRes.accessToken) {
1613
+ deps.saveCredentials({ apiKey: tokenRes.accessToken, baseUrl });
1614
+ const who = tokenRes.user?.email ? ` as ${tokenRes.user.email}` : "";
1615
+ deps.log(`Logged in${who}. Credentials saved.`);
1616
+ return 0;
1617
+ }
1618
+ deps.log(`Error: ${tokenRes.error ?? "unknown error"}`);
1619
+ return 1;
1620
+ }
1621
+ deps.log("Error: login timed out. Please try again.");
1622
+ return 1;
947
1623
  };
1624
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
948
1625
  var emptySchemaBundle = {
949
1626
  agents: [],
950
1627
  queries: [],
951
1628
  connectors: [],
952
1629
  pipelines: [],
953
- skills: []
1630
+ skills: [],
1631
+ actions: []
954
1632
  };
955
1633
  var fetchRemoteSchemas = async (creds, deps) => {
956
- const headers = { Authorization: `Bearer ${creds.apiKey}` };
1634
+ const apiKey = new SecretString(creds.apiKey);
1635
+ const headers = { Authorization: `Bearer ${unwrapSecret(apiKey)}` };
957
1636
  const controller = new AbortController();
958
1637
  const timeout = setTimeout(() => controller.abort(), 1e4);
959
1638
  try {
960
- const endpoints = ["agents", "queries", "connectors", "pipelines", "skills"];
1639
+ const endpoints = [
1640
+ "agents",
1641
+ "queries",
1642
+ "connectors",
1643
+ "pipelines",
1644
+ "skills",
1645
+ "actions"
1646
+ ];
961
1647
  const responses = await Promise.all(
962
1648
  endpoints.map(
963
1649
  (ep) => deps.fetch(`${creds.baseUrl}/api/v1/schema/${ep}`, { headers, signal: controller.signal }).then(async (res) => {
@@ -968,26 +1654,27 @@ var fetchRemoteSchemas = async (creds, deps) => {
968
1654
  }).catch(() => null)
969
1655
  )
970
1656
  );
971
- const [agentsRes, queriesRes, connectorsRes, pipelinesRes, skillsRes] = responses;
1657
+ const [agentsRes, queriesRes, connectorsRes, pipelinesRes, skillsRes, actionsRes] = responses;
972
1658
  const allFailed = responses.every((r) => !r?.ok);
973
1659
  if (allFailed) {
974
1660
  warn(`Could not reach platform at ${creds.baseUrl}. Generating from local definitions only.`);
975
- return emptySchemaBundle;
1661
+ return { bundle: emptySchemaBundle, reachable: false };
976
1662
  }
977
- const agents = parseEndpointData(agentsRes, z12.array(agentSchemaValidator), "agents");
978
- const queries = parseEndpointData(queriesRes, z12.array(querySchemaValidator), "queries");
1663
+ const agents = parseEndpointData(agentsRes, z11.array(agentSchemaValidator), "agents");
1664
+ const queries = parseEndpointData(queriesRes, z11.array(querySchemaValidator), "queries");
979
1665
  const connectors = parseEndpointData(
980
1666
  connectorsRes,
981
- z12.array(connectorSchemaValidator),
1667
+ z11.array(connectorSchemaValidator),
982
1668
  "connectors"
983
1669
  );
984
1670
  const pipelines = parseEndpointData(
985
1671
  pipelinesRes,
986
- z12.array(pipelineSchemaValidator),
1672
+ z11.array(pipelineSchemaValidator),
987
1673
  "pipelines"
988
1674
  );
989
- const skills = parseEndpointData(skillsRes, z12.array(skillSchemaValidator), "skills");
990
- return { agents, queries, connectors, pipelines, skills };
1675
+ const skills = parseEndpointData(skillsRes, z11.array(skillSchemaValidator), "skills");
1676
+ const actions = parseEndpointData(actionsRes, z11.array(actionSchemaValidator), "actions");
1677
+ return { bundle: { agents, queries, connectors, pipelines, skills, actions }, reachable: true };
991
1678
  } finally {
992
1679
  clearTimeout(timeout);
993
1680
  }
@@ -998,24 +1685,85 @@ var parseEndpointData = (res, validator, endpointName) => {
998
1685
  const result = validator.safeParse(data);
999
1686
  if (!result.success) {
1000
1687
  throw new Error(
1001
- `Invalid remote schema from ${endpointName} endpoint: ${z12.prettifyError(result.error)}`
1688
+ `Invalid remote schema from ${endpointName} endpoint: ${z11.prettifyError(result.error)}`
1002
1689
  );
1003
1690
  }
1004
1691
  return result.data;
1005
1692
  };
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: [] }));
1693
+ var execFileAsync = promisify(execFile);
1694
+ var parseGitRemoteFullName = (url) => {
1695
+ const match = url.match(/[:/]([^/:]+\/[^/.]+?)(?:\.git)?\s*$/);
1696
+ return match?.[1] ?? null;
1697
+ };
1698
+ var getGitRemoteFullName = async (cwd) => {
1699
+ try {
1700
+ const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], { cwd });
1701
+ return parseGitRemoteFullName(stdout.trim());
1702
+ } catch {
1703
+ return null;
1704
+ }
1705
+ };
1706
+ var resolveRepoName = async (fullName, creds, deps) => {
1707
+ const apiKey = new SecretString(creds.apiKey);
1708
+ try {
1709
+ const res = await deps.fetch(
1710
+ `${creds.baseUrl}/api/v1/schema/repo-name?fullName=${encodeURIComponent(fullName)}`,
1711
+ { headers: { Authorization: `Bearer ${unwrapSecret(apiKey)}` } }
1712
+ );
1713
+ if (!res.ok) return null;
1714
+ const data = await res.json();
1715
+ return data.name;
1716
+ } catch {
1717
+ return null;
1718
+ }
1719
+ };
1720
+ var mergeLocalAndRemote = (local, remote, repoName) => {
1721
+ const localAgentNames = new Set(local.agents.map((a) => a.name));
1722
+ const localConnectorNames = new Set(local.connectors.map((c) => c.name));
1723
+ const localSkillNames = new Set(local.skills.map((s) => s.name));
1724
+ const localActionNames = new Set(local.actions.map((a) => a.name));
1725
+ const localAgents = local.agents.map((a) => ({
1726
+ id: a.name,
1727
+ name: a.name,
1728
+ input: {},
1729
+ output: {},
1730
+ config: a.config
1731
+ }));
1732
+ const localSkills = local.skills.map((s) => ({
1733
+ id: s.name,
1734
+ name: s.name
1735
+ }));
1736
+ const localConnectors = local.connectors.map((c) => ({
1737
+ id: c.name,
1738
+ tools: []
1739
+ }));
1740
+ const localActions = local.actions.map((a) => ({
1741
+ id: a.name,
1742
+ name: a.name,
1743
+ schema: a.config.schema,
1744
+ hasHandler: a.config.hasHandler
1745
+ }));
1746
+ const isSameRepoLocal = (remoteId, localNames) => {
1747
+ const slashIdx = remoteId.indexOf("/");
1748
+ if (slashIdx === -1) return localNames.has(remoteId);
1749
+ const remoteRepo = remoteId.slice(0, slashIdx);
1750
+ const bareName = remoteId.slice(slashIdx + 1);
1751
+ if (!localNames.has(bareName)) return false;
1752
+ return repoName !== null && remoteRepo === repoName;
1753
+ };
1754
+ const remoteOnlyAgents = remote.agents.filter((a) => !isSameRepoLocal(a.id, localAgentNames));
1755
+ const remoteOnlyConnectors = remote.connectors.filter(
1756
+ (c) => !isSameRepoLocal(c.id, localConnectorNames)
1757
+ );
1758
+ const remoteOnlySkills = remote.skills.filter((s) => !isSameRepoLocal(s.id, localSkillNames));
1759
+ const remoteOnlyActions = remote.actions.filter((a) => !isSameRepoLocal(a.id, localActionNames));
1013
1760
  return {
1014
- agents: [...remote.agents, ...localAgents],
1761
+ agents: [...localAgents, ...remoteOnlyAgents],
1015
1762
  queries: remote.queries,
1016
- connectors: [...remote.connectors, ...localConnectors],
1763
+ connectors: [...localConnectors, ...remoteOnlyConnectors],
1017
1764
  pipelines: remote.pipelines,
1018
- skills: [...remote.skills, ...localSkills]
1765
+ skills: [...localSkills, ...remoteOnlySkills],
1766
+ actions: [...localActions, ...remoteOnlyActions]
1019
1767
  };
1020
1768
  };
1021
1769
  var handleGenerate = async (parsed, deps) => {
@@ -1029,22 +1777,30 @@ var handleGenerate = async (parsed, deps) => {
1029
1777
  }
1030
1778
  const creds = deps.loadCredentials();
1031
1779
  let remote;
1780
+ let remoteReachable = false;
1781
+ let repoName = null;
1032
1782
  if (creds?.apiKey && creds?.baseUrl) {
1033
1783
  try {
1034
- remote = await fetchRemoteSchemas(creds, deps);
1784
+ const result = await fetchRemoteSchemas(creds, deps);
1785
+ remote = result.bundle;
1786
+ remoteReachable = result.reachable;
1035
1787
  } catch (err) {
1036
1788
  deps.log(
1037
1789
  `Error fetching remote schemas: ${err instanceof Error ? err.message : String(err)}`
1038
1790
  );
1039
1791
  return 1;
1040
1792
  }
1793
+ const fullName = await getGitRemoteFullName(outDir);
1794
+ if (fullName) {
1795
+ repoName = await resolveRepoName(fullName, creds, deps);
1796
+ }
1041
1797
  } else {
1042
1798
  warn(
1043
1799
  "Not logged in. Generating from local definitions only. Run `kraken login` to sync with the platform."
1044
1800
  );
1045
1801
  remote = emptySchemaBundle;
1046
1802
  }
1047
- const bundle = mergeLocalAndRemote(manifest, remote);
1803
+ const bundle = mergeLocalAndRemote(manifest, remote, repoName);
1048
1804
  if ("check" in parsed.flags) {
1049
1805
  const { mkdtempSync, readFileSync, rmSync } = await import("fs");
1050
1806
  const { join } = await import("path");
@@ -1061,11 +1817,10 @@ var handleGenerate = async (parsed, deps) => {
1061
1817
  const files = [
1062
1818
  "pipelines.json",
1063
1819
  "pipelines.d.ts",
1064
- "pipelines.ts",
1065
- // stale file detection — should not exist after migration
1066
1820
  "agents.d.ts",
1067
1821
  "queries.d.ts",
1068
1822
  "connectors.d.ts",
1823
+ "actions.d.ts",
1069
1824
  "platform-types.d.ts",
1070
1825
  "index.d.ts",
1071
1826
  "manifest.json"
@@ -1097,9 +1852,49 @@ var handleGenerate = async (parsed, deps) => {
1097
1852
  deps.log(`Error generating types: ${err instanceof Error ? err.message : String(err)}`);
1098
1853
  return 1;
1099
1854
  }
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.`);
1855
+ const localNames = {
1856
+ agents: new Set(manifest.agents.map((a) => a.name)),
1857
+ connectors: new Set(manifest.connectors.map((c) => c.name)),
1858
+ skills: new Set(manifest.skills.map((s) => s.name)),
1859
+ actions: new Set(manifest.actions.map((a) => a.name))
1860
+ };
1861
+ const matchesLocalSummary = (remoteId, names) => {
1862
+ const slashIdx = remoteId.indexOf("/");
1863
+ if (slashIdx !== -1) {
1864
+ return names.has(remoteId.slice(slashIdx + 1));
1865
+ }
1866
+ return names.has(remoteId);
1867
+ };
1868
+ const remoteOnlyAgents = remote.agents.filter(
1869
+ (a) => !matchesLocalSummary(a.id, localNames.agents)
1870
+ );
1871
+ const remoteOnlyConnectors = remote.connectors.filter(
1872
+ (c) => !matchesLocalSummary(c.id, localNames.connectors)
1873
+ );
1874
+ const remoteOnlySkills = remote.skills.filter(
1875
+ (s) => !matchesLocalSummary(s.id, localNames.skills)
1876
+ );
1877
+ const remoteOnlyActions = remote.actions.filter(
1878
+ (a) => !matchesLocalSummary(a.id, localNames.actions)
1879
+ );
1880
+ const lines = [];
1881
+ lines.push(` ${dim("[kraken-ai]")} ${bold("Generated types:")}`);
1882
+ for (const a of manifest.agents) lines.push(` agent ${a.name}`);
1883
+ for (const a of remoteOnlyAgents) lines.push(` agent ${a.id} (remote)`);
1884
+ for (const c of manifest.connectors) lines.push(` connector ${c.name}`);
1885
+ for (const c of remoteOnlyConnectors) lines.push(` connector ${c.id} (remote)`);
1886
+ for (const s of manifest.skills) lines.push(` skill ${s.name}`);
1887
+ for (const s of remoteOnlySkills) lines.push(` skill ${s.id} (remote)`);
1888
+ for (const a of manifest.actions) lines.push(` action ${a.name}`);
1889
+ for (const a of remoteOnlyActions) lines.push(` action ${a.id} (remote)`);
1890
+ if (bundle.queries.length > 0) lines.push(` queries ${String(bundle.queries.length)}`);
1891
+ if (bundle.pipelines.length > 0) lines.push(` pipelines ${String(bundle.pipelines.length)}`);
1892
+ const hasRemote = remoteOnlyAgents.length + remoteOnlyConnectors.length + remoteOnlySkills.length + remoteOnlyActions.length > 0;
1893
+ if (!hasRemote && remoteReachable) {
1894
+ lines.push("");
1895
+ lines.push(" All platform entities are defined locally. No remote-only dependencies.");
1896
+ }
1897
+ deps.log(lines.join("\n"));
1103
1898
  return 0;
1104
1899
  };
1105
1900
  var safeReadFile = (readFileSync, filePath) => {
@@ -1123,7 +1918,7 @@ var buildDevBootstrap = (fileUrl, file) => [
1123
1918
  ].join("\n");
1124
1919
  var discoverAgentFiles = async () => {
1125
1920
  const { readdirSync, existsSync } = await import("fs");
1126
- const agentsDir = "agents";
1921
+ const agentsDir = "src/agents";
1127
1922
  if (!existsSync(agentsDir)) return [];
1128
1923
  return readdirSync(agentsDir).filter((f) => f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".mjs")).sort();
1129
1924
  };
@@ -1132,7 +1927,9 @@ var handleDev = async (parsed, deps) => {
1132
1927
  if (!name) {
1133
1928
  const agents = await discoverAgentFiles();
1134
1929
  if (agents.length === 0) {
1135
- deps.log("No agent files found in agents/. Create one or pass a name: kraken dev <agent>");
1930
+ deps.log(
1931
+ "No agent files found in src/agents/. Create one or pass a name: kraken dev <agent>"
1932
+ );
1136
1933
  return 1;
1137
1934
  }
1138
1935
  if (agents.length === 1) {
@@ -1144,7 +1941,7 @@ var handleDev = async (parsed, deps) => {
1144
1941
  }
1145
1942
  }
1146
1943
  if (!name) return 1;
1147
- const file = name.includes("/") ? name : `agents/${name}`;
1944
+ const file = name.includes("/") ? name : `src/agents/${name}`;
1148
1945
  const { spawn } = await import("child_process");
1149
1946
  const { resolve } = await import("path");
1150
1947
  const { pathToFileURL: pathToFileURL2 } = await import("url");
@@ -1161,9 +1958,11 @@ var handleDev = async (parsed, deps) => {
1161
1958
  var handleDiscover = async (parsed, deps) => {
1162
1959
  const dir = parsed.flags.dir ?? process.cwd();
1163
1960
  const manifest = await deps.discoverProject(dir);
1164
- const total = manifest.agents.length + manifest.skills.length + manifest.connectors.length;
1961
+ const total = manifest.agents.length + manifest.skills.length + manifest.connectors.length + manifest.actions.length;
1165
1962
  if (total === 0) {
1166
- deps.log("No entities found in agents/, skills/, or connectors/ directories.");
1963
+ deps.log(
1964
+ "No entities found in src/agents/, src/skills/, src/connectors/, or src/actions/ directories."
1965
+ );
1167
1966
  return 1;
1168
1967
  }
1169
1968
  if ("emit" in parsed.flags) {
@@ -1173,89 +1972,6 @@ var handleDiscover = async (parsed, deps) => {
1173
1972
  }
1174
1973
  return 0;
1175
1974
  };
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
1975
  var handleLogout = (deps) => {
1260
1976
  deps.clearCredentials();
1261
1977
  deps.log("Credentials cleared.");
@@ -1267,23 +1983,20 @@ kraken \u2014 Kraken AI Platform CLI
1267
1983
 
1268
1984
  Commands:
1269
1985
  dev Run an agent locally with an interactive REPL
1270
- [agent] Agent filename, e.g. researcher.ts (optional \u2014 picks from agents/ if omitted)
1986
+ [agent] Agent filename, e.g. researcher.ts (optional \u2014 picks from src/agents/ if omitted)
1271
1987
 
1272
- login Store API credentials
1988
+ login Authenticate via browser-based device authorization
1273
1989
  --url <platform-url> Platform instance URL
1274
- --key <api-key> API key
1990
+
1991
+ api-keys Manage API keys for server-side SDK authentication
1992
+ create --name <name> [--expires 90d] Create a new API key
1993
+ list List all API keys
1994
+ revoke <id> Revoke an API key
1275
1995
 
1276
1996
  generate Discover local entities and fetch remote schemas to generate types
1277
1997
  --dir <path> Project root / output directory (default: cwd)
1278
1998
  --check Check if types are up-to-date (exit 1 if stale)
1279
1999
 
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
2000
  logout Clear stored credentials
1288
2001
 
1289
2002
  Environment variables:
@@ -1302,6 +2015,21 @@ var readlinePrompt = async (question) => {
1302
2015
  });
1303
2016
  });
1304
2017
  };
2018
+ var openUrlImpl = async (url) => {
2019
+ if (!/^https?:\/\//.test(url)) return;
2020
+ const { platform } = await import("os");
2021
+ try {
2022
+ const p = platform();
2023
+ if (p === "darwin") {
2024
+ await execFileAsync("open", [url]);
2025
+ } else if (p === "win32") {
2026
+ await execFileAsync("cmd", ["/c", "start", "", url]);
2027
+ } else {
2028
+ await execFileAsync("xdg-open", [url]);
2029
+ }
2030
+ } catch {
2031
+ }
2032
+ };
1305
2033
  var main = async () => {
1306
2034
  const parsed = parseArgs(process.argv.slice(2));
1307
2035
  const exitCode = await runCommand(parsed, {
@@ -1312,7 +2040,8 @@ var main = async () => {
1312
2040
  discoverProject,
1313
2041
  fetch: globalThis.fetch,
1314
2042
  prompt: readlinePrompt,
1315
- log: console.log
2043
+ log: console.log,
2044
+ openUrl: openUrlImpl
1316
2045
  });
1317
2046
  if (exitCode !== 0) process.exit(exitCode);
1318
2047
  };