@primitivedotdev/sdk 0.20.0 → 0.21.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.
@@ -4,7 +4,7 @@ import { Command, Errors, Flags } from "@oclif/core";
4
4
  import { getAccount, pollCliLogin, startCliLogin, } from "../../api/generated/sdk.gen.js";
5
5
  import { PrimitiveApiClient } from "../../api/index.js";
6
6
  import { API_ERROR_CODES, extractErrorCode, extractErrorPayload, removeStaleSavedCredentialOnUnauthorized, writeErrorWithHints, } from "../api-command.js";
7
- import { acquireCliCredentialsLock, credentialsPath, loadCliCredentials, normalizeBaseUrl, saveCliCredentials, } from "../auth.js";
7
+ import { acquireCliCredentialsLock, credentialsPath, loadCliCredentials, normalizeApiBaseUrl1, normalizeApiBaseUrl2, saveCliCredentials, } from "../auth.js";
8
8
  const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
9
9
  function cliError(message) {
10
10
  return new Errors.CLIError(message, { exit: 1 });
@@ -36,13 +36,13 @@ function retryAfterSeconds(result) {
36
36
  return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
37
37
  }
38
38
  export async function checkExistingLogin(params) {
39
- const baseUrlOverridden = params.baseUrl !== undefined;
40
- const probeBaseUrl = baseUrlOverridden
41
- ? normalizeBaseUrl(params.baseUrl)
42
- : params.credentials.base_url;
39
+ const baseUrlOverridden = params.apiBaseUrl1 !== undefined;
40
+ const probeApiBaseUrl1 = baseUrlOverridden
41
+ ? normalizeApiBaseUrl1(params.apiBaseUrl1)
42
+ : params.credentials.api_base_url_1;
43
43
  const apiClient = new PrimitiveApiClient({
44
44
  apiKey: params.credentials.api_key,
45
- baseUrl: probeBaseUrl,
45
+ apiBaseUrl1: probeApiBaseUrl1,
46
46
  });
47
47
  const result = await (params.checkAccount ??
48
48
  ((client) => getAccount({
@@ -54,7 +54,10 @@ export async function checkExistingLogin(params) {
54
54
  const payload = extractErrorPayload(result.error);
55
55
  const auth = {
56
56
  apiKey: params.credentials.api_key,
57
- baseUrl: probeBaseUrl,
57
+ apiBaseUrl1: probeApiBaseUrl1,
58
+ // Host-2 isn't relevant to checkExistingLogin (login is on host-1
59
+ // only), but the auth shape requires it. Use the default.
60
+ apiBaseUrl2: normalizeApiBaseUrl2(undefined),
58
61
  credentials: params.credentials,
59
62
  source: "stored",
60
63
  };
@@ -84,9 +87,10 @@ class LoginCommand extends Command {
84
87
  "<%= config.bin %> login --force",
85
88
  ];
86
89
  static flags = {
87
- "base-url": Flags.string({
88
- description: "API base URL (defaults to PRIMITIVE_API_URL or production)",
89
- env: "PRIMITIVE_API_URL",
90
+ "api-base-url-1": Flags.string({
91
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
92
+ env: "PRIMITIVE_API_BASE_URL_1",
93
+ hidden: true,
90
94
  }),
91
95
  "device-name": Flags.string({
92
96
  description: "Device name shown in the browser approval screen",
@@ -117,7 +121,7 @@ class LoginCommand extends Command {
117
121
  }
118
122
  }
119
123
  async runWithCredentialLock(flags) {
120
- const baseUrl = normalizeBaseUrl(flags["base-url"]);
124
+ const apiBaseUrl1 = normalizeApiBaseUrl1(flags["api-base-url-1"]);
121
125
  let existing;
122
126
  try {
123
127
  existing = loadCliCredentials(this.config.configDir);
@@ -134,7 +138,7 @@ class LoginCommand extends Command {
134
138
  }
135
139
  else if (existing) {
136
140
  const existingStatus = await checkExistingLogin({
137
- baseUrl: flags["base-url"],
141
+ apiBaseUrl1: flags["api-base-url-1"],
138
142
  configDir: this.config.configDir,
139
143
  credentials: existing,
140
144
  });
@@ -150,7 +154,7 @@ class LoginCommand extends Command {
150
154
  throw cliError(`Already logged in${org}. Run \`primitive logout\` before logging in again.`);
151
155
  }
152
156
  }
153
- const apiClient = new PrimitiveApiClient({ baseUrl });
157
+ const apiClient = new PrimitiveApiClient({ apiBaseUrl1 });
154
158
  const deviceName = flags["device-name"] ?? hostname();
155
159
  const started = await startCliLogin({
156
160
  body: {
@@ -192,7 +196,7 @@ class LoginCommand extends Command {
192
196
  }
193
197
  saveCliCredentials(this.config.configDir, {
194
198
  api_key: login.api_key,
195
- base_url: baseUrl,
199
+ api_base_url_1: apiBaseUrl1,
196
200
  created_at: new Date().toISOString(),
197
201
  key_id: login.key_id,
198
202
  key_prefix: login.key_prefix,
@@ -2,7 +2,7 @@ import { Command, Errors, Flags } from "@oclif/core";
2
2
  import { cliLogout } from "../../api/generated/sdk.gen.js";
3
3
  import { PrimitiveApiClient } from "../../api/index.js";
4
4
  import { API_ERROR_CODES, extractErrorCode, extractErrorPayload, writeErrorWithHints, } from "../api-command.js";
5
- import { acquireCliCredentialsLock, deleteCliCredentials, loadCliCredentials, normalizeBaseUrl, } from "../auth.js";
5
+ import { acquireCliCredentialsLock, deleteCliCredentials, loadCliCredentials, normalizeApiBaseUrl1, } from "../auth.js";
6
6
  function cliError(message) {
7
7
  return new Errors.CLIError(message, { exit: 1 });
8
8
  }
@@ -15,9 +15,10 @@ class LogoutCommand extends Command {
15
15
  static summary = "Log out and revoke the saved CLI key";
16
16
  static examples = ["<%= config.bin %> logout"];
17
17
  static flags = {
18
- "base-url": Flags.string({
19
- description: "Override the API base URL used for key revocation",
20
- env: "PRIMITIVE_API_URL",
18
+ "api-base-url-1": Flags.string({
19
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
20
+ env: "PRIMITIVE_API_BASE_URL_1",
21
+ hidden: true,
21
22
  }),
22
23
  };
23
24
  async run() {
@@ -52,12 +53,12 @@ class LogoutCommand extends Command {
52
53
  if (!credentials) {
53
54
  throw cliError("Not logged in. Run `primitive login` to create saved CLI credentials.");
54
55
  }
55
- const baseUrl = flags["base-url"]
56
- ? normalizeBaseUrl(flags["base-url"])
57
- : credentials.base_url;
56
+ const apiBaseUrl1 = flags["api-base-url-1"]
57
+ ? normalizeApiBaseUrl1(flags["api-base-url-1"])
58
+ : credentials.api_base_url_1;
58
59
  const apiClient = new PrimitiveApiClient({
59
60
  apiKey: credentials.api_key,
60
- baseUrl,
61
+ apiBaseUrl1,
61
62
  });
62
63
  const result = await cliLogout({
63
64
  body: { key_id: credentials.key_id },
@@ -115,9 +115,15 @@ class SendCommand extends Command {
115
115
  description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
116
116
  env: "PRIMITIVE_API_KEY",
117
117
  }),
118
- "base-url": Flags.string({
119
- description: "API base URL (defaults to PRIMITIVE_API_URL or production)",
120
- env: "PRIMITIVE_API_URL",
118
+ "api-base-url-1": Flags.string({
119
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
120
+ env: "PRIMITIVE_API_BASE_URL_1",
121
+ hidden: true,
122
+ }),
123
+ "api-base-url-2": Flags.string({
124
+ description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
125
+ env: "PRIMITIVE_API_BASE_URL_2",
126
+ hidden: true,
121
127
  }),
122
128
  to: Flags.string({
123
129
  description: "Recipient address (e.g. alice@example.com).",
@@ -154,15 +160,18 @@ class SendCommand extends Command {
154
160
  throw new Errors.CLIError("Either --body or --html (or both) is required.");
155
161
  }
156
162
  await runWithTiming(flags.time, async () => {
157
- const baseUrlOverridden = flags["base-url"] !== undefined;
163
+ const baseUrlOverridden = flags["api-base-url-1"] !== undefined ||
164
+ flags["api-base-url-2"] !== undefined;
158
165
  const auth = resolveCliAuth({
159
166
  apiKey: flags["api-key"],
160
- baseUrl: flags["base-url"],
167
+ apiBaseUrl1: flags["api-base-url-1"],
168
+ apiBaseUrl2: flags["api-base-url-2"],
161
169
  configDir: this.config.configDir,
162
170
  });
163
171
  const apiClient = new PrimitiveApiClient({
164
172
  apiKey: auth.apiKey,
165
- baseUrl: auth.baseUrl,
173
+ apiBaseUrl1: auth.apiBaseUrl1,
174
+ apiBaseUrl2: auth.apiBaseUrl2,
166
175
  });
167
176
  const authFailureContext = {
168
177
  auth,
@@ -187,7 +196,12 @@ class SendCommand extends Command {
187
196
  ? { wait_timeout_ms: flags["wait-timeout-ms"] }
188
197
  : {}),
189
198
  },
190
- client: apiClient.client,
199
+ // /send-mail goes to the attachments-supporting host. The
200
+ // wrapper exposes the host-2 client as _sendClient for this
201
+ // and any other host-2 operation that lands here. Customer
202
+ // SDK callers should use PrimitiveClient.send() instead so
203
+ // the routing stays internal.
204
+ client: apiClient._sendClient,
191
205
  responseStyle: "fields",
192
206
  });
193
207
  if (result.error) {
@@ -25,9 +25,15 @@ class WhoamiCommand extends Command {
25
25
  description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
26
26
  env: "PRIMITIVE_API_KEY",
27
27
  }),
28
- "base-url": Flags.string({
29
- description: "API base URL (defaults to PRIMITIVE_API_URL or production)",
30
- env: "PRIMITIVE_API_URL",
28
+ "api-base-url-1": Flags.string({
29
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
30
+ env: "PRIMITIVE_API_BASE_URL_1",
31
+ hidden: true,
32
+ }),
33
+ "api-base-url-2": Flags.string({
34
+ description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
35
+ env: "PRIMITIVE_API_BASE_URL_2",
36
+ hidden: true,
31
37
  }),
32
38
  time: Flags.boolean({
33
39
  description: TIME_FLAG_DESCRIPTION,
@@ -36,15 +42,18 @@ class WhoamiCommand extends Command {
36
42
  async run() {
37
43
  const { flags } = await this.parse(WhoamiCommand);
38
44
  await runWithTiming(flags.time, async () => {
39
- const baseUrlOverridden = flags["base-url"] !== undefined;
45
+ const baseUrlOverridden = flags["api-base-url-1"] !== undefined ||
46
+ flags["api-base-url-2"] !== undefined;
40
47
  const auth = resolveCliAuth({
41
48
  apiKey: flags["api-key"],
42
- baseUrl: flags["base-url"],
49
+ apiBaseUrl1: flags["api-base-url-1"],
50
+ apiBaseUrl2: flags["api-base-url-2"],
43
51
  configDir: this.config.configDir,
44
52
  });
45
53
  const apiClient = new PrimitiveApiClient({
46
54
  apiKey: auth.apiKey,
47
- baseUrl: auth.baseUrl,
55
+ apiBaseUrl1: auth.apiBaseUrl1,
56
+ apiBaseUrl2: auth.apiBaseUrl2,
48
57
  });
49
58
  const result = await getAccount({
50
59
  client: apiClient.client,
@@ -73,7 +73,7 @@ export function renderFishCompletion(binName) {
73
73
  ]) {
74
74
  lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l '${fishEscape(parameter.name.replace(/_/g, "-"))}' -r -d '${fishEscape(parameter.description ?? parameter.name)}'`);
75
75
  }
76
- lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'api-key' -r -d 'Primitive API key (defaults to PRIMITIVE_API_KEY or saved primitive login credentials)'`, `complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'base-url' -r -d 'API base URL (defaults to PRIMITIVE_API_URL or production)'`);
76
+ lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'api-key' -r -d 'Primitive API key (defaults to PRIMITIVE_API_KEY or saved primitive login credentials)'`);
77
77
  if (operation.hasJsonBody) {
78
78
  lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'body' -r -d 'JSON request body'`, `complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'body-file' -r -d 'Path to a JSON file used as the request body'`);
79
79
  }
@@ -2,6 +2,8 @@ import { Args, Command, Errors } from "@oclif/core";
2
2
  import { operationManifest, } from "../openapi/index.js";
3
3
  import { createOperationCommand } from "./api-command.js";
4
4
  import EmailsLatestCommand from "./commands/emails-latest.js";
5
+ import EmailsWaitCommand from "./commands/emails-wait.js";
6
+ import EmailsWatchCommand from "./commands/emails-watch.js";
5
7
  import FunctionsDeployCommand from "./commands/functions-deploy.js";
6
8
  import FunctionsRedeployCommand from "./commands/functions-redeploy.js";
7
9
  import LoginCommand from "./commands/login.js";
@@ -133,6 +135,10 @@ export const COMMANDS = {
133
135
  // inbound emails as a compact text table. emails:list-emails stays
134
136
  // available for the full JSON envelope + cursor pagination.
135
137
  "emails:latest": EmailsLatestCommand,
138
+ // `emails:watch` and `emails:wait` poll the search API for new matching
139
+ // inbound mail. `watch` defaults to a human table; `wait` defaults to JSONL.
140
+ "emails:watch": EmailsWatchCommand,
141
+ "emails:wait": EmailsWaitCommand,
136
142
  // `functions:deploy` and `functions:redeploy` are file-input
137
143
  // shortcuts for create-function / update-function. The underlying
138
144
  // ops take `code` as a body string, which is awkward at the CLI