@spencer-kit/coder-studio 0.4.9 → 0.5.1

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.
@@ -6,14 +6,14 @@
6
6
  <meta name="description" content="Coder Studio - Agent-First Development Environment" />
7
7
  <title>Coder Studio</title>
8
8
  <link rel="icon" type="image/x-icon" href="/favicon.ico" />
9
- <script type="module" crossorigin src="/assets/main-BjHz157g.js"></script>
9
+ <script type="module" crossorigin src="/assets/main-Na2CPXZJ.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CpWojdLp.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/monaco-editor-VTbRDH-J.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/xterm-BVlcrOZ1.js">
13
- <link rel="modulepreload" crossorigin href="/assets/components-SjBXPKn9.js">
13
+ <link rel="modulepreload" crossorigin href="/assets/components-CGHXUDfF.js">
14
14
  <link rel="stylesheet" crossorigin href="/assets/monaco-editor-Br_kD0ds.css">
15
15
  <link rel="stylesheet" crossorigin href="/assets/xterm-BrP-ENHg.css">
16
- <link rel="stylesheet" crossorigin href="/assets/components-6O5yG1fL.css">
16
+ <link rel="stylesheet" crossorigin href="/assets/components-CBwEj8F3.css">
17
17
  </head>
18
18
  <body>
19
19
  <div id="root"></div>
@@ -4,14 +4,14 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Coder Studio UI Preview</title>
7
- <script type="module" crossorigin src="/assets/ui-preview-tdq8QPlu.js"></script>
7
+ <script type="module" crossorigin src="/assets/ui-preview-DHlB71h3.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CpWojdLp.js">
9
9
  <link rel="modulepreload" crossorigin href="/assets/monaco-editor-VTbRDH-J.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/xterm-BVlcrOZ1.js">
11
- <link rel="modulepreload" crossorigin href="/assets/components-SjBXPKn9.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/components-CGHXUDfF.js">
12
12
  <link rel="stylesheet" crossorigin href="/assets/monaco-editor-Br_kD0ds.css">
13
13
  <link rel="stylesheet" crossorigin href="/assets/xterm-BrP-ENHg.css">
14
- <link rel="stylesheet" crossorigin href="/assets/components-6O5yG1fL.css">
14
+ <link rel="stylesheet" crossorigin href="/assets/components-CBwEj8F3.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spencer-kit/coder-studio",
3
- "version": "0.4.9",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "description": "Deploy once, code everywhere. Browser-based AI coding workspace for Claude Code and Codex.",
6
6
  "main": "./dist/esm/index.mjs",
