@ekanos/cli 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/agents-edit.d.ts +49 -0
  3. package/dist/commands/agents-edit.js +385 -0
  4. package/dist/commands/agents-edit.js.map +1 -0
  5. package/dist/commands/agents.d.ts +24 -1
  6. package/dist/commands/agents.js +117 -8
  7. package/dist/commands/agents.js.map +1 -1
  8. package/dist/commands/apps.d.ts +3 -0
  9. package/dist/commands/apps.js +2 -2
  10. package/dist/commands/apps.js.map +1 -1
  11. package/dist/commands/reviewers.d.ts +21 -0
  12. package/dist/commands/reviewers.js +162 -0
  13. package/dist/commands/reviewers.js.map +1 -0
  14. package/dist/commands/runs.d.ts +42 -0
  15. package/dist/commands/runs.js +362 -0
  16. package/dist/commands/runs.js.map +1 -0
  17. package/dist/commands/skills.d.ts +60 -0
  18. package/dist/commands/skills.js +397 -0
  19. package/dist/commands/skills.js.map +1 -0
  20. package/dist/commands/team-agents-agents.d.ts +9 -0
  21. package/dist/commands/team-agents-agents.js +188 -0
  22. package/dist/commands/team-agents-agents.js.map +1 -0
  23. package/dist/commands/team-agents-mcp.d.ts +4 -0
  24. package/dist/commands/team-agents-mcp.js +218 -0
  25. package/dist/commands/team-agents-mcp.js.map +1 -0
  26. package/dist/commands/team-agents-triggers.d.ts +12 -0
  27. package/dist/commands/team-agents-triggers.js +251 -0
  28. package/dist/commands/team-agents-triggers.js.map +1 -0
  29. package/dist/commands/team-agents.d.ts +31 -0
  30. package/dist/commands/team-agents.js +71 -0
  31. package/dist/commands/team-agents.js.map +1 -0
  32. package/dist/commands/teams-onboard.d.ts +51 -0
  33. package/dist/commands/teams-onboard.js +261 -0
  34. package/dist/commands/teams-onboard.js.map +1 -0
  35. package/dist/commands/teams.d.ts +45 -0
  36. package/dist/commands/teams.js +314 -0
  37. package/dist/commands/teams.js.map +1 -0
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.js +222 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/reviewers-api.d.ts +33 -0
  42. package/dist/reviewers-api.js +81 -0
  43. package/dist/reviewers-api.js.map +1 -0
  44. package/dist/runs-api.d.ts +34 -0
  45. package/dist/runs-api.js +112 -0
  46. package/dist/runs-api.js.map +1 -0
  47. package/dist/skills-api.d.ts +28 -0
  48. package/dist/skills-api.js +104 -0
  49. package/dist/skills-api.js.map +1 -0
  50. package/dist/teams-api.d.ts +85 -0
  51. package/dist/teams-api.js +168 -0
  52. package/dist/teams-api.js.map +1 -0
  53. package/package.json +2 -2
  54. package/templates/package.json.tmpl +4 -4
  55. package/templates/skills/build-agent/SKILL.md +106 -17
  56. package/templates/skills/build-agent/references/agent-json.md +78 -2
  57. package/templates/skills/build-agent/references/rollout.md +9 -1
  58. package/templates/skills/build-agent/references/set-up-an-integration.md +134 -0
  59. package/templates/skills/build-agent/references/skills.md +78 -0
  60. package/templates/skills/build-agent/references/triggers.md +27 -6
  61. package/templates/skills/ekanos-platform/SKILL.md +115 -5
