@actionway/cli 0.18.2 → 0.18.4

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.
@@ -36,6 +36,7 @@ Use the installed `actionway` CLI and its server-backed Capability Registry as t
36
36
  - `accepted`: immediately wait on the returned `job.jobRef`.
37
37
  - `completed`: consume the returned result.
38
38
  - `confirmation: strong`: never treat chat text alone as approval; follow a server-verifiable approval instruction or report that execution cannot continue.
39
+ 6. On `E_INSUFFICIENT_BALANCE`, stop the call. When the failure includes `top_up_url`, the user-facing response must render that exact field value as a Markdown link by substituting it into `[Top up Actionway balance](<top_up_url>)`; never leave it only in terminal JSON, replace it with a terminal command, or synthesize a URL for another environment. Ask the user to retry the original task after the top-up is complete.
39
40
 
40
41
  ## Handle asynchronous jobs
41
42
 
package/dist/index.js CHANGED
@@ -12434,7 +12434,7 @@ var REFRESH_SKEW_S = 30;
12434
12434
  var TOKEN_TIMEOUT_MS = 2e4;
12435
12435
  var DISCOVERY_TIMEOUT_MS = 1e4;
12436
12436
  var SESSION_TIMEOUT_MS = 2e4;
12437
- var DEFAULT_LOGIN_TIMEOUT_MS = 12e4;
12437
+ var DEFAULT_LOGIN_TIMEOUT_MS = 12e5;
12438
12438
  function stripSlash(url) {
12439
12439
  return url.replace(/\/+$/, "");
12440
12440
  }
@@ -12521,7 +12521,15 @@ async function fetchCliOAuthConfig(gateway, fetchImpl = fetch) {
12521
12521
  signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS)
12522
12522
  });
12523
12523
  if (!response.ok) {
12524
- throw new Error(`Actionway login configuration is unavailable (${response.status})`);
12524
+ let detail = "";
12525
+ try {
12526
+ const body2 = await response.json();
12527
+ const code = typeof body2?.error?.code === "string" ? body2.error.code : "";
12528
+ const message = typeof body2?.error?.message === "string" ? body2.error.message : "";
12529
+ detail = [code, message].filter(Boolean).join(": ");
12530
+ } catch {
12531
+ }
12532
+ throw new Error(`Actionway login configuration is unavailable (${response.status}${detail ? `; ${detail}` : ""})`);
12525
12533
  }
12526
12534
  const body = await response.json();