@@ -0,0 +1,36 @@
1
+ import {
2
+ buildIdentifyResult,
3
+ DEFAULT_AGENT_AUTOMATION_PERMISSIONS,
4
+ listAutomationCapabilities,
5
+ } from "@coder-studio/core";
6
+
7
+ interface PrintOptions {
8
+ json?: boolean;
9
+ }
10
+
11
+ export function printIdentify(options: PrintOptions = {}): void {
12
+ const result = buildIdentifyResult();
13
+
14
+ if (options.json) {
15
+ console.log(JSON.stringify(result, null, 2));
16
+ return;
17
+ }
18
+
19
+ console.log(result.insideCoderStudio ? "Inside Coder Studio" : "Not running inside Coder Studio");
20
+ }
21
+
22
+ export function printCapabilities(options: PrintOptions = {}): void {
23
+ const result = {
24
+ version: 1,
25
+ commands: listAutomationCapabilities({
26
+ permissions: DEFAULT_AGENT_AUTOMATION_PERMISSIONS,
27
+ }),
28
+ };
29
+
30
+ if (options.json) {
31
+ console.log(JSON.stringify(result, null, 2));
32
+ return;
33
+ }
34
+
35
+ console.log(result.commands.map((command) => `${command.name}: ${command.cli}`).join("\n"));
36
+ }
@@ -0,0 +1,142 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { Result } from "@coder-studio/core";
3
+ import WebSocket from "ws";
4
+ import { getServerStatus } from "./server-control.js";
5
+ import { getBrowserUrl } from "./server-url.js";
6
+
7
+ const DEFAULT_COMMAND_TIMEOUT_MS = 30_000;
8
+
9
+ export interface CoderStudioCommandInput {
10
+ apiUrl?: string;
11
+ op: string;
12
+ args: unknown;
13
+ timeoutMs?: number;
14
+ }
15
+
16
+ function normalizeApiUrl(apiUrl: string): string {
17
+ return apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl;
18
+ }
19
+
20
+ function toWebSocketUrl(apiUrl: string): string {
21
+ const url = new URL(normalizeApiUrl(apiUrl));
22
+ if (url.protocol === "http:") {
23
+ url.protocol = "ws:";
24
+ } else if (url.protocol === "https:") {
25
+ url.protocol = "wss:";
26
+ } else if (url.protocol !== "ws:" && url.protocol !== "wss:") {
27
+ throw new Error(`Unsupported Coder Studio API URL protocol: ${url.protocol}`);
28
+ }
29
+
30
+ url.pathname = `${url.pathname.replace(/\/$/u, "")}/ws`;
31
+ url.search = "";
32
+ url.hash = "";
33
+ return url.toString();
34
+ }
35
+
36
+ async function resolveApiUrl(explicitApiUrl: string | undefined): Promise<string> {
37
+ if (explicitApiUrl) {
38
+ return explicitApiUrl;
39
+ }
40
+
41
+ if (process.env.CODER_STUDIO_API_URL) {
42
+ return process.env.CODER_STUDIO_API_URL;
43
+ }
44
+
45
+ const status = await getServerStatus();
46
+ const browserUrl = getBrowserUrl(status);
47
+ if (browserUrl) {
48
+ return browserUrl;
49
+ }
50
+
51
+ throw new Error(
52
+ "Unable to find a running Coder Studio server. Start it first or pass --api-url."
53
+ );
54
+ }
55
+
56
+ function parseResultMessage(data: WebSocket.RawData): Result | null {
57
+ if (Array.isArray(data)) {
58
+ return null;
59
+ }
60
+
61
+ const text = Buffer.isBuffer(data)
62
+ ? data.toString("utf8")
63
+ : Buffer.from(data as ArrayBuffer).toString("utf8");
64
+ const message = JSON.parse(text) as { kind?: string };
65
+
66
+ if (message.kind !== "result") {
67
+ return null;
68
+ }
69
+
70
+ return message as Result;
71
+ }
72
+
73
+ export async function callCoderStudioCommand<T = unknown>(
74
+ input: CoderStudioCommandInput
75
+ ): Promise<T> {
76
+ const apiUrl = await resolveApiUrl(input.apiUrl);
77
+ const wsUrl = toWebSocketUrl(apiUrl);
78
+ const id = randomUUID();
79
+ const timeoutMs = input.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
80
+
81
+ return new Promise<T>((resolve, reject) => {
82
+ const socket = new WebSocket(wsUrl);
83
+ let settled = false;
84
+ const timer = setTimeout(() => {
85
+ finish(() => reject(new Error(`Timed out waiting for ${input.op} result`)));
86
+ }, timeoutMs);
87
+
88
+ function finish(callback: () => void): void {
89
+ if (settled) {
90
+ return;
91
+ }
92
+
93
+ settled = true;
94
+ clearTimeout(timer);
95
+ socket.close();
96
+ callback();
97
+ }
98
+
99
+ socket.on("open", () => {
100
+ socket.send(
101
+ JSON.stringify({
102
+ kind: "command",
103
+ id,
104
+ op: input.op,
105
+ args: input.args,
106
+ })
107
+ );
108
+ });
109
+
110
+ socket.on("message", (data) => {
111
+ let result: Result | null;
112
+ try {
113
+ result = parseResultMessage(data);
114
+ } catch (error) {
115
+ finish(() => reject(error));
116
+ return;
117
+ }
118
+
119
+ if (!result || result.id !== id) {
120
+ return;
121
+ }
122
+
123
+ if (result.ok) {
124
+ finish(() => resolve(result.data as T));
125
+ return;
126
+ }
127
+
128
+ const code = result.error?.code ? `${result.error.code}: ` : "";
129
+ finish(() => reject(new Error(`${code}${result.error?.message ?? "Command failed"}`)));
130
+ });
131
+
132
+ socket.on("error", (error) => {
133
+ finish(() => reject(error));
134
+ });
135
+
136
+ socket.on("close", () => {
137
+ if (!settled) {
138
+ finish(() => reject(new Error("Coder Studio command connection closed before a result")));
139
+ }
140
+ });
141
+ });
142
+ }
package/src/bin.test.ts CHANGED
@@ -4,6 +4,7 @@ import { join } from "path";
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
5
 
