@ocis/myagent-cli 0.2.4 → 0.2.6

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/README.md CHANGED
@@ -229,7 +229,7 @@ terminal.
229
229
 
230
230
  ### Commands
231
231
 
232
- `/help` `/new` `/session [all]` (`/sessions`, `/resume` — aliases) `/plan` `/coding` `/cowork` `/mode` `/edit` `/thinking` `/model` `/compact`
232
+ `/help` `/new` `/session [all]` (`/sessions`, `/resume` — aliases) `/plan` `/coding` `/cowork` `/mode` `/edit` `/thinking` `/model` `/balance` `/compact`
233
233
  `/stop` `/reload` `/export` `/config` `/quit`
234
234
 
235
235
  ## Sessions
@@ -291,8 +291,10 @@ Integrations page (`https://host/integrations/<id>`) — the integration id is d
291
291
  Create an integration + API key on the MyAgent **Integrations** page. Recommended settings
292
292
  for the CLI:
293
293
 
294
- - **Tools**: leave empty (all coding tools are supplied by the CLI) or enable `web_fetch` for
295
- server-side fetching.
294
+ - **Tools**: leave empty (all coding tools are supplied by the CLI) or enable the `web` group for
295
+ server-side fetching. Server tool groups are extras — the CLI's own tools are client-side.
296
+ - **Skills**: enable the ones you need. The CLI's local tools satisfy their script needs
297
+ (`bash`/`read`/`write`), so no server tool group is required for them.
296
298
  - **Model**: any tool-capable model with a reasonable context window.
297
299
 
298
300
  The CLI and the server deploy in lockstep — every documented integration-API feature is
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { stdin as input, stdout as output } from "node:process";
10
10
  import { homedir } from "node:os";
11
11
  import { resolve } from "node:path";
12
12
  import pkg from "../package.json" with { type: "json" };
13
- import { IntegrationClient } from "./protocol/client.js";
13
+ import { IntegrationClient, IntegrationApiError } from "./protocol/client.js";
14
14
  import { ToolRegistry } from "./tools/registry.js";
15
15
  import { combinedIntegrationUrl, configDir, isConfigured, loadConfig, migrateLegacyConfigDir, resolveConfig, saveConfig, sessionsPath, splitIntegrationUrl, } from "./config.js";
16
16
  import { SessionIndex } from "./agent/sessions.js";
@@ -335,15 +335,24 @@ async function main() {
335
335
  integrationId: config.integrationId,
336
336
  apiKey: config.apiKey,
337
337
  });
338
- let status;
338
+ let status = null;
339
339
  try {
340
340
  // Bounded connect: a hung server must fail (exit 2) instead of hanging CI.
341
341
  status = await client.getStatus({ signal: AbortSignal.timeout(30_000) });
342
342
  }
343
343
  catch (err) {
344
- return fail(args.flags.outputFormat, `Could not reach ${config.baseUrl}: ${err instanceof Error ? err.message : String(err)}`, "connection_failed");
344
+ // A paused platform sandbox answers the status check with 503 — the check
345
+ // deliberately does not wake it (see the platform's proxy-activator). That
346
+ // is not a connection failure: the first run wakes the sandbox, so proceed
347
+ // with a notice instead of failing.
348
+ if (err instanceof IntegrationApiError && err.status === 503) {
349
+ process.stderr.write("Sandbox is paused — it will start on your first message.\n");
350
+ }
351
+ else {
352
+ return fail(args.flags.outputFormat, `Could not reach ${config.baseUrl}: ${err instanceof Error ? err.message : String(err)}`, "connection_failed");
353
+ }
345
354
  }
