@kikimoradev/kcloud 1.7.0 → 1.8.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 (2) hide show
  1. package/dist/main.js +365 -158
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2856,6 +2856,8 @@ z.object({
2856
2856
  "agent.state",
2857
2857
  "notification",
2858
2858
  "notification.count",
2859
+ "dashboard.agent",
2860
+ "dashboard.removed",
2859
2861
  "stream.closed",
2860
2862
  "error"
2861
2863
  ]),
@@ -2918,6 +2920,78 @@ z.object({
2918
2920
  contentDigest: agentBackupContentDigestSchema
2919
2921
  });
2920
2922
  z.object({ backupId: idSchema.nullable() });
2923
+ //#endregion
2924
+ //#region ../../packages/contracts/src/agent-phase.ts
2925
+ const agentPhaseKinds = [
2926
+ "offHours",
2927
+ "limitWait",
2928
+ "authBlocked",
2929
+ "queued",
2930
+ "waiting",
2931
+ "patrolling",
2932
+ "sleeping",
2933
+ "working",
2934
+ "summarising"
2935
+ ];
2936
+ const dashboardStatusSchema = z.enum([
2937
+ "working",
2938
+ "waiting",
2939
+ "paused",
2940
+ "stopped",
2941
+ "error",
2942
+ "transitioning"
2943
+ ]);
2944
+ const dashboardFrameNames = [
2945
+ "dashboard.agent",
2946
+ "dashboard.removed",
2947
+ "stream.closed",
2948
+ "error"
2949
+ ];
2950
+ const dashboardActivitySchema = z.object({
2951
+ role: agentRoleSchema,
2952
+ kind: z.enum(agentPhaseKinds),
2953
+ taskId: z.string().nullable(),
2954
+ title: z.string().nullable(),
2955
+ cycle: z.int().nullable(),
2956
+ sessionId: z.string().nullable(),
2957
+ startedAt: z.iso.datetime()
2958
+ }).meta({ id: "DashboardActivity" });
2959
+ const dashboardAgentSchema = z.object({
2960
+ id: idSchema,
2961
+ projectId: idSchema,
2962
+ name: z.string(),
2963
+ slug: slugSchema,
2964
+ state: agentStateSchema,
2965
+ status: dashboardStatusSchema,
2966
+ since: z.iso.datetime(),
2967
+ until: z.iso.datetime().nullable(),
2968
+ failureReason: z.string().nullable(),
2969
+ retryAt: z.iso.datetime().nullable(),
2970
+ activity: dashboardActivitySchema.nullable(),
2971
+ costTodayUsd: z.number().nonnegative()
2972
+ }).meta({ id: "DashboardAgent" });
2973
+ const dashboardProjectSchema = z.object({
2974
+ id: idSchema,
2975
+ name: z.string(),
2976
+ slug: slugSchema,
2977
+ color: colorSchema,
2978
+ logoHash: logoHashSchema.nullable(),
2979
+ agents: z.array(dashboardAgentSchema)
2980
+ }).meta({ id: "DashboardProject" });
2981
+ z.object({
2982
+ day: z.iso.date(),
2983
+ projects: z.array(dashboardProjectSchema)
2984
+ }).meta({ id: "OrganizationDashboard" });
2985
+ const dashboardRemovalSchema = z.object({ agentId: idSchema }).meta({ id: "DashboardRemoval" });
2986
+ z.object({
2987
+ event: z.enum(dashboardFrameNames),
2988
+ data: z.union([
2989
+ dashboardAgentSchema,
2990
+ dashboardRemovalSchema,
2991
+ z.object({ reason: z.string() }),
2992
+ z.object({ code: errorCodeSchema })
2993
+ ])
2994
+ }).meta({ id: "DashboardFrame" });
2921
2995
  [
2922
2996
  "application/vnd.oci.image.index.v1+json",
2923
2997
  "application/vnd.oci.image.manifest.v1+json",
@@ -4207,10 +4281,10 @@ const splitHints = {
4207
4281
  "node.split_exceeds_host": "The split asks for more than the host has; kcloud nodes split <node> shows the host."
4208
4282
  };
4209
4283
  const nodesOf = async (api, organizationId) => dataOf("list the nodes", await api.GET("/organizations/{organizationId}/nodes", { params: { path: { organizationId } } }), nodeHints).items;
4210
- const namesOf = (nodes) => nodes.map(({ name }) => name).join(", ");
4284
+ const namesOf$1 = (nodes) => nodes.map(({ name }) => name).join(", ");
4211
4285
  const nodeIn = (nodes, reference, organization) => {
4212
4286
  const found = nodes.find((node) => node.id === reference.toLowerCase() || node.name === reference);
4213
- if (found === void 0) throw new CliError(`There is no node ${reference} in ${organization}.`, nodes.length === 0 ? "kcloud nodes create <name> creates one." : `kcloud nodes list shows ${namesOf(nodes)}.`);
4287
+ if (found === void 0) throw new CliError(`There is no node ${reference} in ${organization}.`, nodes.length === 0 ? "kcloud nodes create <name> creates one." : `kcloud nodes list shows ${namesOf$1(nodes)}.`);
4214
4288
  return found;
4215
4289
  };
4216
4290
  const resolveNode = async (scope, reference) => {
@@ -5233,6 +5307,284 @@ const createCommand = (adaptersOf) => defineCommand({
5233
5307
  }
5234
5308
  });
5235
5309
  //#endregion
5310
+ //#region src/client/stream.ts
5311
+ const firstBackoffMs = 1e3;
5312
+ const lastBackoffMs = 3e4;
5313
+ const defaultIdleMs = 1e3;
5314
+ const fatalStatuses = /* @__PURE__ */ new Set([
5315
+ 400,
5316
+ 401,
5317
+ 403,
5318
+ 404
5319
+ ]);
5320
+ const fatalReasons = /* @__PURE__ */ new Set(["session.expired", "member.removed"]);
5321
+ const jittered = (milliseconds) => Math.round(milliseconds * (.8 + Math.random() * .2));
5322
+ const nextBackoff = (previous) => Math.min(previous === 0 ? firstBackoffMs : previous * 2, lastBackoffMs);
5323
+ const framesOf = (block) => {
5324
+ let event = "message";
5325
+ let id = null;
5326
+ const data = [];
5327
+ for (const line of block.split("\n")) {
5328
+ if (line === "" || line.startsWith(":")) continue;
5329
+ const separator = line.indexOf(":");
5330
+ const field = separator === -1 ? line : line.slice(0, separator);
5331
+ const value = separator === -1 ? "" : line.slice(separator + 1).trimStart();
5332
+ if (field === "event") event = value;
5333
+ else if (field === "id") id = value;
5334
+ else if (field === "data") data.push(value);
5335
+ }
5336
+ if (data.length === 0) return null;
5337
+ const text = data.join("\n");
5338
+ try {
5339
+ return {
5340
+ event,
5341
+ id,
5342
+ data: JSON.parse(text)
5343
+ };
5344
+ } catch {
5345
+ return {
5346
+ event,
5347
+ id,
5348
+ data: text
5349
+ };
5350
+ }
5351
+ };
5352
+ const readBody = async (response, request, onId) => {
5353
+ const body = response.body;
5354
+ if (body === null) return {
5355
+ closed: null,
5356
+ frames: 0
5357
+ };
5358
+ const reader = body.getReader();
5359
+ const decoder = new TextDecoder();
5360
+ const timer = { current: null };
5361
+ let buffer = "";
5362
+ let frames = 0;
5363
+ let closed = null;
5364
+ const armIdle = () => {
5365
+ if (request.follow) return;
5366
+ if (timer.current !== null) clearTimeout(timer.current);
5367
+ timer.current = setTimeout(() => {
5368
+ reader.cancel();
5369
+ }, request.idleMs ?? 1e3);
5370
+ };
5371
+ const stop = () => {
5372
+ reader.cancel().catch(() => void 0);
5373
+ };
5374
+ request.signal?.addEventListener("abort", stop, { once: true });
5375
+ if (request.signal?.aborted === true) stop();
5376
+ armIdle();
5377
+ try {
5378
+ for (;;) {
5379
+ const chunk = await reader.read().catch((error) => {
5380
+ if (request.signal?.aborted === true) return { done: true };
5381
+ throw error;
5382
+ });
5383
+ if (chunk.done) break;
5384
+ buffer += decoder.decode(chunk.value, { stream: true });
5385
+ let boundary = buffer.indexOf("\n\n");
5386
+ while (boundary !== -1) {
5387
+ const block = buffer.slice(0, boundary);
5388
+ buffer = buffer.slice(boundary + 2);
5389
+ const frame = framesOf(block);
5390
+ if (frame !== null) {
5391
+ frames += 1;
5392
+ if (frame.id !== null) onId(frame.id);
5393
+ if (frame.event === "stream.closed") closed = reasonOf(frame.data);
5394
+ else if (frame.event === "error") throw new CliError(`${request.action} failed on the instance: ${codeOf(frame.data) ?? "internal.error"}.`);
5395
+ else {
5396
+ request.onFrame(frame);
5397
+ armIdle();
5398
+ }
5399
+ }
5400
+ boundary = buffer.indexOf("\n\n");
5401
+ }
5402
+ if (closed !== null) break;
5403
+ }
5404
+ } finally {
5405
+ if (timer.current !== null) clearTimeout(timer.current);
5406
+ request.signal?.removeEventListener("abort", stop);
5407
+ }
5408
+ return {
5409
+ closed,
5410
+ frames
5411
+ };
5412
+ };
5413
+ const reasonOf = (data) => {
5414
+ if (typeof data === "object" && data !== null && "reason" in data) {
5415
+ const { reason } = data;
5416
+ if (typeof reason === "string") return reason;
5417
+ }
5418
+ return "unknown";
5419
+ };
5420
+ const streamFrames = async (request) => {
5421
+ const fetching = request.fetch ?? globalThis.fetch;
5422
+ const query = new URLSearchParams(request.query ?? {});
5423
+ const url = `${request.origin}/api/v1${request.path}${query.size === 0 ? "" : `?${query.toString()}`}`;
5424
+ const resume = { lastEventId: null };
5425
+ let backoff = 0;
5426
+ const aborted = () => request.signal?.aborted === true;
5427
+ for (;;) {
5428
+ if (aborted()) return;
5429
+ let response;
5430
+ try {
5431
+ response = await fetching(url, {
5432
+ headers: {
5433
+ accept: "text/event-stream",
5434
+ authorization: `Bearer ${request.token}`,
5435
+ ...request.resumable === true && resume.lastEventId !== null ? { "last-event-id": resume.lastEventId } : {}
5436
+ },
5437
+ redirect: "error",
5438
+ ...request.signal === void 0 ? {} : { signal: request.signal }
5439
+ });
5440
+ } catch (error) {
5441
+ if (aborted()) return;
5442
+ throw unreachable(request.origin, error);
5443
+ }
5444
+ if (!response.ok) {
5445
+ const body = await response.json().catch(() => void 0);
5446
+ const failure = new ApiError(response.status, codeOf(body), request.action, detailsOf$1(body));
5447
+ if (fatalStatuses.has(response.status) || !request.follow) throw failure;
5448
+ request.onNotice?.(`${failure.message} Waiting to try again.`);
5449
+ backoff = nextBackoff(backoff);
5450
+ await request.wait(jittered(backoff));
5451
+ continue;
5452
+ }
5453
+ backoff = 0;
5454
+ const { closed } = await readBody(response, request, (id) => {
5455
+ resume.lastEventId = id;
5456
+ });
5457
+ if (closed !== null && fatalReasons.has(closed)) throw new CliError(`The instance closed the stream: ${closed}.`, closed === "session.expired" ? "The token is no longer valid; run kcloud login again." : "You are no longer a member of that project.");
5458
+ if (!request.follow || aborted()) return;
5459
+ request.onNotice?.(closed === null ? "The stream ended; opening it again." : `The instance closed the stream: ${closed}. Opening it again.`);
5460
+ backoff = nextBackoff(backoff);
5461
+ await request.wait(jittered(backoff));
5462
+ }
5463
+ };
5464
+ //#endregion
5465
+ //#region src/commands/agents/dashboard.ts
5466
+ const noTask = "-";
5467
+ const workKinds = /* @__PURE__ */ new Set([
5468
+ "working",
5469
+ "patrolling",
5470
+ "summarising"
5471
+ ]);
5472
+ const organizationDashboardOf = async (api, organizationId) => dataOf("show the dashboard of the organization", await api.GET("/organizations/{organizationId}/dashboard", { params: { path: { organizationId } } }), organizationHints);
5473
+ const statusOf = (agent) => agent.status === "transitioning" ? `${agent.status} (${agent.state})` : agent.status;
5474
+ const taskOf = (agent) => {
5475
+ const { activity } = agent;
5476
+ if (activity === null) return noTask;
5477
+ if (!workKinds.has(activity.kind)) return agent.until === null ? activity.kind : `${activity.kind} until ${agent.until}`;
5478
+ if (activity.kind === "patrolling") return activity.cycle === null ? "cycle" : `cycle ${activity.cycle}`;
5479
+ if (activity.kind === "summarising") return "summary";
5480
+ return activity.title ?? activity.taskId ?? noTask;
5481
+ };
5482
+ const dashboardText = (dashboard) => {
5483
+ const rows = dashboard.projects.flatMap((project) => project.agents.map((agent) => [
5484
+ project.slug,
5485
+ agent.slug,
5486
+ statusOf(agent),
5487
+ agent.since,
5488
+ taskOf(agent),
5489
+ agent.costTodayUsd.toFixed(2)
5490
+ ]));
5491
+ return rows.length === 0 ? `No agent in the projects you can see on ${dashboard.day}.` : renderTable([
5492
+ { header: "PROJECT" },
5493
+ { header: "AGENT" },
5494
+ { header: "STATUS" },
5495
+ { header: "SINCE" },
5496
+ { header: "TASK" },
5497
+ {
5498
+ header: "COST TODAY",
5499
+ align: "right"
5500
+ }
5501
+ ], rows);
5502
+ };
5503
+ const namesOf = (dashboard) => ({
5504
+ projects: new Map(dashboard.projects.map(({ id, slug }) => [id, slug])),
5505
+ agents: new Map(dashboard.projects.flatMap((project) => project.agents.map(({ id, slug }) => [id, {
5506
+ projectId: project.id,
5507
+ slug
5508
+ }])))
5509
+ });
5510
+ const frameLineOf = (names, frame, at) => {
5511
+ const changed = dashboardAgentSchema.safeParse(frame.data);
5512
+ if (frame.event === "dashboard.agent" && changed.success) {
5513
+ const agent = changed.data;
5514
+ names.agents.set(agent.id, {
5515
+ projectId: agent.projectId,
5516
+ slug: agent.slug
5517
+ });
5518
+ const project = names.projects.get(agent.projectId) ?? agent.projectId;
5519
+ return `${at.toISOString()} ${project}/${agent.slug} ${statusOf(agent)} ${taskOf(agent)}`;
5520
+ }
5521
+ const removed = dashboardRemovalSchema.safeParse(frame.data);
5522
+ if (frame.event === "dashboard.removed" && removed.success) {
5523
+ const known = names.agents.get(removed.data.agentId);
5524
+ names.agents.delete(removed.data.agentId);
5525
+ const project = known === void 0 ? "-" : names.projects.get(known.projectId) ?? known.projectId;
5526
+ return `${at.toISOString()} ${project}/${known?.slug ?? removed.data.agentId} removed ${noTask}`;
5527
+ }
5528
+ return null;
5529
+ };
5530
+ const dashboardCommand = (adaptersOf) => defineCommand({
5531
+ name: "dashboard",
5532
+ summary: "Show what each agent of the organization is doing now, by project.",
5533
+ usage: "agents dashboard [--follow]",
5534
+ optionsUsage: [["-f, --follow", "keep printing a line for each change of an agent after the table"]],
5535
+ options: { follow: {
5536
+ type: "boolean",
5537
+ short: "f",
5538
+ default: false
5539
+ } },
5540
+ positionals: {
5541
+ min: 0,
5542
+ max: 0
5543
+ },
5544
+ run: async ({ values, settings, context }) => {
5545
+ const adapters = adaptersOf(context);
5546
+ const session = await openSession({
5547
+ settings,
5548
+ context,
5549
+ api: adapters.api
5550
+ });
5551
+ const { id } = await session.organization();
5552
+ const dashboard = await organizationDashboardOf(session.api, id);
5553
+ if (!values.follow) {
5554
+ if (settings.json) context.terminal.json(dashboard);
5555
+ else context.terminal.line(dashboardText(dashboard));
5556
+ return exitCodes.ok;
5557
+ }
5558
+ context.terminal.line(settings.json ? JSON.stringify(dashboard) : dashboardText(dashboard));
5559
+ const names = namesOf(dashboard);
5560
+ await streamFrames({
5561
+ origin: session.origin,
5562
+ token: session.token,
5563
+ path: `/organizations/${encodeURIComponent(id)}/dashboard/stream`,
5564
+ follow: true,
5565
+ resumable: false,
5566
+ idleMs: defaultIdleMs,
5567
+ action: "follow the dashboard of the organization",
5568
+ wait: adapters.wait,
5569
+ onNotice: (text) => {
5570
+ context.terminal.error(context.terminal.errorStyles.dim(text));
5571
+ },
5572
+ onFrame: (frame) => {
5573
+ if (settings.json) {
5574
+ context.terminal.line(JSON.stringify({
5575
+ event: frame.event,
5576
+ data: frame.data
5577
+ }));
5578
+ return;
5579
+ }
5580
+ const line = frameLineOf(names, frame, adapters.now());
5581
+ if (line !== null) context.terminal.line(line);
5582
+ }
5583
+ });
5584
+ return exitCodes.ok;
5585
+ }
5586
+ });
5587
+ //#endregion
5236
5588
  //#region src/commands/agents/lifecycle.ts
5237
5589
  const summaries = {
5238
5590
  start: "Start the agent and let it work.",
@@ -5645,161 +5997,6 @@ const observabilityCommands = (adaptersOf) => [
5645
5997
  })
5646
5998
  ];