@@ -0,0 +1,162 @@
1
+ import { refreshStoredSession, requireSession, resolveAuthEnvironment, } from '../auth/session.js';
2
+ import { authRequiredError, usageError } from '../errors.js';
3
+ import { addReviewer, listReviewers, removeReviewer, } from '../reviewers-api.js';
4
+ import { resolveSourceTarget } from '../source-target.js';
5
+ export const REVIEWERS_SUBCOMMANDS = ['list', 'add', 'remove'];
6
+ const REACHES = ['source', 'team'];
7
+ /**
8
+ * `reviewers` — a Source admin names who decides its agents' reply drafts
9
+ * held under an `approve` policy. A reviewer is one of the Source's own
10
+ * users, at a reach: `source` (drafts from every team's provided agents) or
11
+ * `team` (one named team's). `list` shows them; `add` names one; `remove`
12
+ * takes one off — every reach, or only the named team's with `--team`.
13
+ */
14
+ export async function runReviewers(ctx, args) {
15
+ var _a;
16
+ var _b;
17
+ const [rawSubcommand, rawEmail, ...extra] = args.positionals;
18
+ const subcommand = parseSubcommand(rawSubcommand);
19
+ if (subcommand === 'list') {
20
+ if (rawEmail !== undefined || args.reach || args.team) {
21
+ throw usageError('reviewers list takes no arguments.', 'Run "ekanos reviewers list --source <slug>".');
22
+ }
23
+ }
24
+ else if (extra.length > 0) {
25
+ throw usageError(`Unexpected argument "${extra[0]}".`, 'See "ekanos reviewers --help".');
26
+ }
27
+ const email = subcommand === 'list' ? '' : requireEmail(rawEmail, subcommand);
28
+ const team = args.team === undefined ? undefined : requireTeam(args.team);
29
+ const reach = subcommand === 'add' ? requireReach(args.reach, email, team) : undefined;
30
+ if (subcommand === 'remove' && args.reach) {
31
+ throw usageError('reviewers remove takes no --reach.', `Run "ekanos reviewers remove ${email}" (every reach) or add "--team <slug>" for one team's.`);
32
+ }
33
+ const env = resolveAuthEnvironment(ctx, args.host, args.env, {
34
+ projectDir: args.dir,
35
+ });
36
+ const resolved = await resolveSourceTarget(env, requireSession(env.store, env.host), args);
37
+ const client = new ReviewersClient(env, resolved.session);
38
+ const source = resolved.source;
39
+ if (subcommand === 'list') {
40
+ const list = await client.call((token) => listReviewers(env.host, token, source));
41
+ if (list.reviewers.length === 0) {
42
+ return ctx.succeed(Object.assign({ host: env.host }, list), ctx.ui.result({
43
+ title: `No reviewers on ${list.source.slug}`,
44
+ tone: 'warn',
45
+ notes: [
46
+ 'Reply drafts under an "approve" policy wait for a named reviewer.',
47
+ 'Name one: "ekanos reviewers add <email> --reach source".',
48
+ ],
49
+ }));
50
+ }
51
+ return ctx.succeed(Object.assign({ host: env.host }, list), [
52
+ ctx.ui.bold(`Reviewers on ${list.source.slug}`),
53
+ ctx.ui.table(['Email', 'Name', 'Reach', 'Team'], list.reviewers.map(reviewerRow)),
54
+ ].join('\n'));
55
+ }
56
+ if (subcommand === 'add' && reach) {
57
+ const result = await client.call((token) => addReviewer(env.host, token, source, Object.assign({ email,
58
+ reach }, (team ? { team } : {}))));
59
+ const reviewer = result.reviewer;
60
+ return ctx.succeed(Object.assign({ host: env.host }, result), ctx.ui.result({
61
+ title: result.created
62
+ ? `Added ${who(reviewer)} as a reviewer`
63
+ : `${who(reviewer)} already reviews at that reach`,
64
+ tone: result.created ? 'ok' : 'warn',
65
+ rows: [
66
+ ['Source', source],
67
+ ['Reach', reachLabel(reviewer)],
68
+ ],
69
+ notes: [
70
+ reviewer.reach === 'source'
71
+ ? "Decides drafts from every team's provided agents, in the Source's review queue."
72
+ : `Decides ${(_b = (_a = reviewer.team) === null || _a === void 0 ? void 0 : _a.slug) !== null && _b !== void 0 ? _b : 'the team'}'s drafts, from its Approvals inbox or the Source's review queue.`,
73
+ ],
74
+ }));
75
+ }
76
+ const result = await client.call((token) => removeReviewer(env.host, token, source, Object.assign({ email }, (team ? { team } : {}))));
77
+ return ctx.succeed(Object.assign({ host: env.host }, result), ctx.ui.result({
78
+ title: `Removed ${email}${team ? ` from ${team}` : ''}`,
79
+ rows: [
80
+ ['Source', source],
81
+ [
82
+ 'Removed',
83
+ `${result.removed} reach${result.removed === 1 ? '' : 'es'}`,
84
+ ],
85
+ ],
86
+ }));
87
+ }
88
+ function reviewerRow(reviewer) {
89
+ var _a;
90
+ var _b, _c, _d;
91
+ return [
92
+ (_b = reviewer.email) !== null && _b !== void 0 ? _b : reviewer.userId,
93
+ (_c = reviewer.name) !== null && _c !== void 0 ? _c : '—',
94
+ reviewer.reach,
95
+ (_d = (_a = reviewer.team) === null || _a === void 0 ? void 0 : _a.slug) !== null && _d !== void 0 ? _d : '—',
96
+ ];
97
+ }
98
+ function reachLabel(reviewer) {
99
+ return reviewer.reach === 'team' && reviewer.team
100
+ ? `team ${reviewer.team.slug} (${reviewer.team.name})`
101
+ : 'source';
102
+ }
103
+ function who(reviewer) {
104
+ var _a, _b;
105
+ return (_b = (_a = reviewer.email) !== null && _a !== void 0 ? _a : reviewer.name) !== null && _b !== void 0 ? _b : reviewer.userId;
106
+ }
107
+ class ReviewersClient {
108
+ constructor(env, session) {
109
+ this.env = env;
110
+ this.session = session;
111
+ }
112
+ async call(send) {
113
+ let result = await send(this.session.accessToken);
114
+ if (result.status === 'unauthorized') {
115
+ this.session = await refreshStoredSession(this.env, this.session);
116
+ result = await send(this.session.accessToken);
117
+ }
118
+ if (result.status === 'ok')
119
+ return result.data;
120
+ throw authRequiredError(`The session for ${this.env.host} could not authorize the reviewers call.`, `Run "ekanos login --host ${this.env.host}" and retry.`);
121
+ }
122
+ }
123
+ function parseSubcommand(value) {
124
+ const match = REVIEWERS_SUBCOMMANDS.find((name) => name === value);
125
+ if (match)
126
+ return match;
127
+ throw usageError(value
128
+ ? `Unknown reviewers subcommand "${value}".`
129
+ : 'reviewers needs a subcommand.', 'Run one of: ekanos reviewers list, ekanos reviewers add <email> --reach source|team [--team <slug>], ekanos reviewers remove <email> [--team <slug>].');
130
+ }
131
+ function requireEmail(value, subcommand) {
132
+ const email = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();
133
+ if (email && /^[^\s@]+@[^\s@]+$/.test(email))
134
+ return email;
135
+ throw usageError(value
136
+ ? `"${value}" is not an email address.`
137
+ : 'Name the reviewer by email.', subcommand === 'add'
138
+ ? 'e.g. "ekanos reviewers add ana@example.com --reach source".'
139
+ : '"ekanos reviewers list" shows who reviews.');
140
+ }
141
+ function requireTeam(value) {
142
+ const slug = value.trim().toLowerCase();
143
+ if (/^[a-z0-9][a-z0-9-]*$/.test(slug))
144
+ return slug;
145
+ throw usageError(`"${value}" is not a team slug.`, 'Run "ekanos teams list" to see the teams on this Source.');
146
+ }
147
+ function requireReach(value, email, team) {
148
+ const reach = REACHES.find((entry) => entry === (value === null || value === void 0 ? void 0 : value.trim().toLowerCase()));
149
+ if (!reach) {
150
+ throw usageError(value
151
+ ? `"${value}" is not a reach: use source or team.`
152
+ : 'Name the reach: --reach source or --reach team.', `e.g. "ekanos reviewers add ${email} --reach source", or "--reach team --team <slug>" for one team's drafts.`);
153
+ }
154
+ if (reach === 'team' && !team) {
155
+ throw usageError('A team-reach reviewer needs --team <slug>.', 'Run "ekanos teams list" to see the teams on this Source.');
156
+ }
157
+ if (reach === 'source' && team) {
158
+ throw usageError('A source-reach reviewer decides for every team; drop --team.', `Run "ekanos reviewers add ${email} --reach source", or use "--reach team --team ${team}".`);
159
+ }
160
+ return reach;
161
+ }
162
+ //# sourceMappingURL=reviewers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reviewers.js","sourceRoot":"","sources":["../../src/commands/reviewers.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReviewerReach, SourceReviewer } from '@ekanos/integration-schema';\n\nimport type { StoredSession } from '../auth/credential-store';\nimport {\n type AuthEnvironment,\n refreshStoredSession,\n requireSession,\n resolveAuthEnvironment,\n} from '../auth/session';\nimport type { CliContext } from '../context';\nimport { authRequiredError, usageError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport {\n type ReviewersApiResult,\n addReviewer,\n listReviewers,\n removeReviewer,\n} from '../reviewers-api';\nimport { resolveSourceTarget } from '../source-target';\n\nexport const REVIEWERS_SUBCOMMANDS = ['list', 'add', 'remove'] as const;\ntype ReviewersSubcommand = (typeof REVIEWERS_SUBCOMMANDS)[number];\n\nconst REACHES: readonly ReviewerReach[] = ['source', 'team'];\n\nexport interface ReviewersArgs {\n host?: string;\n source?: string;\n /** `list`, `add <email>`, `remove <email>`. */\n positionals: readonly string[];\n reach?: string;\n team?: string;\n dir: string;\n env: Record<string, string | undefined>;\n}\n\n/**\n * `reviewers` — a Source admin names who decides its agents' reply drafts\n * held under an `approve` policy. A reviewer is one of the Source's own\n * users, at a reach: `source` (drafts from every team's provided agents) or\n * `team` (one named team's). `list` shows them; `add` names one; `remove`\n * takes one off — every reach, or only the named team's with `--team`.\n */\nexport async function runReviewers(\n ctx: CliContext,\n args: ReviewersArgs,\n): Promise<ExitCode> {\n const [rawSubcommand, rawEmail, ...extra] = args.positionals;\n const subcommand = parseSubcommand(rawSubcommand);\n\n if (subcommand === 'list') {\n if (rawEmail !== undefined || args.reach || args.team) {\n throw usageError(\n 'reviewers list takes no arguments.',\n 'Run \"ekanos reviewers list --source <slug>\".',\n );\n }\n } else if (extra.length > 0) {\n throw usageError(\n `Unexpected argument \"${extra[0]}\".`,\n 'See \"ekanos reviewers --help\".',\n );\n }\n\n const email = subcommand === 'list' ? '' : requireEmail(rawEmail, subcommand);\n const team = args.team === undefined ? undefined : requireTeam(args.team);\n const reach =\n subcommand === 'add' ? requireReach(args.reach, email, team) : undefined;\n if (subcommand === 'remove' && args.reach) {\n throw usageError(\n 'reviewers remove takes no --reach.',\n `Run \"ekanos reviewers remove ${email}\" (every reach) or add \"--team <slug>\" for one team's.`,\n );\n }\n\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n const resolved = await resolveSourceTarget(\n env,\n requireSession(env.store, env.host),\n args,\n );\n const client = new ReviewersClient(env, resolved.session);\n const source = resolved.source;\n\n if (subcommand === 'list') {\n const list = await client.call((token) =>\n listReviewers(env.host, token, source),\n );\n if (list.reviewers.length === 0) {\n return ctx.succeed(\n { host: env.host, ...list },\n ctx.ui.result({\n title: `No reviewers on ${list.source.slug}`,\n tone: 'warn',\n notes: [\n 'Reply drafts under an \"approve\" policy wait for a named reviewer.',\n 'Name one: \"ekanos reviewers add <email> --reach source\".',\n ],\n }),\n );\n }\n return ctx.succeed(\n { host: env.host, ...list },\n [\n ctx.ui.bold(`Reviewers on ${list.source.slug}`),\n ctx.ui.table(\n ['Email', 'Name', 'Reach', 'Team'],\n list.reviewers.map(reviewerRow),\n ),\n ].join('\\n'),\n );\n }\n\n if (subcommand === 'add' && reach) {\n const result = await client.call((token) =>\n addReviewer(env.host, token, source, {\n email,\n reach,\n ...(team ? { team } : {}),\n }),\n );\n const reviewer = result.reviewer;\n return ctx.succeed(\n { host: env.host, ...result },\n ctx.ui.result({\n title: result.created\n ? `Added ${who(reviewer)} as a reviewer`\n : `${who(reviewer)} already reviews at that reach`,\n tone: result.created ? 'ok' : 'warn',\n rows: [\n ['Source', source],\n ['Reach', reachLabel(reviewer)],\n ],\n notes: [\n reviewer.reach === 'source'\n ? \"Decides drafts from every team's provided agents, in the Source's review queue.\"\n : `Decides ${reviewer.team?.slug ?? 'the team'}'s drafts, from its Approvals inbox or the Source's review queue.`,\n ],\n }),\n );\n }\n\n const result = await client.call((token) =>\n removeReviewer(env.host, token, source, {\n email,\n ...(team ? { team } : {}),\n }),\n );\n return ctx.succeed(\n { host: env.host, ...result },\n ctx.ui.result({\n title: `Removed ${email}${team ? ` from ${team}` : ''}`,\n rows: [\n ['Source', source],\n [\n 'Removed',\n `${result.removed} reach${result.removed === 1 ? '' : 'es'}`,\n ],\n ],\n }),\n );\n}\n\nfunction reviewerRow(reviewer: SourceReviewer): string[] {\n return [\n reviewer.email ?? reviewer.userId,\n reviewer.name ?? '—',\n reviewer.reach,\n reviewer.team?.slug ?? '—',\n ];\n}\n\nfunction reachLabel(reviewer: SourceReviewer): string {\n return reviewer.reach === 'team' && reviewer.team\n ? `team ${reviewer.team.slug} (${reviewer.team.name})`\n : 'source';\n}\n\nfunction who(reviewer: SourceReviewer): string {\n return reviewer.email ?? reviewer.name ?? reviewer.userId;\n}\n\nclass ReviewersClient {\n constructor(\n private readonly env: AuthEnvironment,\n private session: StoredSession,\n ) {}\n\n async call<T>(\n send: (accessToken: string) => Promise<ReviewersApiResult<T>>,\n ): Promise<T> {\n let result = await send(this.session.accessToken);\n if (result.status === 'unauthorized') {\n this.session = await refreshStoredSession(this.env, this.session);\n result = await send(this.session.accessToken);\n }\n if (result.status === 'ok') return result.data;\n throw authRequiredError(\n `The session for ${this.env.host} could not authorize the reviewers call.`,\n `Run \"ekanos login --host ${this.env.host}\" and retry.`,\n );\n }\n}\n\nfunction parseSubcommand(value: string | undefined): ReviewersSubcommand {\n const match = REVIEWERS_SUBCOMMANDS.find((name) => name === value);\n if (match) return match;\n throw usageError(\n value\n ? `Unknown reviewers subcommand \"${value}\".`\n : 'reviewers needs a subcommand.',\n 'Run one of: ekanos reviewers list, ekanos reviewers add <email> --reach source|team [--team <slug>], ekanos reviewers remove <email> [--team <slug>].',\n );\n}\n\nfunction requireEmail(value: string | undefined, subcommand: 'add' | 'remove') {\n const email = value?.trim().toLowerCase();\n if (email && /^[^\\s@]+@[^\\s@]+$/.test(email)) return email;\n throw usageError(\n value\n ? `\"${value}\" is not an email address.`\n : 'Name the reviewer by email.',\n subcommand === 'add'\n ? 'e.g. \"ekanos reviewers add ana@example.com --reach source\".'\n : '\"ekanos reviewers list\" shows who reviews.',\n );\n}\n\nfunction requireTeam(value: string): string {\n const slug = value.trim().toLowerCase();\n if (/^[a-z0-9][a-z0-9-]*$/.test(slug)) return slug;\n throw usageError(\n `\"${value}\" is not a team slug.`,\n 'Run \"ekanos teams list\" to see the teams on this Source.',\n );\n}\n\nfunction requireReach(\n value: string | undefined,\n email: string,\n team: string | undefined,\n): ReviewerReach {\n const reach = REACHES.find((entry) => entry === value?.trim().toLowerCase());\n if (!reach) {\n throw usageError(\n value\n ? `\"${value}\" is not a reach: use source or team.`\n : 'Name the reach: --reach source or --reach team.',\n `e.g. \"ekanos reviewers add ${email} --reach source\", or \"--reach team --team <slug>\" for one team's drafts.`,\n );\n }\n if (reach === 'team' && !team) {\n throw usageError(\n 'A team-reach reviewer needs --team <slug>.',\n 'Run \"ekanos teams list\" to see the teams on this Source.',\n );\n }\n if (reach === 'source' && team) {\n throw usageError(\n 'A source-reach reviewer decides for every team; drop --team.',\n `Run \"ekanos reviewers add ${email} --reach source\", or use \"--reach team --team ${team}\".`,\n );\n }\n return reach;\n}\n"]}
@@ -0,0 +1,42 @@
1
+ import type { CliContext } from '../context.js';
2
+ import type { ExitCode } from '../exit-codes.js';
3
+ export declare const RUNS_SUBCOMMANDS: readonly ['start', 'list', 'show'];
4
+ export declare const APPROVALS_SUBCOMMANDS: readonly ['list', 'approve', 'reject', 'edit'];
5
+ export interface RunsArgs {
6
+ host?: string;
7
+ source?: string;
8
+ positionals: readonly string[];
9
+ team?: string;
10
+ agent?: string;
11
+ prompt?: string;
12
+ input?: string;
13
+ follow: boolean;
14
+ reason?: string;
15
+ body?: string;
16
+ dir: string;
17
+ env: Record<string, string | undefined>;
18
+ /** Test seam for the `--follow` poll delay. */
19
+ sleep?: (ms: number) => Promise<void>;
20
+ }
21
+ /**
22
+ * `runs` — run, list and inspect a team's agents as its Source admin. A run
23
+ * started here is the app's "Run now": it runs as the agent's creator under
24
+ * the agent's own grant, policy and approvals (writes still wait for one),
25
+ * and is attributed to you.
26
+ */
27
+ export declare function runRuns(ctx: CliContext, args: RunsArgs): Promise<ExitCode>;
28
+ /**
29
+ * `agents test <key> --team <slug>`: run the template's latest version — its
30
+ * draft, published or not — on one team, without publishing it. Marked as a
31
+ * draft test run in the ledger; the team's stored agent still bounds every
32
+ * tool call, so a draft can narrow what runs but never widen it.
33
+ */
34
+ export declare function runAgentTest(ctx: CliContext, args: RunsArgs & {
35
+ key: string | undefined;
36
+ }): Promise<ExitCode>;
37
+ /**
38
+ * `approvals` — what a team's agent runs are waiting on, and deciding it.
39
+ * Decided with the authority a team's agent manager has in the app, and
40
+ * recorded as your decision.
41
+ */
42
+ export declare function runApprovals(ctx: CliContext, args: RunsArgs): Promise<ExitCode>;
@@ -0,0 +1,362 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { refreshStoredSession, requireSession, resolveAuthEnvironment, } from '../auth/session.js';
4
+ import { authRequiredError, usageError, validationError } from '../errors.js';
5
+ import { decideApproval, listApprovals, listRuns, showRun, startRun, } from '../runs-api.js';
6
+ import { resolveSourceTarget } from '../source-target.js';
7
+ export const RUNS_SUBCOMMANDS = ['start', 'list', 'show'];
8
+ export const APPROVALS_SUBCOMMANDS = [
9
+ 'list',
10
+ 'approve',
11
+ 'reject',
12
+ 'edit',
13
+ ];
14
+ const FOLLOW_INTERVAL_MS = 2000;
15
+ const FOLLOW_TIMEOUT_MS = 15 * 60 * 1000;
16
+ /**
17
+ * `runs` — run, list and inspect a team's agents as its Source admin. A run
18
+ * started here is the app's "Run now": it runs as the agent's creator under
19
+ * the agent's own grant, policy and approvals (writes still wait for one),
20
+ * and is attributed to you.
21
+ */
22
+ export async function runRuns(ctx, args) {
23
+ const [rawSubcommand, operand, ...extra] = args.positionals;
24
+ const subcommand = RUNS_SUBCOMMANDS.find((name) => name === rawSubcommand);
25
+ if (!subcommand) {
26
+ throw usageError(rawSubcommand
27
+ ? `Unknown runs subcommand "${rawSubcommand}".`
28
+ : 'runs needs a subcommand.', 'Run one of: ekanos runs start <agent> --team <slug> --prompt "…", ekanos runs list --team <slug>, ekanos runs show <run-id> --team <slug>.');
29
+ }
30
+ rejectExtra(extra, 'runs');
31
+ const team = requireTeam(args.team);
32
+ if (subcommand === 'start') {
33
+ const agent = requireOperand(operand, 'agent key', 'runs start');
34
+ return startAndReport(ctx, args, { team, agent, draft: false });
35
+ }
36
+ const { env, client, source } = await connect(ctx, args);
37
+ if (subcommand === 'list') {
38
+ if (operand)
39
+ rejectExtra([operand], 'runs list');
40
+ const list = await client.call((token) => listRuns(env.host, token, { source, team, agent: args.agent }));
41
+ return ctx.succeed(Object.assign({ host: env.host }, list), list.runs.length === 0
42
+ ? ctx.ui.result({
43
+ title: `No runs on ${list.team.slug}${args.agent ? ` for ${args.agent}` : ''}`,
44
+ tone: 'warn',
45
+ })
46
+ : ctx.ui.table(['RUN', 'AGENT', 'STATUS', 'TRIGGER', 'VERSION', 'STARTED'], list.runs.map((run) => [
47
+ run.id,
48
+ agentLabel(run.agent),
49
+ run.status,
50
+ run.trigger,
51
+ versionLabel(run),
52
+ run.startedAt.slice(0, 19).replace('T', ' '),
53
+ ])));
54
+ }
55
+ const runId = requireOperand(operand, 'run id', 'runs show');
56
+ const detail = await client.call((token) => showRun(env.host, token, { source, team, runId }));
57
+ return ctx.succeed(Object.assign({ host: env.host }, detail), runDetail(ctx.ui, detail));
58
+ }
59
+ /**
60
+ * `agents test <key> --team <slug>`: run the template's latest version — its
61
+ * draft, published or not — on one team, without publishing it. Marked as a
62
+ * draft test run in the ledger; the team's stored agent still bounds every
63
+ * tool call, so a draft can narrow what runs but never widen it.
64
+ */
65
+ export async function runAgentTest(ctx, args) {
66
+ const agent = requireOperand(args.key, 'agent key', 'agents test');
67
+ const team = requireTeam(args.team);
68
+ return startAndReport(ctx, args, { team, agent, draft: true });
69
+ }
70
+ /**
71
+ * `approvals` — what a team's agent runs are waiting on, and deciding it.
72
+ * Decided with the authority a team's agent manager has in the app, and
73
+ * recorded as your decision.
74
+ */
75
+ export async function runApprovals(ctx, args) {
76
+ const [rawSubcommand, operand, ...extra] = args.positionals;
77
+ const subcommand = APPROVALS_SUBCOMMANDS.find((name) => name === rawSubcommand);
78
+ if (!subcommand) {
79
+ throw usageError(rawSubcommand
80
+ ? `Unknown approvals subcommand "${rawSubcommand}".`
81
+ : 'approvals needs a subcommand.', 'Run one of: ekanos approvals list --team <slug>, ekanos approvals approve|reject <id> --team <slug> [--reason "…"], ekanos approvals edit <id> --team <slug> --body <file>.');
82
+ }
83
+ rejectExtra(extra, 'approvals');
84
+ const team = requireTeam(args.team);
85
+ if (subcommand === 'list') {
86
+ if (operand)
87
+ rejectExtra([operand], 'approvals list');
88
+ const { env, client, source } = await connect(ctx, args);
89
+ const list = await client.call((token) => listApprovals(env.host, token, { source, team }));
90
+ return ctx.succeed(Object.assign({ host: env.host }, list), list.approvals.length === 0
91
+ ? ctx.ui.result({ title: `Nothing waiting on ${list.team.slug}` })
92
+ : ctx.ui.result({
93
+ title: `${list.approvals.length} waiting on ${list.team.slug}`,
94
+ tone: 'warn',
95
+ rows: list.approvals.map((approval) => [
96
+ approval.id,
97
+ approvalLines(approval),
98
+ ]),
99
+ notes: [
100
+ '"ekanos approvals approve|reject <id> --team <slug>" decides one; "approvals edit <id> --body <file>" edits a reply draft and sends it.',
101
+ ],
102
+ }));
103
+ }
104
+ const approvalId = requireOperand(operand, 'approval id', `approvals ${subcommand}`);
105
+ const editedBody = subcommand === 'edit' ? readBodyFile(args.body, args.dir) : undefined;
106
+ if (subcommand !== 'edit' && args.body) {
107
+ throw usageError('--body is for "approvals edit".', `Run "ekanos approvals edit ${approvalId} --team ${team} --body <file>".`);
108
+ }
109
+ const { env, client, source } = await connect(ctx, args);
110
+ const decided = await client.call((token) => decideApproval(env.host, token, { source, team, approvalId }, Object.assign(Object.assign({ decision: subcommand === 'reject' ? 'rejected' : 'approved' }, (args.reason ? { reason: args.reason } : {})), (editedBody ? { body: editedBody } : {}))));
111
+ const approval = decided.approval;
112
+ return ctx.succeed(Object.assign({ host: env.host }, decided), ctx.ui.result({
113
+ title: `${approval.status === 'rejected' ? 'Rejected' : editedBody ? 'Edited and approved' : 'Approved'} ${approval.toolName}`,
114
+ tone: approval.status === 'rejected' ? 'warn' : 'ok',
115
+ rows: [
116
+ ['Agent', agentLabel(approval.agent)],
117
+ ['Run', approval.runId],
118
+ ],
119
+ notes: [
120
+ `The run picks it up now: "ekanos runs show ${approval.runId} --team ${team}".`,
121
+ ],
122
+ }));
123
+ }
124
+ async function startAndReport(ctx, args, target) {
125
+ const body = startBody(args, target);
126
+ const { env, client, source } = await connect(ctx, args);
127
+ const started = await client.call((token) => startRun(env.host, token, { source, team: target.team }, body));
128
+ const startedNote = `${target.draft ? 'Testing' : 'Started'} ${agentLabel(started.agent)} on ${started.team.slug}${started.draftTemplateVersion ? ` (draft v${started.draftTemplateVersion}, unpublished)` : ''}: run ${started.runId}`;
129
+ if (!args.follow) {
130
+ return ctx.succeed(Object.assign({ host: env.host }, started), ctx.ui.result({
131
+ title: startedNote,
132
+ notes: [
133
+ `Watch it: "ekanos runs show ${started.runId} --team ${target.team}".`,
134
+ ],
135
+ }));
136
+ }
137
+ ctx.log(startedNote);
138
+ const detail = await follow(ctx, args, async (after) => client.call((token) => showRun(env.host, token, {
139
+ source,
140
+ team: target.team,
141
+ runId: started.runId,
142
+ after,
143
+ })));
144
+ return ctx.succeed(Object.assign({ host: env.host, started }, detail), runDetail(ctx.ui, detail));
145
+ }
146
+ /**
147
+ * Poll the run, printing its status changes, its messages and each tool
148
+ * call as it lands, until it ends or parks on an approval.
149
+ */
150
+ async function follow(ctx, args, read) {
151
+ var _a;
152
+ const sleep = (_a = args.sleep) !== null && _a !== void 0 ? _a : ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
153
+ const seenCalls = new Set();
154
+ const events = [];
155
+ let lastEvent;
156
+ let lastStatus = '';
157
+ const deadline = Date.now() + FOLLOW_TIMEOUT_MS;
158
+ for (;;) {
159
+ const detail = await read(lastEvent);
160
+ for (const event of detail.events) {
161
+ events.push(event);
162
+ lastEvent = event.id;
163
+ for (const line of eventLines(event.message))
164
+ ctx.log(` ${line}`);
165
+ }
166
+ for (const call of detail.toolCalls) {
167
+ if (seenCalls.has(call.toolCallId))
168
+ continue;
169
+ seenCalls.add(call.toolCallId);
170
+ ctx.log(` ${call.ok === false ? '✗' : '✓'} ${call.toolName} (${call.effect}, ${call.authorizedVia})${call.error ? ` — ${call.error}` : ''}`);
171
+ }
172
+ if (detail.run.status !== lastStatus) {
173
+ lastStatus = detail.run.status;
174
+ ctx.log(` status: ${lastStatus}`);
175
+ }
176
+ const waiting = detail.approvals.some((approval) => approval.status === 'pending');
177
+ const ended = ['completed', 'failed', 'cancelled'].includes(detail.run.status);
178
+ if (ended || waiting || Date.now() > deadline) {
179
+ return Object.assign(Object.assign({}, detail), { events });
180
+ }
181
+ await sleep(FOLLOW_INTERVAL_MS);
182
+ }
183
+ }
184
+ /** Assistant text and tool calls from one live transcript message. */
185
+ function eventLines(message) {
186
+ if (!isRecord(message))
187
+ return [];
188
+ const content = message.content;
189
+ if (message.role === 'assistant' && typeof content === 'string') {
190
+ return content.trim() ? [`agent: ${clip(content)}`] : [];
191
+ }
192
+ if (!Array.isArray(content))
193
+ return [];
194
+ return content.flatMap((part) => {
195
+ if (!isRecord(part))
196
+ return [];
197
+ if (part.type === 'text' && message.role === 'assistant') {
198
+ return typeof part.text === 'string' && part.text.trim()
199
+ ? [`agent: ${clip(part.text)}`]
200
+ : [];
201
+ }
202
+ if (part.type === 'tool-call' && typeof part.toolName === 'string') {
203
+ return [`→ ${part.toolName}`];
204
+ }
205
+ return [];
206
+ });
207
+ }
208
+ function startBody(args, target) {
209
+ if (!args.prompt && !args.input) {
210
+ throw usageError('Give the run a prompt or an input.', `Pass --prompt "…" or --input <file.json>.`);
211
+ }
212
+ let input;
213
+ if (args.input) {
214
+ const file = path.resolve(args.dir, args.input);
215
+ let raw;
216
+ try {
217
+ raw = fs.readFileSync(file, 'utf8');
218
+ }
219
+ catch (_a) {
220
+ throw usageError(`Cannot read ${file}.`, 'Pass --input <file.json>.');
221
+ }
222
+ try {
223
+ input = JSON.parse(raw);
224
+ }
225
+ catch (_b) {
226
+ throw validationError(`${file} is not valid JSON.`, 'Fix the file, or pass the text with --prompt instead.');
227
+ }
228
+ }
229
+ return Object.assign(Object.assign(Object.assign({ agent: target.agent }, (args.prompt ? { prompt: args.prompt } : {})), (input !== undefined ? { input } : {})), { draft: target.draft });
230
+ }
231
+ function readBodyFile(file, dir) {
232
+ if (!file) {
233
+ throw usageError('approvals edit needs the reply to send.', 'Pass --body <file> with the edited reply text.');
234
+ }
235
+ const resolved = path.resolve(dir, file);
236
+ let text;
237
+ try {
238
+ text = fs.readFileSync(resolved, 'utf8');
239
+ }
240
+ catch (_a) {
241
+ throw usageError(`Cannot read ${resolved}.`, 'Pass --body <file>.');
242
+ }
243
+ if (!text.trim()) {
244
+ throw validationError(`${resolved} is empty.`, 'Write the reply to send.');
245
+ }
246
+ return text;
247
+ }
248
+ function runDetail(ui, detail) {
249
+ const { run } = detail;
250
+ const pending = detail.approvals.filter((approval) => approval.status === 'pending');
251
+ const rows = [
252
+ ['Agent', agentLabel(run.agent)],
253
+ ['Status', run.status],
254
+ ['Trigger', run.trigger],
255
+ ['Version', versionLabel(run)],
256
+ ['Started', run.startedAt],
257
+ ...(run.finishedAt ? [['Finished', run.finishedAt]] : []),
258
+ ...(run.error ? [['Error', run.error]] : []),
259
+ [
260
+ 'Tool calls',
261
+ detail.toolCalls.length === 0
262
+ ? 'none'
263
+ : detail.toolCalls.map((call) => `${call.ok === false ? '✗' : '✓'} ${call.toolName} (${call.effect}, ${call.authorizedVia})`),
264
+ ],
265
+ ...(pending.length > 0
266
+ ? [
267
+ [
268
+ 'Waiting on',
269
+ pending.flatMap((approval) => [
270
+ `${approval.id} ${approval.toolName}${approval.replyDraft ? ' (reply draft)' : ''}`,
271
+ ]),
272
+ ],
273
+ ]
274
+ : []),
275
+ ...(run.output ? [['Output', run.output.split('\n')]] : []),
276
+ ];
277
+ return ui.result({
278
+ title: `Run ${run.id}`,
279
+ tone: run.status === 'failed'
280
+ ? 'err'
281
+ : run.status === 'completed'
282
+ ? 'ok'
283
+ : 'warn',
284
+ rows,
285
+ notes: pending.length > 0
286
+ ? [
287
+ `Decide: "ekanos approvals approve|reject <id> --team ${detail.team.slug}". Full record: --json.`,
288
+ ]
289
+ : ['The full transcript and every tool call: --json.'],
290
+ });
291
+ }
292
+ function approvalLines(approval) {
293
+ var _a;
294
+ return [
295
+ `${approval.toolName} · ${agentLabel(approval.agent)}${approval.replyDraft ? ' · reply draft (editable)' : ''}`,
296
+ `run ${approval.runId}${approval.expiresAt ? ` · expires ${approval.expiresAt}` : ''}`,
297
+ clip(JSON.stringify((_a = approval.args) !== null && _a !== void 0 ? _a : null)),
298
+ ];
299
+ }
300
+ function agentLabel(agent) {
301
+ var _a;
302
+ return (_a = agent.templateKey) !== null && _a !== void 0 ? _a : agent.slug;
303
+ }
304
+ function versionLabel(run) {
305
+ return run.draftTemplateVersion
306
+ ? `v${run.agentVersion} · draft test of template v${run.draftTemplateVersion}`
307
+ : `v${run.agentVersion}`;
308
+ }
309
+ function clip(text, max = 200) {
310
+ const flat = text.replace(/\s+/g, ' ').trim();
311
+ return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
312
+ }
313
+ function isRecord(value) {
314
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
315
+ }
316
+ function requireTeam(value) {
317
+ const slug = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();
318
+ if (slug && /^[a-z0-9][a-z0-9-]*$/.test(slug))
319
+ return slug;
320
+ throw usageError(value
321
+ ? `"${value}" is not a team slug.`
322
+ : 'Name the team with --team <slug>.', '"ekanos teams list" lists the teams on this Source.');
323
+ }
324
+ function requireOperand(value, what, command) {
325
+ const trimmed = value === null || value === void 0 ? void 0 : value.trim();
326
+ if (trimmed)
327
+ return trimmed;
328
+ throw usageError(`Name the ${what}.`, `See "ekanos ${command} --help".`);
329
+ }
330
+ function rejectExtra(extra, command) {
331
+ if (extra.length > 0) {
332
+ throw usageError(`Unexpected argument "${extra[0]}".`, `See "ekanos ${command.split(' ')[0]} --help".`);
333
+ }
334
+ }
335
+ async function connect(ctx, args) {
336
+ const env = resolveAuthEnvironment(ctx, args.host, args.env, {
337
+ projectDir: args.dir,
338
+ });
339
+ const resolved = await resolveSourceTarget(env, requireSession(env.store, env.host), args);
340
+ return {
341
+ env,
342
+ client: new RunsClient(env, resolved.session),
343
+ source: resolved.source,
344
+ };
345
+ }
346
+ class RunsClient {
347
+ constructor(env, session) {
348
+ this.env = env;
349
+ this.session = session;
350
+ }
351
+ async call(send) {
352
+ let result = await send(this.session.accessToken);
353
+ if (result.status === 'unauthorized') {
354
+ this.session = await refreshStoredSession(this.env, this.session);
355
+ result = await send(this.session.accessToken);
356
+ }
357
+ if (result.status === 'ok')
358
+ return result.data;
359
+ throw authRequiredError(`The session for ${this.env.host} could not authorize the call.`, `Run "ekanos login --host ${this.env.host}" and retry.`);
360
+ }
361
+ }
362
+ //# sourceMappingURL=runs.js.map