346
- if (!status.enabled) {
355
+ if (status && !status.enabled) {
347
356
  return fail(args.flags.outputFormat, `Integration "${status.label}" is disabled on the server.`, "integration_disabled");
348
357
  }
349
358
  const sessionIndex = await SessionIndex.load(sessionsPath());
@@ -1,4 +1,4 @@
1
- import type { AguiEvent, AguiInputMessage, AguiRunRequest, AguiToolDef, IntegrationStatus, ModelInfo, RunStreamResult, SessionDetail, SessionSummary, WorkspaceEntry, WorkspaceFile, WorkspaceWriteResult } from "./types.js";
1
+ import type { AguiEvent, AguiInputMessage, AguiRunRequest, AguiToolDef, BalanceInfo, IntegrationStatus, ModelInfo, RunStreamResult, SessionDetail, SessionSummary, WorkspaceEntry, WorkspaceFile, WorkspaceWriteResult } from "./types.js";
2
2
  export interface IntegrationClientOptions {
3
3
  baseUrl: string;
4
4
  integrationId: string;
@@ -73,6 +73,13 @@ export declare class IntegrationClient {
73
73
  getStatus(opts?: {
74
74
  signal?: AbortSignal;
75
75
  }): Promise<IntegrationStatus>;
76
+ /**
77
+ * The user's platform credit balance. Platform mode only — a standalone
78
+ * server answers 404 with code `platform_only`. Used by `/balance`.
79
+ */
80
+ getBalance(opts?: {
81
+ signal?: AbortSignal;
82
+ }): Promise<BalanceInfo>;
76
83
  /**
77
84
  * Models this integration accepts (default first, `default: true` marks it).
78
85
  * A forced-model integration returns exactly one entry — there is nothing to
@@ -126,6 +126,13 @@ export class IntegrationClient {
126
126
  async getStatus(opts = {}) {
127
127
  return this.json(this.url(""), { method: "GET", signal: opts.signal });
128
128
  }
129
+ /**
130
+ * The user's platform credit balance. Platform mode only — a standalone
131
+ * server answers 404 with code `platform_only`. Used by `/balance`.
132
+ */
133
+ async getBalance(opts = {}) {
134
+ return this.json(this.url("/balance"), { method: "GET", signal: opts.signal });
135
+ }
129
136
  /**
130
137
  * Models this integration accepts (default first, `default: true` marks it).
131
138
  * A forced-model integration returns exactly one entry — there is nothing to
@@ -231,14 +238,26 @@ export class IntegrationClient {
231
238
  let message = `HTTP ${res.status}`;
232
239
  let code;
233
240
  let details;
241
+ // Read the body once as text: myagent's error bodies are JSON, but the
242
+ // platform proxy answers with plain text (e.g. its 503 for a paused
243
+ // sandbox) — keep that text instead of collapsing it to "HTTP 503".
244
+ let text = "";
234
245
  try {
235
- const body = await res.json();
236
- if (body.error?.message)
237
- message = body.error.message;
238
- code = body.error?.code;
239
- details = body.error?.details;
246
+ text = await res.text();
247
+ }
248
+ catch { /* unreadable body */ }
249
+ if (text) {
250
+ try {
251
+ const body = JSON.parse(text);
252
+ if (body.error?.message)
253
+ message = body.error.message;
254
+ code = body.error?.code;
255
+ details = body.error?.details;
256
+ }
257
+ catch {
258
+ message = text.trim() || message;
259
+ }
240
260
  }
241
- catch { /* non-JSON error body */ }
242
261
  if (res.status === 404) {
243
262
  message += " (check the integration id and that the integration is enabled)";
244
263
  }
@@ -39,6 +39,10 @@ export interface IntegrationStatus {
39
39
  label: string;
40
40
  enabled: boolean;
41
41
  }
42
+ /** Response of GET /balance (platform mode only). */
43
+ export interface BalanceInfo {
44
+ balance: number;
45
+ }
42
46
  /** One directory entry from GET /integrations/workspace. */
43
47
  export interface WorkspaceEntry {
44
48
  name: string;
package/dist/ui/app.js CHANGED
@@ -8,6 +8,7 @@
8
8
  import { writeFile } from "node:fs/promises";
9
9
  import { basename, join, resolve } from "node:path";
10
10
  import { CombinedAutocompleteProvider, Box, Container, HStack, isViewportTUI, matchesKey, MouseRegion, ProcessTerminal, ScrollView, SelectList, Spacer, TuiAltScreen, VStack, visibleWidth, } from "@earendil-works/pi-tui";
11
+ import { IntegrationApiError } from "../protocol/client.js";
11
12
  import { AgentRunner, usageFromSession } from "../agent/turn.js";
12
13
  import { MODES, nextMode, stripModeMarker } from "../agent/modes.js";
13
14
  import { createSystemPromptBuilder } from "../agent/prompt-builder.js";
@@ -484,6 +485,7 @@ export async function runTui(opts) {
484
485
  { name: "edit", description: "toggle auto/ask approval" },
485
486
  { name: "thinking", description: "set the thinking level" },
486
487
  { name: "model", description: "choose the model (new sessions)" },
488
+ { name: "balance", description: "show the credit balance" },
487
489
  { name: "compact", description: "compact the session history" },
488
490
  { name: "stop", description: "force-stop the running turn" },
489
491
  { name: "reload", description: "reload context files and skills" },
@@ -628,6 +630,9 @@ export async function runTui(opts) {
628
630
  case "model":
629
631
  await showModelPicker();
630
632
  break;
633
+ case "balance":
634
+ await showBalance();
635
+ break;
631
636
  case "compact":
632
637
  if (!runner.threadId)
633
638
  addTranscript(new NoticeComponent("No active session.", "warn"));
@@ -668,6 +673,23 @@ export async function runTui(opts) {
668
673
  addTranscript(new NoticeComponent(`Unknown command: /${name} (try /help)`, "warn"));
669
674
  }
670
675
  }
676
+ /** `/balance` — the platform credit balance (platform mode only). */
677
+ async function showBalance() {
678
+ try {
679
+ const { balance } = await opts.client.getBalance();
680
+ const amount = balance < 0 ? `-$${Math.abs(balance).toFixed(2)}` : `$${balance.toFixed(2)}`;
681
+ addTranscript(new NoticeComponent(`Credit balance: ${amount}`, "info"));
682
+ }
683
+ catch (err) {
684
+ // The server's 404 for a standalone deployment carries code
685
+ // `platform_only`; show it plainly instead of the generic 404 hint.
686
+ if (err instanceof IntegrationApiError && err.code === "platform_only") {
687
+ addTranscript(new NoticeComponent("Credit balance is only available on platform deployments.", "warn"));
688
+ return;
689
+ }
690
+ addTranscript(new NoticeComponent(`Failed to fetch balance: ${err instanceof Error ? err.message : String(err)}`, "error"));
691
+ }
692
+ }
671
693
  function setMode(mode) {
672
694
  runner.setMode(mode);
673
695
  editor.mode = mode;
package/dist/ui/help.js CHANGED
@@ -24,7 +24,8 @@ const COMMANDS = [
24
24
  ["/cowork", "general tasks", "/edit", "AUTO/ASK"],
25
25
  ["/new", "new session", "/session [all]", "list · switch"],
26
26
  ["/thinking /model", "level · model", "/compact /stop", "compact · stop"],
27
- ["/reload /export", "reload · export", "/config /quit", "info · exit"],
27
+ ["/balance", "credit balance", "/reload /export", "reload · export"],
28
+ ["/config /quit", "info · exit", "", ""],
28
29
  ];
29
30
  const KEYS = [
30
31
  ["Tab", "plan/coding/cowork", "Shift+Tab", "AUTO/ASK"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ocis/myagent-cli",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Terminal coding agent for your local repository, powered by a remote MyAgent server",
5
5
  "type": "module",
6
6
  "bin": {