5647
5999
  //#endregion
5648
- //#region src/client/stream.ts
5649
- const firstBackoffMs = 1e3;
5650
- const lastBackoffMs = 3e4;
5651
- const defaultIdleMs = 1e3;
5652
- const fatalStatuses = /* @__PURE__ */ new Set([
5653
- 400,
5654
- 401,
5655
- 403,
5656
- 404
5657
- ]);
5658
- const fatalReasons = /* @__PURE__ */ new Set(["session.expired", "member.removed"]);
5659
- const jittered = (milliseconds) => Math.round(milliseconds * (.8 + Math.random() * .2));
5660
- const nextBackoff = (previous) => Math.min(previous === 0 ? firstBackoffMs : previous * 2, lastBackoffMs);
5661
- const framesOf = (block) => {
5662
- let event = "message";
5663
- let id = null;
5664
- const data = [];
5665
- for (const line of block.split("\n")) {
5666
- if (line === "" || line.startsWith(":")) continue;
5667
- const separator = line.indexOf(":");
5668
- const field = separator === -1 ? line : line.slice(0, separator);
5669
- const value = separator === -1 ? "" : line.slice(separator + 1).trimStart();
5670
- if (field === "event") event = value;
5671
- else if (field === "id") id = value;
5672
- else if (field === "data") data.push(value);
5673
- }
5674
- if (data.length === 0) return null;
5675
- const text = data.join("\n");
5676
- try {
5677
- return {
5678
- event,
5679
- id,
5680
- data: JSON.parse(text)
5681
- };
5682
- } catch {
5683
- return {
5684
- event,
5685
- id,
5686
- data: text
5687
- };
5688
- }
5689
- };
5690
- const readBody = async (response, request, onId) => {
5691
- const body = response.body;
5692
- if (body === null) return {
5693
- closed: null,
5694
- frames: 0
5695
- };
5696
- const reader = body.getReader();
5697
- const decoder = new TextDecoder();
5698
- const timer = { current: null };
5699
- let buffer = "";
5700
- let frames = 0;
5701
- let closed = null;
5702
- const armIdle = () => {
5703
- if (request.follow) return;
5704
- if (timer.current !== null) clearTimeout(timer.current);
5705
- timer.current = setTimeout(() => {
5706
- reader.cancel();
5707
- }, request.idleMs ?? 1e3);
5708
- };
5709
- const stop = () => {
5710
- reader.cancel().catch(() => void 0);
5711
- };
5712
- request.signal?.addEventListener("abort", stop, { once: true });
5713
- if (request.signal?.aborted === true) stop();
5714
- armIdle();
5715
- try {
5716
- for (;;) {
5717
- const chunk = await reader.read().catch((error) => {
5718
- if (request.signal?.aborted === true) return { done: true };
5719
- throw error;
5720
- });
5721
- if (chunk.done) break;
5722
- buffer += decoder.decode(chunk.value, { stream: true });
5723
- let boundary = buffer.indexOf("\n\n");
5724
- while (boundary !== -1) {
5725
- const block = buffer.slice(0, boundary);
5726
- buffer = buffer.slice(boundary + 2);
5727
- const frame = framesOf(block);
5728
- if (frame !== null) {
5729
- frames += 1;
5730
- if (frame.id !== null) onId(frame.id);
5731
- if (frame.event === "stream.closed") closed = reasonOf(frame.data);
5732
- else if (frame.event === "error") throw new CliError(`${request.action} failed on the instance: ${codeOf(frame.data) ?? "internal.error"}.`);
5733
- else {
5734
- request.onFrame(frame);
5735
- armIdle();
5736
- }
5737
- }
5738
- boundary = buffer.indexOf("\n\n");
5739
- }
5740
- if (closed !== null) break;
5741
- }
5742
- } finally {
5743
- if (timer.current !== null) clearTimeout(timer.current);
5744
- request.signal?.removeEventListener("abort", stop);
5745
- }
5746
- return {
5747
- closed,
5748
- frames
5749
- };
5750
- };
5751
- const reasonOf = (data) => {
5752
- if (typeof data === "object" && data !== null && "reason" in data) {
5753
- const { reason } = data;
5754
- if (typeof reason === "string") return reason;
5755
- }
5756
- return "unknown";
5757
- };
5758
- const streamFrames = async (request) => {
5759
- const fetching = request.fetch ?? globalThis.fetch;
5760
- const query = new URLSearchParams(request.query ?? {});
5761
- const url = `${request.origin}/api/v1${request.path}${query.size === 0 ? "" : `?${query.toString()}`}`;
5762
- const resume = { lastEventId: null };
5763
- let backoff = 0;
5764
- const aborted = () => request.signal?.aborted === true;
5765
- for (;;) {
5766
- if (aborted()) return;
5767
- let response;
5768
- try {
5769
- response = await fetching(url, {
5770
- headers: {
5771
- accept: "text/event-stream",
5772
- authorization: `Bearer ${request.token}`,
5773
- ...request.resumable === true && resume.lastEventId !== null ? { "last-event-id": resume.lastEventId } : {}
5774
- },
5775
- redirect: "error",
5776
- ...request.signal === void 0 ? {} : { signal: request.signal }
5777
- });
5778
- } catch (error) {
5779
- if (aborted()) return;
5780
- throw unreachable(request.origin, error);
5781
- }
5782
- if (!response.ok) {
5783
- const body = await response.json().catch(() => void 0);
5784
- const failure = new ApiError(response.status, codeOf(body), request.action, detailsOf$1(body));
5785
- if (fatalStatuses.has(response.status) || !request.follow) throw failure;
5786
- request.onNotice?.(`${failure.message} Waiting to try again.`);
5787
- backoff = nextBackoff(backoff);
5788
- await request.wait(jittered(backoff));
5789
- continue;
5790
- }
5791
- backoff = 0;
5792
- const { closed } = await readBody(response, request, (id) => {
5793
- resume.lastEventId = id;
5794
- });
5795
- if (closed !== null && fatalReasons.has(closed)) throw new CliError(`The instance closed the stream: ${closed}.`, closed === "session.expired" ? "The token is no longer valid; run kcloud login again." : "You are no longer a member of that project.");
5796
- if (!request.follow || aborted()) return;
5797
- request.onNotice?.(closed === null ? "The stream ended; opening it again." : `The instance closed the stream: ${closed}. Opening it again.`);
5798
- backoff = nextBackoff(backoff);
5799
- await request.wait(jittered(backoff));
5800
- }
5801
- };
5802
- //#endregion
5803
6000
  //#region src/commands/agents/streams.ts
