@enter-pro/enter-cli 0.4.2 → 0.4.3

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/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { baseURL, workURL } from "./config.js";
2
- import { getToken } from "./auth.js";
2
+ import { getValidToken } from "./auth.js";
3
3
  import { safeOutput } from "./safe-output.js";
4
4
  import { APIError, RequestError } from "./errors.js";
5
5
  import { setTimeout as delay } from "node:timers/promises";
@@ -24,9 +24,6 @@ async function request(method, path, options, base) {
24
24
  const headers = {
25
25
  "Content-Type": "application/json",
26
26
  };
27
- const token = getToken();
28
- if (token)
29
- headers["Authorization"] = `Bearer ${token}`;
30
27
  const init = { method, headers, signal: options?.signal };
31
28
  if (options?.body !== undefined) {
32
29
  init.body = JSON.stringify(options.body);
@@ -48,11 +45,17 @@ async function request(method, path, options, base) {
48
45
  const timer = requestTimeoutMs === undefined ? undefined : setTimeout(() => controller.abort(), requestTimeoutMs);
49
46
  init.signal = controller.signal;
50
47
  try {
48
+ const token = await getValidToken(controller.signal);
49
+ if (token)
50
+ headers["Authorization"] = `Bearer ${token}`;
51
51
  for (let attempt = 0;; attempt++) {
52
52
  let resp;
53
53
  let text;
54
54
  try {
55
55
  resp = await fetch(url, init);
56
+ if (options?.raw && resp.ok && !resp.headers.get("content-type")?.includes("application/json")) {
57
+ return await resp.arrayBuffer();
58
+ }
56
59
  text = await resp.text();
57
60
  }
58
61
  catch (error) {
@@ -70,7 +73,7 @@ async function request(method, path, options, base) {
70
73
  console.error(`< ${resp.status} ${resp.statusText}`);
71
74
  }
72
75
  if (resp.status === 401) {
73
- throw new Error("Authentication required. Run `enter login` or set ENTER_API_KEY environment variable.");
76
+ throw new Error("Authentication required. Run `enter-cli login` or set ENTER_API_KEY environment variable.");
74
77
  }
75
78
  if (retryRead && [502, 503, 504].includes(resp.status) && attempt < 1) {
76
79
  await delay(200, undefined, { signal: controller.signal });
@@ -92,6 +95,8 @@ async function request(method, path, options, base) {
92
95
  if (resp.status >= 400) {
93
96
  throw new RequestError(`HTTP_${resp.status}`, `Enter returned HTTP ${resp.status}.`, readOnly && resp.status >= 500, !readOnly && resp.status >= 500);
94
97
  }
98
+ if (options?.raw)
99
+ throw new RequestError("INVALID_RESPONSE", "Expected a binary download, but Enter returned JSON.", true);
95
100
  return apiResp;
96
101
  }
97
102
  if (resp.status >= 500)
@@ -99,6 +104,8 @@ async function request(method, path, options, base) {
99
104
  if (apiResp.code !== CODE_SUCCESS) {
100
105
  throw new APIError(apiResp.code, apiResp.message ?? "", apiResp.detail ?? "");
101
106
  }
107
+ if (options?.raw)
108
+ throw new RequestError("INVALID_RESPONSE", "Expected a binary download, but Enter returned JSON.", true);
102
109
  return apiResp.data;
103
110
  }
104
111
  }
@@ -122,9 +129,6 @@ export async function post(path, body, signal) {
122
129
  export async function del(path) {
123
130
  return request("DELETE", path);
124
131
  }
125
- export async function put(path, body) {
126
- return request("PUT", path, { body });
127
- }
128
132
  export async function patch(path, body) {
129
133
  return request("PATCH", path, { body });
130
134
  }
@@ -135,60 +139,6 @@ export async function workGet(path, params) {
135
139
  export async function workPost(path, body) {
136
140
  return request("POST", path, { body }, workURL());
137
141
  }
138
- export async function workPatch(path, body) {
139
- return request("PATCH", path, { body }, workURL());
140
- }
141
- export async function workDel(path) {
142
- return request("DELETE", path, {}, workURL());
143
- }
144
142
  export async function getRaw(path) {
145
- const url = `${baseURL()}${path}`;
146
- const headers = {};
147
- const token = getToken();
148
- if (token)
149
- headers["Authorization"] = `Bearer ${token}`;
150
- if (verbose)
151
- console.error(`> GET ${url}`);
152
- const resp = await fetch(url, { headers });
153
- if (verbose)
154
- console.error(`< ${resp.status} ${resp.statusText}`);
155
- if (resp.status === 401) {
156
- throw new Error("Authentication required. Run `enter login` or set ENTER_API_KEY environment variable.");
157
- }
158
- // Successful binary response — caller consumes the body.
159
- if (resp.ok) {
160
- // But: a JSON error envelope can come back with status 200 too. Peek the
161
- // content-type; only treat as raw if it's not JSON.
162
- const ct = resp.headers.get("content-type") ?? "";
163
- if (!ct.includes("application/json"))
164
- return resp;
165
- const text = await resp.text();
166
- try {
167
- const apiResp = JSON.parse(text);
168
- if (apiResp.code !== 0) {
169
- throw new APIError(apiResp.code, apiResp.message, apiResp.detail);
170
- }
171
- // code === 0 with JSON body on a binary endpoint shouldn't happen; surface as raw text
172
- throw new Error(`Unexpected JSON response on binary endpoint: ${text.slice(0, 200)}`);
173
- }
174
- catch (err) {
175
- if (err instanceof APIError)
176
- throw err;
177
- throw new Error(`Failed to parse JSON response: ${text.slice(0, 200)}`);
178
- }
179
- }
180
- // Non-2xx — try to parse the API error envelope so callers can distinguish
181
- // VIP_REQUIRED, NOT_FOUND, etc. Fall back to raw text if it isn't JSON.
182
- const text = await resp.text();
183
- try {
184
- const apiResp = JSON.parse(text);
185
- if (apiResp.code !== undefined) {
186
- throw new APIError(apiResp.code, apiResp.message ?? "", apiResp.detail ?? "");
187
- }
188
- }
189
- catch (err) {
190
- if (err instanceof APIError)
191
- throw err;
192
- }
193
- throw new Error(`HTTP ${resp.status}: ${text || resp.statusText}`);
143
+ return request("GET", path, { raw: true });
194
144
  }
@@ -1,25 +1,20 @@
1
1
  import { Command } from "commander";
2
2
  import { setConfig, getConfig, allSettings } from "../config.js";
3
- import { print, printMessage, printTable } from "../output.js";
3
+ import { print, printResult, printTable, getFormat } from "../output.js";
4
4
  export const configCmd = new Command("config").description("Manage CLI configuration");
5
5
  configCmd
6
6
  .command("set <key> <value>")
7
7
  .description("Set a configuration value")
8
- .action(async (key, value) => {
8
+ .action(async (key, value, _opts, cmd) => {
9
9
  setConfig(key, value);
10
- printMessage(`Set ${key} = ${value}`);
10
+ printResult(getFormat(cmd), { key, value }, `Set ${key} = ${value}`);
11
11
  });
12
12
  configCmd
13
13
  .command("get <key>")
14
14
  .description("Get a configuration value")
15
- .action(async (key) => {
15
+ .action(async (key, _opts, cmd) => {
16
16
  const val = getConfig(key);
17
- if (!val) {
18
- printMessage(`${key}: (not set)`);
19
- }
20
- else {
21
- printMessage(`${key}: ${val}`);
22
- }
17
+ printResult(getFormat(cmd), { key, value: val || null }, val ? `${key}: ${val}` : `${key}: (not set)`);
23
18
  });
24
19
  configCmd
25
20
  .command("list")
@@ -1,10 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import * as client from "../client.js";
3
- import { print, printMessage, printTable } from "../output.js";
3
+ import { print, printResult, printTable, getFormat } from "../output.js";
4
4
  export const domainCmd = new Command("domain").description("Manage project domains");
5
- function getFormat(cmd) {
6
- return cmd.optsWithGlobals().output || "json";
7
- }
8
5
  domainCmd
9
6
  .command("list <project_id>")
10
7
  .description("List project domains")
@@ -37,9 +34,9 @@ domainCmd
37
34
  .command("remove <project_id>")
38
35
  .description("Remove a custom domain from project")
39
36
  .requiredOption("--domain <name>", "Domain name to remove")
40
- .action(async (id, opts) => {
37
+ .action(async (id, opts, cmd) => {
41
38
  await client.del(`/v1/projects/${id}/domain?domain=${opts.domain}`);
42
- printMessage(`Domain "${opts.domain}" removed.`);
39
+ printResult(getFormat(cmd), { removed: true, domain: opts.domain }, `Domain "${opts.domain}" removed.`);
43
40
  });
44
41
  domainCmd
45
42
  .command("refresh <project_id>")
@@ -2,12 +2,12 @@ import { Command } from "commander";
2
2
  import { createServer } from "http";
3
3
  import { randomBytes, createHash } from "crypto";
4
4
  import { execSync } from "child_process";
5
- import { saveCredentials, verifyAccessToken } from "../auth.js";
6
- import { printMessage } from "../output.js";
5
+ import { saveCredentials, verifyAccessToken, OAUTH_TOKEN_URL, OAUTH_CLIENT_ID } from "../auth.js";
6
+ import { printResult, getFormat } from "../output.js";
7
7
  const AUTH0_DOMAIN = "auth.converge.ai";
8
- const CLIENT_ID = "anCisSaaIA36fTZ2DUMiTMro3bYuptrf";
8
+ const CLIENT_ID = OAUTH_CLIENT_ID;
9
9
  const AUTHORIZE_URL = `https://${AUTH0_DOMAIN}/authorize`;
10
- const TOKEN_URL = `https://${AUTH0_DOMAIN}/oauth/token`;
10
+ const TOKEN_URL = OAUTH_TOKEN_URL;
11
11
  const AUDIENCE = "https://api.enter.pro";
12
12
  const SCOPE = "openid profile email offline_access";
13
13
  const REDIRECT_URI = "http://localhost:19820";
@@ -94,7 +94,6 @@ function startOAuthFlow() {
94
94
  });
95
95
  res.writeHead(200, { "Content-Type": "text/html" });
96
96
  res.end(`<html><body><h2>Login Successful!</h2><p>You can close this window and return to the terminal.</p></body></html>`);
97
- printMessage("Login successful. Credentials saved.");
98
97
  clearTimeout(timeoutId);
99
98
  server.close();
100
99
  resolve();
@@ -118,8 +117,8 @@ function startOAuthFlow() {
118
117
  code_challenge_method: "S256",
119
118
  });
120
119
  const authUrl = `${AUTHORIZE_URL}?${params}`;
121
- printMessage("Opening browser for authentication...");
122
- printMessage(`If browser doesn't open, visit:\n${authUrl}\n`);
120
+ console.error("Opening browser for authentication...");
121
+ console.error(`If browser doesn't open, visit:\n${authUrl}\n`);
123
122
  openBrowser(authUrl);
124
123
  });
125
124
  timeoutId = setTimeout(() => {
@@ -131,15 +130,20 @@ function startOAuthFlow() {
131
130
  export const loginCmd = new Command("login")
132
131
  .description("Authenticate with Enter platform")
133
132
  .option("--api-key <key>", "Authenticate with a workspace API key instead of OAuth")
134
- .action(async (opts) => {
133
+ .action(async (opts, cmd) => {
135
134
  if (opts.apiKey) {
136
135
  await verifyAccessToken(opts.apiKey);
137
136
  saveCredentials({
138
137
  access_token: opts.apiKey,
139
138
  token_type: "Bearer",
140
139
  });
141
- printMessage("API key saved. You are now authenticated.");
142
- return;
143
140
  }
144
- await startOAuthFlow();
141
+ else {
142
+ await startOAuthFlow();
143
+ }
144
+ const injected = Boolean(process.env.ENTER_API_KEY);
145
+ const message = injected
146
+ ? "Local credentials verified and saved. ENTER_API_KEY still takes precedence; this has not switched the active account. Change authentication in the host or unset ENTER_API_KEY to use the saved credentials."
147
+ : "Login successful. Credentials saved.";
148
+ printResult(getFormat(cmd), { credentials_saved: true, auth_source: injected ? "environment" : "local", ...(!injected ? { authenticated: true } : {}), message }, message);
145
149
  });
@@ -1,9 +1,13 @@
1
1
  import { Command } from "commander";
2
2
  import { clearCredentials } from "../auth.js";
3
- import { printMessage } from "../output.js";
3
+ import { printResult, getFormat } from "../output.js";
4
4
  export const logoutCmd = new Command("logout")
5
5
  .description("Clear stored credentials")
6
- .action(async () => {
6
+ .action(async (_opts, cmd) => {
7
7
  clearCredentials();
8
- printMessage("Logged out successfully.");
8
+ const injected = Boolean(process.env.ENTER_API_KEY);
9
+ const message = injected
10
+ ? "Local credentials cleared. ENTER_API_KEY still takes precedence; change or disconnect authentication in the host, or unset it in your shell, to switch accounts."
11
+ : "Stored credentials cleared. Logged out.";
12
+ printResult(getFormat(cmd), { logged_out: !injected, local_credentials_cleared: true, auth_source: injected ? "environment" : "none", message }, message);
9
13
  });
@@ -1,3 +1,4 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
1
2
  import { Command } from "commander";
2
3
  import { writeFileSync } from "fs";
3
4
  import * as client from "../client.js";
@@ -5,6 +6,7 @@ import { print, printMessage, printResult, printTable, pickList, getFormat } fro
5
6
  import { pollUntil, TimeoutError } from "../poll.js";
6
7
  import { reportThread, threadInteraction } from "./thread.js";
7
8
  import { safeOutput } from "../safe-output.js";
9
+ import { RequestError } from "../errors.js";
8
10
  import { resolveLifecycleStatus } from "../lifecycle.js";
9
11
  export const projectCmd = new Command("project")
10
12
  .alias("proj")
@@ -63,7 +65,8 @@ projectCmd
63
65
  .action(async (id, _opts, cmd) => {
64
66
  const data = await client.get(`/v1/projects/${id}/detail`);
65
67
  const p = data;
66
- const lifecycle = resolveLifecycleStatus(p);
68
+ const project = unwrapProject(p);
69
+ const lifecycle = resolveLifecycleStatus(project);
67
70
  const enriched = { ...p, lifecycle_status: lifecycle };
68
71
  const format = getFormat(cmd);
69
72
  if (format !== "table") {
@@ -71,14 +74,14 @@ projectCmd
71
74
  return;
72
75
  }
73
76
  printTable(["Project ID", "Name", "Visibility", "Status", "Lifecycle", "Preview URL", "Workspace", "Updated"], [[
74
- String(p.project_id ?? ""),
75
- String(p.name ?? ""),
76
- String(p.visibility ?? ""),
77
- String(p.status ?? ""),
77
+ String(project.project_id ?? ""),
78
+ String(project.name ?? ""),
79
+ String(project.visibility ?? ""),
80
+ String(project.status ?? ""),
78
81
  lifecycle,
79
- String(p.preview_url ?? ""),
80
- String(p.workspace_id ?? ""),
81
- String(p.updated_at ?? ""),
82
+ String(project.preview_url ?? ""),
83
+ String(project.workspace_id ?? ""),
84
+ String(project.updated_at ?? ""),
82
85
  ]]);
83
86
  });
84
87
  projectCmd
@@ -132,83 +135,108 @@ projectCmd
132
135
  .command("download <project_id>")
133
136
  .description("Download project as zip file")
134
137
  .option("--out <path>", "Output file path")
135
- .action(async (id, opts) => {
138
+ .action(async (id, opts, cmd) => {
136
139
  const data = await client.getRaw(`/v1/projects/${id}/download`);
137
140
  const outPath = opts.out || `${id}.zip`;
138
- writeFileSync(outPath, Buffer.from(await data.arrayBuffer()));
139
- printMessage(`Project downloaded to ${outPath}`);
141
+ writeFileSync(outPath, Buffer.from(data));
142
+ printResult(getFormat(cmd), { path: outPath }, `Project downloaded to ${outPath}`);
140
143
  });
141
144
  projectCmd
142
145
  .command("publish <project_id>")
143
146
  .description("Publish a project (synchronous: waits for completion and verifies URL)")
144
147
  .option("--timeout <seconds>", "Timeout in seconds", "300")
145
148
  .action(async (id, opts, cmd) => {
146
- const initialDetail = await client.get(`/v1/projects/${id}/detail`);
147
- const initialProject = unwrapProject(initialDetail);
148
- const buildStatus = initialProject.build_status;
149
- const commitId = String(buildStatus?.commit_id ?? initialProject.commit ?? "");
150
- if (!commitId) {
151
- console.error("Error: project has no committed build yet (build_status.commit_id is empty).");
152
- console.error("Wait for the latest turn to finish, then retry.");
153
- process.exit(1);
154
- }
155
- await client.post(`/v1/projects/${id}/publish`);
156
- console.error("Waiting for publish to complete...");
157
- const timeoutMs = parseInt(opts.timeout, 10) * 1000;
149
+ const seconds = Number(opts.timeout);
150
+ if (!Number.isFinite(seconds) || seconds <= 0 || seconds * 1000 > 2147483647)
151
+ throw new Error("--timeout must be a positive bounded number");
152
+ const controller = new AbortController();
153
+ const timer = setTimeout(() => controller.abort(), seconds * 1000);
154
+ let submitted = false;
155
+ let published = false;
158
156
  try {
159
- const result = await pollUntil(() => client.get(`/v1/projects/${id}/detail`), (d) => {
157
+ const initialDetail = await client.get(`/v1/projects/${id}/detail`, undefined, controller.signal);
158
+ const initialProject = unwrapProject(initialDetail);
159
+ const buildStatus = initialProject.build_status;
160
+ const commitId = String(buildStatus?.commit_id ?? initialProject.commit ?? "");
161
+ if (!commitId) {
162
+ throw new Error("Project has no committed build yet. Wait for the latest turn to finish, then retry.");
163
+ }
164
+ const format = getFormat(cmd);
165
+ const human = format === "table";
166
+ controller.signal.throwIfAborted();
167
+ submitted = true;
168
+ await client.post(`/v1/projects/${id}/publish`, undefined, controller.signal);
169
+ if (human)
170
+ console.error("Waiting for publish to complete...");
171
+ const timeoutMs = seconds * 1000;
172
+ const result = await pollUntil(() => client.get(`/v1/projects/${id}/detail`, undefined, controller.signal), (d) => {
160
173
  const view = readPublishStatus(unwrapProject(d));
161
174
  // Done when the latest committed build has been published and nothing is queued.
162
175
  return view.lastPublishedCommit === commitId && view.unpublishedChanges === 0;
163
176
  }, {
177
+ signal: controller.signal,
164
178
  intervalMs: 3000,
165
179
  timeoutMs,
166
180
  onTick: (elapsed) => {
167
- process.stderr.write(`\rWaiting... ${Math.round(elapsed / 1000)}s elapsed`);
181
+ if (human)
182
+ process.stderr.write(`\rWaiting... ${Math.round(elapsed / 1000)}s elapsed`);
168
183
  },
169
184
  });
170
- process.stderr.write("\n");
185
+ if (human)
186
+ process.stderr.write("\n");
187
+ published = true;
171
188
  const project = unwrapProject(result);
172
189
  const view = readPublishStatus(project);
173
190
  const publishUrl = String(project.publish_url ?? "");
174
- const reachable = publishUrl ? await verifyUrlReachable(publishUrl) : false;
175
- if (publishUrl && !reachable) {
191
+ const reachable = publishUrl ? await verifyUrlReachable(publishUrl, 3, 2000, controller.signal) : false;
192
+ if (human && publishUrl && !reachable) {
176
193
  console.error(`Warning: ${publishUrl} did not return 200 within retries. The publish completed server-side but the URL may still be propagating.`);
177
194
  }
178
- printMessage(`Published commit ${commitId.slice(0, 7)} at ${view.lastPublishedAt}. URL: ${publishUrl}${reachable ? " (200 OK)" : ""}`);
179
- print(getFormat(cmd), { ...result, lifecycle_status: resolveLifecycleStatus(project) });
195
+ if (human)
196
+ printMessage(`Published commit ${commitId.slice(0, 7)} at ${view.lastPublishedAt}. URL: ${publishUrl}${reachable ? " (200 OK)" : ""}`);
197
+ print(format, { ...result, lifecycle_status: resolveLifecycleStatus(project), publish_reachable: reachable });
180
198
  }
181
199
  catch (err) {
182
- if (err instanceof TimeoutError) {
183
- console.error(`\nTimed out after ${opts.timeout}s. Publish may still be in progress.`);
184
- console.error(`Run "proj urls ${id}" to check current status.`);
185
- process.exit(1);
200
+ if (err instanceof TimeoutError || controller.signal.aborted) {
201
+ throw new RequestError("PUBLISH_TIMEOUT", `Publish observation exceeded its deadline. Check enter-cli project publish-status ${id} before retrying.`, false, submitted && !published);
186
202
  }
187
203
  throw err;
188
204
  }
205
+ finally {
206
+ clearTimeout(timer);
207
+ }
189
208
  });
190
209
  // HEAD-first URL probe with a small wall-clock budget. Falls back to GET when
191
210
  // HEAD isn't allowed (some hosts return 405). Used by `proj publish` (after
192
211
  // publish completes) and `proj urls` (to determine the recommended URL).
193
- async function verifyUrlReachable(url, attempts = 3, perAttemptMs = 2000) {
212
+ async function verifyUrlReachable(url, attempts = 3, perAttemptMs = 2000, signal) {
194
213
  for (let i = 0; i < attempts; i++) {
214
+ signal?.throwIfAborted();
195
215
  const ctrl = new AbortController();
216
+ const abort = () => ctrl.abort();
217
+ signal?.addEventListener("abort", abort, { once: true });
196
218
  const t = setTimeout(() => ctrl.abort(), perAttemptMs);
197
219
  try {
198
220
  let res = await fetch(url, { method: "HEAD", signal: ctrl.signal });
199
221
  // Some hosts reject HEAD (405 / 501) — retry once with GET on this attempt.
200
222
  if (res.status === 405 || res.status === 501) {
223
+ await res.body?.cancel();
201
224
  res = await fetch(url, { method: "GET", signal: ctrl.signal });
202
225
  }
203
- clearTimeout(t);
226
+ await res.body?.cancel();
204
227
  if (res.status === 200)
205
228
  return true;
206
229
  }
207
230
  catch {
231
+ signal?.throwIfAborted();
232
+ }
233
+ finally {
234
+ ctrl.abort();
208
235
  clearTimeout(t);
236
+ signal?.removeEventListener("abort", abort);
209
237
  }
210
238
  if (i < attempts - 1)
211
- await new Promise((r) => setTimeout(r, 1000));
239
+ await delay(1000, undefined, { signal });
212
240
  }
213
241
  return false;
214
242
  }
@@ -276,8 +304,13 @@ projectCmd
276
304
  .option("--workspace-id <id>", "Target workspace ID for remix")
277
305
  .action(async (id, opts, cmd) => {
278
306
  const body = {};
279
- if (opts.workspaceId)
280
- body.workspace_id = opts.workspaceId;
307
+ if (opts.workspaceId !== undefined) {
308
+ const workspaceId = Number(opts.workspaceId);
309
+ if (!/^\d+$/.test(opts.workspaceId) || !Number.isSafeInteger(workspaceId) || workspaceId <= 0) {
310
+ throw new Error("--workspace-id must be a positive safe integer");
311
+ }
312
+ body.workspace_id = workspaceId;
313
+ }
281
314
  const data = await client.post(`/v1/projects/${id}/remix`, body);
282
315
  print(getFormat(cmd), data);
283
316
  });
@@ -393,9 +426,9 @@ mcpCmd
393
426
  mcpCmd
394
427
  .command("delete <project_id> <server_id>")
395
428
  .description("Delete an MCP server")
396
- .action(async (id, serverId) => {
429
+ .action(async (id, serverId, _opts, cmd) => {
397
430
  await client.del(`/v1/projects/${id}/mcp/servers/${serverId}`);
398
- printMessage(`MCP server ${serverId} deleted.`);
431
+ printResult(getFormat(cmd), { deleted: true, server_id: serverId }, `MCP server ${serverId} deleted.`);
399
432
  });
400
433
  projectCmd.addCommand(mcpCmd);
401
434
  // Project skills subcommand group
@@ -7,6 +7,33 @@ type InteractionOptions = {
7
7
  requireBuild?: boolean;
8
8
  };
9
9
  export declare function threadInteraction(id: string, { taskId, chatId, turn, requireBuild }?: InteractionOptions): {
10
+ workflow: {
11
+ actions?: {
12
+ id: unknown;
13
+ kind: unknown;
14
+ decision: string;
15
+ required_fields: {};
16
+ submit_command: unknown;
17
+ }[] | undefined;
18
+ build?: {
19
+ commit: {} | null;
20
+ matches_task: boolean;
21
+ success: {} | null;
22
+ } | undefined;
23
+ observation: {
24
+ timed_out: boolean;
25
+ retrying: boolean;
26
+ interrupted: boolean;
27
+ };
28
+ reason?: string | undefined;
29
+ state: string;
30
+ task: {
31
+ id: {} | null;
32
+ turn: {} | null;
33
+ chat_id: {} | null;
34
+ };
35
+ next_action: string;
36
+ };
10
37
  monitoring_required: boolean;
11
38
  wait_command: string;
12
39
  watch_command: string;