6
6
  const {
7
+ callCoderStudioCommand,
7
8
  clearAuthBlockByIp,
8
9
  confirmYesNo,
9
10
  getServerStatus,
@@ -17,6 +18,7 @@ const {
17
18
  stopRunningServer,
18
19
  writeCliConfig,
19
20
  } = vi.hoisted(() => ({
21
+ callCoderStudioCommand: vi.fn(),
20
22
  clearAuthBlockByIp: vi.fn(),
21
23
  confirmYesNo: vi.fn(),
22
24
  getServerStatus: vi.fn(),
@@ -64,6 +66,10 @@ vi.mock("./browser.js", () => ({
64
66
  openBrowser,
65
67
  }));
66
68
 
69
+ vi.mock("./automation-command-client.js", () => ({
70
+ callCoderStudioCommand,
71
+ }));
72
+
67
73
  import { main } from "./cli";
68
74
  import { parseArgs, RUNTIME_CONFIG_ERROR } from "./parse-args";
69
75
 
@@ -79,6 +85,7 @@ beforeEach(() => {
79
85
  confirmYesNo.mockResolvedValue(false);
80
86
  isInteractiveSession.mockReturnValue(true);
81
87
  openBrowser.mockResolvedValue(undefined);
88
+ callCoderStudioCommand.mockResolvedValue({ ok: true });
82
89
  getServerStatus.mockResolvedValue({
83
90
  status: "stopped",
84
91
  pid: null,
@@ -559,6 +566,111 @@ describe("main", () => {
559
566
  expect(clearAuthBlockByIp).toHaveBeenCalledWith("198.51.100.24");
560
567
  expect(logSpy).toHaveBeenCalledWith("Unblocked IP: 198.51.100.24");
561
568
  });
569
+
570
+ it("prints identify output", async () => {
571
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
572
+
573
+ await main(["identify", "--json"]);
574
+
575
+ expect(JSON.parse(logSpy.mock.calls[0]?.[0] as string)).toEqual({
576
+ insideCoderStudio: false,
577
+ });
578
+ });
579
+
580
+ it("prints capabilities output", async () => {
581
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
582
+
583
+ await main(["capabilities", "--json"]);
584
+
585
+ expect(JSON.parse(logSpy.mock.calls[0]?.[0] as string)).toEqual(
586
+ expect.objectContaining({
587
+ version: 1,
588
+ commands: expect.arrayContaining([expect.objectContaining({ name: "git.status" })]),
589
+ })
590
+ );
591
+ });
592
+
593
+ it("prints workspace list output through the Coder Studio command API", async () => {
594
+ callCoderStudioCommand.mockResolvedValueOnce([{ id: "ws-1", path: "/repo" }]);
595
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
596
+
597
+ await main(["workspace", "list", "--json"]);
598
+
599
+ expect(callCoderStudioCommand).toHaveBeenCalledWith({
600
+ apiUrl: undefined,
601
+ op: "workspace.list",
602
+ args: {},
603
+ });
604
+ expect(JSON.parse(logSpy.mock.calls[0]?.[0] as string)).toEqual([
605
+ { id: "ws-1", path: "/repo" },
606
+ ]);
607
+ });
608
+
609
+ it("prints session list output through the Coder Studio command API", async () => {
610
+ callCoderStudioCommand.mockResolvedValueOnce([{ id: "sess-1", workspaceId: "ws-1" }]);
611
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
612
+
613
+ await main(["session", "list", "--workspace", "ws-1", "--json"]);
614
+
615
+ expect(callCoderStudioCommand).toHaveBeenCalledWith({
616
+ apiUrl: undefined,
617
+ op: "session.list",
618
+ args: { workspaceId: "ws-1" },
619
+ });
620
+ expect(JSON.parse(logSpy.mock.calls[0]?.[0] as string)).toEqual([
621
+ { id: "sess-1", workspaceId: "ws-1" },
622
+ ]);
623
+ });
624
+
625
+ it("prints terminal read output through the Coder Studio command API", async () => {
626
+ callCoderStudioCommand.mockResolvedValueOnce({ terminalId: "term-1", text: "ready\n" });
627
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
628
+
629
+ await main(["terminal", "read", "--terminal", "term-1", "--bytes", "4096", "--json"]);
630
+
631
+ expect(callCoderStudioCommand).toHaveBeenCalledWith({
632
+ apiUrl: undefined,
633
+ op: "terminal.read",
634
+ args: { terminalId: "term-1", bytes: 4096 },
635
+ });
636
+ expect(JSON.parse(logSpy.mock.calls[0]?.[0] as string)).toEqual({
637
+ terminalId: "term-1",
638
+ text: "ready\n",
639
+ });
640
+ });
641
+
642
+ it("prints git status output through the Coder Studio command API", async () => {
643
+ callCoderStudioCommand.mockResolvedValueOnce({ branch: "main", entries: [] });
644
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
645
+
646
+ await main(["git", "status", "--workspace", "ws-1", "--json"]);
647
+
648
+ expect(callCoderStudioCommand).toHaveBeenCalledWith({
649
+ apiUrl: undefined,
650
+ op: "git.status",
651
+ args: { workspaceId: "ws-1" },
652
+ });
653
+ expect(JSON.parse(logSpy.mock.calls[0]?.[0] as string)).toEqual({
654
+ branch: "main",
655
+ entries: [],
656
+ });
657
+ });
658
+
659
+ it("prints git diff output through the Coder Studio command API", async () => {
660
+ callCoderStudioCommand.mockResolvedValueOnce({ diff: "diff --git a/a b/a\n" });
661
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
662
+
663
+ await main(["git", "diff", "--workspace", "ws-1", "--path", "src/a.ts", "--json"]);
664
+
665
+ expect(callCoderStudioCommand).toHaveBeenCalledWith({
666
+ apiUrl: undefined,
667
+ op: "git.diff",
668
+ args: { workspaceId: "ws-1", path: "src/a.ts" },
669
+ });
670
+ expect(JSON.parse(logSpy.mock.calls[0]?.[0] as string)).toEqual({
671
+ diff: "diff --git a/a b/a\n",
672
+ });
673
+ });
562
674
  });
563
675
 
564
676
  describe("parseArgs", () => {
@@ -651,6 +763,68 @@ describe("parseArgs", () => {
651
763
  });
652
764
  });
653
765
 
766
+ it("parses identify command with json output", () => {
767
+ expect(parseArgs(["identify", "--json"])).toEqual({
768
+ command: "identify",
769
+ json: true,
770
+ });
771
+ });
772
+
773
+ it("parses capabilities command with json output", () => {
774
+ expect(parseArgs(["capabilities", "--json"])).toEqual({
775
+ command: "capabilities",
776
+ json: true,
777
+ });
778
+ });
779
+
780
+ it("parses workspace list command with json output", () => {
781
+ expect(parseArgs(["workspace", "list", "--json"])).toEqual({
782
+ command: "workspace",
783
+ workspaceCommand: "list",
784
+ json: true,
785
+ });
786
+ });
787
+
788
+ it("parses session list command with workspace and json output", () => {
789
+ expect(parseArgs(["session", "list", "--workspace", "ws-1", "--json"])).toEqual({
790
+ command: "session",
791
+ sessionCommand: "list",
792
+ workspaceId: "ws-1",
793
+ json: true,
794
+ });
795
+ });
796
+
797
+ it("parses terminal read command with terminal id and byte limit", () => {
798
+ expect(parseArgs(["terminal", "read", "--terminal", "term-1", "--bytes", "4096"])).toEqual({
799
+ command: "terminal",
800
+ terminalCommand: "read",
801
+ terminalId: "term-1",
802
+ bytes: 4096,
803
+ });
804
+ });
805
+
806
+ it("parses git status command with workspace and json output", () => {
807
+ expect(parseArgs(["git", "status", "--workspace", "ws-1", "--json"])).toEqual({
808
+ command: "git",
809
+ gitCommand: "status",
810
+ workspaceId: "ws-1",
811
+ json: true,
812
+ });
813
+ });
814
+
815
+ it("parses git diff command with workspace, path, staged, and json output", () => {
816
+ expect(
817
+ parseArgs(["git", "diff", "--workspace", "ws-1", "--path", "src/a.ts", "--staged", "--json"])
818
+ ).toEqual({
819
+ command: "git",
820
+ gitCommand: "diff",
821
+ workspaceId: "ws-1",
822
+ path: "src/a.ts",
823
+ staged: true,
824
+ json: true,
825
+ });
826
+ });
827
+
654
828
  it("parses server alias as serve", () => {
655
829
  expect(parseArgs(["server"])).toEqual({
656
830
  command: "serve",
@@ -837,6 +1011,26 @@ describe("parseArgs", () => {
837
1011
  expect(() => parseArgs(["status", "--bogus"])).toThrow("Unknown option: --bogus");
838
1012
  });
839
1013
 
1014
+ it("rejects json output on unsupported commands", () => {
1015
+ expect(() => parseArgs(["status", "--json"])).toThrow("Unknown option: --json");
1016
+ });
1017
+
1018
+ it("requires workspace id for session list", () => {
1019
+ expect(() => parseArgs(["session", "list"])).toThrow("Missing workspace value");
1020
+ });
1021
+
1022
+ it("requires terminal id for terminal read", () => {
1023
+ expect(() => parseArgs(["terminal", "read"])).toThrow("Missing terminal value");
1024
+ });
1025
+
1026
+ it("requires workspace id for git status", () => {
1027
+ expect(() => parseArgs(["git", "status"])).toThrow("Missing workspace value");
1028
+ });
1029
+
1030
+ it("requires path for git diff", () => {
1031
+ expect(() => parseArgs(["git", "diff", "--workspace", "ws-1"])).toThrow("Missing path value");
1032
+ });
1033
+
840
1034
  it("allows config-time host-only updates", () => {
841
1035
  expect(parseArgs(["config", "--host", "127.0.0.1"])).toEqual({
842
1036
  command: "config",
package/src/cli.ts CHANGED
@@ -2,6 +2,8 @@ import { existsSync } from "fs";
2
2
  import { dirname, join } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { clearAuthBlockByIp, listAuthBlocks } from "./auth-control.js";
5
+ import { printCapabilities, printIdentify } from "./automation-client.js";
6
+ import { callCoderStudioCommand } from "./automation-command-client.js";
5
7
  import { openBrowser } from "./browser.js";
6
8
  import { type CliConfig, readCliConfig, writeCliConfig } from "./config-store.js";
7
9
  import { readLogExcerpt } from "./log-excerpt.js";
@@ -76,6 +78,12 @@ COMMANDS:
76
78
  status Show the managed server status
77
79
  logs Show the managed server logs
78
80
  help Show this help message
81
+ identify Print Coder Studio agent runtime context
82
+ capabilities Print agent-facing automation capabilities
83
+ workspace Read workspace automation data
84
+ session Read session automation data
85
+ terminal Read terminal automation data
86
+ git Read git automation data
79
87
  version Show version
80
88
 
81
89
  OPTIONS:
@@ -99,6 +107,13 @@ EXAMPLES:
99
107
  coder-studio open --restart
100
108
  coder-studio status
101
109
  coder-studio logs
110
+ coder-studio identify --json
111
+ coder-studio capabilities --json
112
+ coder-studio workspace list --json
113
+ coder-studio session list --workspace ws_123 --json
114
+ coder-studio terminal read --terminal term_123 --bytes 4096 --json
115
+ coder-studio git status --workspace ws_123 --json
116
+ coder-studio git diff --workspace ws_123 --path src/a.ts --json
102
117
  coder-studio stop
103
118
  coder-studio config --host 0.0.0.0 --port 8080
104
119
  `);
@@ -137,6 +152,35 @@ function showVersion(): void {
137
152
  console.log(`@spencer-kit/coder-studio v${getCliVersion(import.meta.url)}`);
138
153
  }
139
154
 
155
+ function printCommandResult(result: unknown, options: { json?: boolean } = {}): void {
156
+ if (options.json) {
157
+ console.log(JSON.stringify(result, null, 2));
158
+ return;
159
+ }
160
+
161
+ if (
162
+ typeof result === "object" &&
163
+ result !== null &&
164
+ "text" in result &&
165
+ typeof (result as { text?: unknown }).text === "string"
166
+ ) {
167
+ console.log((result as { text: string }).text);
168
+ return;
169
+ }
170
+
171
+ if (
172
+ typeof result === "object" &&
173
+ result !== null &&
174
+ "diff" in result &&
175
+ typeof (result as { diff?: unknown }).diff === "string"
176
+ ) {
177
+ console.log((result as { diff: string }).diff);
178
+ return;
179
+ }
180
+
181
+ console.log(JSON.stringify(result, null, 2));
182
+ }
183
+
140
184
  function formatAuthBlocks(blocks: Awaited<ReturnType<typeof listAuthBlocks>>): string {
141
185
  if (blocks.length === 0) {
142
186
  return "No blocked IPs.";
@@ -297,6 +341,83 @@ export async function main(argv = process.argv.slice(2)): Promise<void> {
297
341
  return;
298
342
  }
299
343
 
344
+ if (args.command === "identify") {
345
+ printIdentify({ json: args.json });
346
+ return;
347
+ }
348
+
349
+ if (args.command === "capabilities") {
350
+ printCapabilities({ json: args.json });
351
+ return;
352
+ }
353
+
354
+ if (args.command === "workspace" && args.workspaceCommand === "list") {
355
+ printCommandResult(
356
+ await callCoderStudioCommand({
357
+ apiUrl: args.apiUrl,
358
+ op: "workspace.list",
359
+ args: {},
360
+ }),
361
+ { json: args.json }
362
+ );
363
+ return;
364
+ }
365
+
366
+ if (args.command === "session" && args.sessionCommand === "list") {
367
+ printCommandResult(
368
+ await callCoderStudioCommand({
369
+ apiUrl: args.apiUrl,
370
+ op: "session.list",
371
+ args: { workspaceId: args.workspaceId! },
372
+ }),
373
+ { json: args.json }
374
+ );
375
+ return;
376
+ }
377
+
378
+ if (args.command === "terminal" && args.terminalCommand === "read") {
379
+ printCommandResult(
380
+ await callCoderStudioCommand({
381
+ apiUrl: args.apiUrl,
382
+ op: "terminal.read",
383
+ args: {
384
+ terminalId: args.terminalId!,
385
+ ...(args.bytes !== undefined ? { bytes: args.bytes } : {}),
386
+ },
387
+ }),
388
+ { json: args.json }
389
+ );
390
+ return;
391
+ }
392
+
393
+ if (args.command === "git" && args.gitCommand === "status") {
394
+ printCommandResult(
395
+ await callCoderStudioCommand({
396
+ apiUrl: args.apiUrl,
397
+ op: "git.status",
398
+ args: { workspaceId: args.workspaceId! },
399
+ }),
400
+ { json: args.json }
401
+ );
402
+ return;
403
+ }
404
+
405
+ if (args.command === "git" && args.gitCommand === "diff") {
406
+ printCommandResult(
407
+ await callCoderStudioCommand({
408
+ apiUrl: args.apiUrl,
409
+ op: "git.diff",
410
+ args: {
411
+ workspaceId: args.workspaceId!,
412
+ ...(args.path !== undefined ? { path: args.path } : {}),
413
+ ...(args.staged === true ? { staged: true } : {}),
414
+ },
415
+ }),
416
+ { json: args.json }
417
+ );
418
+ return;
419
+ }
420
+
300
421
  if (args.command === "auth") {
301
422
  if (args.authCommand === "ban-list") {
302
423
  console.log(formatAuthBlocks(await listAuthBlocks()));