5804
6001
  const streamOptions = {
5805
6002
  ...agentOption,
@@ -6165,7 +6362,8 @@ const createAgentsCommand = (adaptersOf) => defineGroup({
6165
6362
  backupsCommand(adaptersOf),
6166
6363
  restoreCommand(adaptersOf),
6167
6364
  ...observabilityCommands(adaptersOf),
6168
- ...streamCommands(adaptersOf)
6365
+ ...streamCommands(adaptersOf),
6366
+ dashboardCommand(adaptersOf)
6169
6367
  ]
6170
6368
  });
6171
6369
  //#endregion
@@ -8567,6 +8765,15 @@ const organizationTools = [
8567
8765
  return organizationCostsOf(run.api, id, costWindowFrom(from, to, run.now()));
8568
8766
  }
8569
8767
  }),
8768
+ defineTool({
8769
+ name: "organization_dashboard",
8770
+ title: "Show what each agent of the organization is doing",
8771
+ description: "Show the agents of the organization this server acts in, by project, each with its status and since when, the task or the cycle it works on, and what it spent today, a UTC day. A member sees the projects they belong to.",
8772
+ operations: ["getOrganizationDashboard"],
8773
+ safety: "read",
8774
+ input: z.object({}),
8775
+ run: async (_, run) => organizationDashboardOf(run.api, (await run.organization()).id)
8776
+ }),
8570
8777
  defineTool({
8571
8778
  name: "organization_members_list",
8572
8779
  title: "List the members of the organization",
package/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  },
18
- "version": "1.7.0",
18
+ "version": "1.8.0",
19
19
  "dependencies": {
20
20
  "@clack/prompts": "1.8.1",
21
21
  "@modelcontextprotocol/server": "2.3.0",