12527
12535
  if (typeof body !== "object" || body === null || !("issuer" in body) || typeof body.issuer !== "string" || !("client_id" in body) || typeof body.client_id !== "string" || !body.issuer.trim() || !body.client_id.trim()) {
@@ -12694,14 +12702,21 @@ async function waitForCode(state, timeoutMs) {
12694
12702
  const port = server.address().port;
12695
12703
  const timer = setTimeout(() => {
12696
12704
  rejectCode(
12697
- new CliError("E_BACKEND", `login timed out after ${Math.round(timeoutMs / 1e3)} seconds`, "run `actionway login` again")
12705
+ new CliError(
12706
+ "E_BACKEND",
12707
+ `login timed out after ${Math.round(timeoutMs / 1e3)} seconds`,
12708
+ "run `actionway login` again and ask the user to finish the browser sign-in promptly; pass --timeout-seconds <n> to allow more time"
12709
+ )
12698
12710
  );
12699
12711
  server.close();
12700
12712
  }, timeoutMs);
12701
12713
  return {
12702
12714
  redirectUri: `http://127.0.0.1:${port}/callback`,
12703
12715
  code: code.finally(() => clearTimeout(timer)),
12704
- close: () => new Promise((resolve3) => server.listening ? server.close(() => resolve3()) : resolve3())
12716
+ close: () => {
12717
+ clearTimeout(timer);
12718
+ return new Promise((resolve3) => server.listening ? server.close(() => resolve3()) : resolve3());
12719
+ }
12705
12720
  };
12706
12721
  }
12707
12722
  async function readJsonObject(response) {
@@ -12720,8 +12735,14 @@ async function tokenRequest(issuer, body, fetchImpl) {
12720
12735
  });
12721
12736
  const value = await readJsonObject(response);
12722
12737
  if (!response.ok || typeof value.access_token !== "string") {
12723
- const message = typeof value.error_description === "string" ? value.error_description : "token request failed";
12724
- throw new CliError("E_BACKEND", message);
12738
+ const code = typeof value.error === "string" ? value.error : "";
12739
+ const description = typeof value.error_description === "string" ? value.error_description : "";
12740
+ const message = [code, description].filter(Boolean).join(": ") || "token request failed";
12741
+ throw new CliError(
12742
+ "E_BACKEND",
12743
+ message,
12744
+ code === "invalid_client" ? "the client_id/issuer pair does not belong to one Actionway environment: clear ACTIONWAY_OAUTH_CLIENT_ID and ACTIONWAY_OAUTH_ISSUER (or drop --client-id/--issuer) and run `actionway login` again to rediscover both; if no overrides are set, the stored credentials are stale \u2014 run `actionway logout` then `actionway login`" : void 0
12745
+ );
12725
12746
  }
12726
12747
  return {
12727
12748
  accessToken: value.access_token,
@@ -12796,19 +12817,44 @@ async function login(options = {}) {
12796
12817
  const onNotice = options.onNotice ?? ((message) => process.stderr.write(`${message}
12797
12818
  `));
12798
12819
  const gateway = resolveGatewayUrl(options.gateway, env);
12820
+ const gatewaySource = options.gateway?.trim() ? "flag" : (env.ACTIONWAY_GATEWAY_URL ?? "").trim() ? "env" : "default";
12799
12821
  let clientId = options.clientId?.trim() || (env.ACTIONWAY_OAUTH_CLIENT_ID ?? "").trim();
12800
12822
  let issuer = options.issuer?.trim() || (env.ACTIONWAY_OAUTH_ISSUER ?? "").trim();
12823
+ const clientIdSource = options.clientId?.trim() ? "flag" : (env.ACTIONWAY_OAUTH_CLIENT_ID ?? "").trim() ? "env" : "discovery";
12824
+ const issuerSource = options.issuer?.trim() ? "flag" : (env.ACTIONWAY_OAUTH_ISSUER ?? "").trim() ? "env" : "discovery";
12825
+ const authContext = () => ({
12826
+ gateway_url: gateway,
12827
+ gateway_source: gatewaySource,
12828
+ ...issuer ? { issuer } : {},
12829
+ issuer_source: issuerSource,
12830
+ ...clientId ? { client_id: clientId } : {},
12831
+ client_id_source: clientIdSource
12832
+ });
12833
+ const withAuthContext = (error) => {
12834
+ if (error instanceof CliError) {
12835
+ throw new CliError(error.code, error.message, error.hint, { ...error.extra ?? {}, auth_context: authContext() });
12836
+ }
12837
+ throw error;
12838
+ };
12839
+ if (!!clientId !== !!issuer) {
12840
+ throw new CliError(
12841
+ "E_SCHEMA",
12842
+ "client_id and issuer overrides must be provided together",
12843
+ "pass both --client-id and --issuer (or set both ACTIONWAY_OAUTH_CLIENT_ID and ACTIONWAY_OAUTH_ISSUER), or neither to use discovery",
12844
+ { auth_context: authContext() }
12845
+ );
12846
+ }
12801
12847
  if (!clientId || !issuer) {
12802
12848
  let discovered;
12803
12849
  try {
12804
12850
  discovered = await fetchCliOAuthConfig(gateway, fetchImpl);
12805
12851
  } catch (error) {
12806
12852
  const message = error instanceof Error ? error.message : String(error);
12807
- throw new CliError(
12853
+ return withAuthContext(new CliError(
12808
12854
  "E_BACKEND",
12809
12855
  `login failed: ${message}`,
12810
- "retry later or pass --client-id and --issuer explicitly"
12811
- );
12856
+ "check auth_context.gateway_url is the intended Actionway environment (unset ACTIONWAY_GATEWAY_URL or fix --gateway if not) and run `actionway login` again; if the gateway is correct, the environment is misconfigured \u2014 report this error to the user instead of retrying (passing both --client-id and --issuer explicitly remains available as a temporary override)"
12857
+ ));
12812
12858
  }
12813
12859
  clientId ||= discovered.clientId;
12814
12860
  issuer ||= discovered.issuer;
@@ -12865,6 +12911,8 @@ ${authorizeUrl}`);
12865
12911
  };
12866
12912
  saveCredentials(credentials, env);
12867
12913
  return { credentials, account };
12914
+ } catch (error) {
12915
+ return withAuthContext(error);
12868
12916
  } finally {
12869
12917
  await callback.close();
12870
12918
  }
@@ -12917,7 +12965,7 @@ function failFromError2(err, fallbackHint) {
12917
12965
  });
12918
12966
  }
12919
12967
  function registerAuthCommands(program2) {
12920
- program2.command("login").description("Authenticate with Actionway in the browser (Clerk OAuth with PKCE).").option("--gateway <url>", "Actionway public origin").option("--client-id <id>", "Actionway OAuth client id (default: discovered from the public origin)").option("--issuer <url>", "Actionway OAuth issuer (default: discovered from the public origin)").option("--timeout-seconds <n>", "how long to wait for browser authentication").option("--no-browser", "print the authorize URL instead of opening a browser").action(async (opts) => {
12968
+ program2.command("login").description("Authenticate with Actionway in the browser (Clerk OAuth with PKCE).").option("--gateway <url>", "Actionway public origin").option("--client-id <id>", "Actionway OAuth client id (must be paired with --issuer; default: both discovered from the public origin)").option("--issuer <url>", "Actionway OAuth issuer (must be paired with --client-id; default: both discovered from the public origin)").option("--timeout-seconds <n>", "how long to wait for browser authentication").option("--no-browser", "print the authorize URL instead of opening a browser").action(async (opts) => {
12921
12969
  try {
12922
12970
  const { credentials, account } = await login({
12923
12971
  ...opts.gateway ? { gateway: opts.gateway } : {},
@@ -13335,7 +13383,10 @@ async function captureInstallStarted(input) {
13335
13383
  {
13336
13384
  method: "POST",
13337
13385
  headers: { Accept: "application/json", "Content-Type": "application/json" },
13338
- body: JSON.stringify({ installSessionId: input.installSessionId })
13386
+ body: JSON.stringify({
13387
+ installSessionId: input.installSessionId,
13388
+ installSource: input.installSource
13389
+ })
13339
13390
  },
13340
13391
  ANALYTICS_TIMEOUT_MS,
13341
13392
  input.fetchImpl ?? fetch
@@ -13348,6 +13399,7 @@ async function captureInstallStarted(input) {
13348
13399
  async function requestCliInstallSession(input) {
13349
13400
  const url = new URL(`${stripSlash2(input.gateway)}/api/auth/cli-session`);
13350
13401
  url.searchParams.set("install_session_id", input.installSessionId);
13402
+ url.searchParams.set("install_source", input.installSource);
13351
13403
  let response;
13352
13404
  try {
13353
13405
  response = await fetchWithTimeout(
@@ -13393,6 +13445,7 @@ async function connectInstallSession(input) {
13393
13445
  gateway,
13394
13446
  accessToken: credentials.access_token,
13395
13447
  installSessionId: input.installSessionId,
13448
+ installSource: input.installSource,
13396
13449
  fetchImpl
13397
13450
  });
13398
13451
  }
@@ -13410,15 +13463,20 @@ function failFromError3(err) {
13410
13463
  function registerInitCommand(program2) {
13411
13464
  program2.command("init").description(
13412
13465
  "Complete browser authentication and stage the Actionway Skill for your agent to install. The CLI never writes into an agent's own configuration directory."
13413
- ).option("--gateway <url>", "Actionway public origin").option("--client-id <id>", "Actionway OAuth client id").option("--issuer <url>", "Actionway OAuth issuer").option("--timeout-seconds <n>", "how long to wait for browser authentication").option("--install-session-id <uuid>", "onboarding install session id").option("--no-browser", "print the authorize URL instead of opening a browser").addOption(new Option("--skill-only").hideHelp()).action(async (opts) => {
13466
+ ).option("--gateway <url>", "Actionway public origin").option("--client-id <id>", "Actionway OAuth client id (must be paired with --issuer)").option("--issuer <url>", "Actionway OAuth issuer (must be paired with --client-id)").option("--timeout-seconds <n>", "how long to wait for browser authentication").option("--install-session-id <uuid>", "onboarding install session id").option("--no-browser", "print the authorize URL instead of opening a browser").addOption(new Option("--skill-only").hideHelp()).action(async (opts) => {
13414
13467
  try {
13415
13468
  const skill = materializeSkill();
13416
13469
  if (opts.skillOnly) {
13417
13470
  ok({ skill });
13418
13471
  }
13472
+ const installSource = opts.installSessionId ? "website_prompt" : "manual_init";
13419
13473
  const installSessionId = resolveInstallSessionId(opts.installSessionId);
13420
13474
  const initGateway = initGatewayUrl(opts.gateway, process.env);
13421
- await captureInstallStarted({ gateway: initGateway, installSessionId });
13475
+ await captureInstallStarted({
13476
+ gateway: initGateway,
13477
+ installSessionId,
13478
+ installSource
13479
+ });
13422
13480
  let loginStarted = false;
13423
13481
  if (!whoamiSnapshot()) {
13424
13482
  loginStarted = true;
@@ -13433,7 +13491,8 @@ function registerInitCommand(program2) {
13433
13491
  }
13434
13492
  const account = await connectInstallSession({
13435
13493
  ...opts.gateway ? { explicitGateway: opts.gateway } : {},
13436
- installSessionId
13494
+ installSessionId,
13495
+ installSource
13437
13496
  });
13438
13497
  const snapshot = whoamiSnapshot();
13439
13498
  ok({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actionway/cli",
3
- "version": "0.18.2",
3
+ "version": "0.18.4",
4
4
  "description": "actionway CLI 的本地端壳:Clerk OAuth 鉴权 + cli-core 共享命令面 + init / update / skill 物化 / 版本三链路(终端用户本地 Codex / Claude Code 使用)",
5
5
  "type": "module",
6
6
  "bin": {