@clickraft/cli 0.1.4 → 0.1.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/CHANGELOG.md CHANGED
@@ -1,9 +1,8 @@
1
- ## [0.1.4](https://github.com/clickraft/cli/compare/v0.1.3...v0.1.4) (2026-05-23)
1
+ ## [0.1.6](https://github.com/clickraft/cli/compare/v0.1.5...v0.1.6) (2026-05-23)
2
2
 
3
3
  ### Bug Fixes
4
4
 
5
- * **errors:** surface zod issue path in E_SCHEMA_MISMATCH messages ([723cc29](https://github.com/clickraft/cli/commit/723cc29cd65c8c34de523ce49974bd2504a119dd))
6
- * **schemas:** allow null estimatedSeconds in POST /jobs response ([52a96b0](https://github.com/clickraft/cli/commit/52a96b0439be9cb2554b9cf192956c5afd731d37))
5
+ * **client:** support Cloudflare Access service tokens via env vars ([4141f21](https://github.com/clickraft/cli/commit/4141f21a3c53f5795fef41f3bd876670563968f1))
7
6
 
8
7
  # Changelog
9
8
 
package/README.md CHANGED
@@ -21,6 +21,19 @@ clickraft generate create --prompt "a serene mountain landscape at dawn"
21
21
 
22
22
  Full documentation: [docs.clickraft.ai/cli](https://docs.clickraft.ai/cli) _(coming soon)_
23
23
 
24
+ ## Connecting to staging
25
+
26
+ The staging API (`https://staging.clickraft.ai/api/agents/v1`) sits behind Cloudflare Access Zero Trust. To authenticate non-interactively, create a service token in the Cloudflare Zero Trust dashboard (**Access → Service Auth → Service Tokens**), then export both halves before invoking the CLI:
27
+
28
+ ```bash
29
+ export CLOUDFLARE_ACCESS_CLIENT_ID="<client-id>.access"
30
+ export CLOUDFLARE_ACCESS_CLIENT_SECRET="<client-secret>"
31
+
32
+ clickraft tokens list --profile staging
33
+ ```
34
+
35
+ Both env vars must be set together — if only one is set the CLI exits with a configuration error. Production usage (the default) does not require these env vars.
36
+
24
37
  ## Use with AI coding agents
25
38
 
26
39
  The Clickraft CLI is designed to work with Claude Code, Cursor, Codex, and other AI coding agents. Install the skills package for your agent:
package/dist/cli.js CHANGED
@@ -529,6 +529,10 @@ var ApiClient = class {
529
529
  if (this.options.accessToken) {
530
530
  h.authorization = `Bearer ${this.options.accessToken}`;
531
531
  }
532
+ if (this.options.cloudflareAccess) {
533
+ h["cf-access-client-id"] = this.options.cloudflareAccess.clientId;
534
+ h["cf-access-client-secret"] = this.options.cloudflareAccess.clientSecret;
535
+ }
532
536
  if (opts.body !== void 0 && opts.method !== "GET" && opts.method !== "DELETE") {
533
537
  h["content-type"] = "application/json";
534
538
  }
@@ -1173,8 +1177,11 @@ var TOKEN_ENV_VAR = "CLICKRAFT_TOKEN";
1173
1177
  var PROFILE_ENV_VAR = "CLICKRAFT_PROFILE";
1174
1178
  var API_BASE_URL_ENV_VAR = "CLICKRAFT_API_BASE_URL";
1175
1179
  var TELEMETRY_DISABLE_ENV_VAR = "CLICKRAFT_DISABLE_TELEMETRY";
1180
+ var CLOUDFLARE_ACCESS_CLIENT_ID_ENV_VAR = "CLOUDFLARE_ACCESS_CLIENT_ID";
1181
+ var CLOUDFLARE_ACCESS_CLIENT_SECRET_ENV_VAR = "CLOUDFLARE_ACCESS_CLIENT_SECRET";
1176
1182
  async function loadConfig(options = {}) {
1177
1183
  const env = options.env ?? process.env;
1184
+ const cloudflareAccess = loadCloudflareAccess(env);
1178
1185
  const flagToken = trim(options.flagToken);
1179
1186
  const envToken = trim(env[TOKEN_ENV_VAR]);
1180
1187
  const flagProfile = trim(options.flagProfile);
@@ -1204,9 +1211,26 @@ async function loadConfig(options = {}) {
1204
1211
  return {
1205
1212
  accessToken: tokenFromFlagOrEnv ?? fileAccessToken ?? null,
1206
1213
  profileName: profileName ?? fileProfileName ?? DEFAULT_PROFILE_NAME,
1207
- apiBaseUrl: apiBaseFromFlagOrEnv ?? fileApiBaseUrl ?? DEFAULT_API_BASE_URL
1214
+ apiBaseUrl: apiBaseFromFlagOrEnv ?? fileApiBaseUrl ?? DEFAULT_API_BASE_URL,
1215
+ cloudflareAccess
1208
1216
  };
1209
1217
  }
1218
+ function loadCloudflareAccess(env) {
1219
+ const clientId = trim(env[CLOUDFLARE_ACCESS_CLIENT_ID_ENV_VAR]);
1220
+ const clientSecret = trim(env[CLOUDFLARE_ACCESS_CLIENT_SECRET_ENV_VAR]);
1221
+ if (clientId && clientSecret) {
1222
+ return { clientId, clientSecret };
1223
+ }
1224
+ if (!clientId && !clientSecret) {
1225
+ return null;
1226
+ }
1227
+ const onlySet = clientId ? CLOUDFLARE_ACCESS_CLIENT_ID_ENV_VAR : CLOUDFLARE_ACCESS_CLIENT_SECRET_ENV_VAR;
1228
+ throw new ApiError(
1229
+ "INPUT_INVALID_FORMAT",
1230
+ `${CLOUDFLARE_ACCESS_CLIENT_ID_ENV_VAR} and ${CLOUDFLARE_ACCESS_CLIENT_SECRET_ENV_VAR} must both be set, or neither. Got only ${onlySet}.`,
1231
+ { retryable: false }
1232
+ );
1233
+ }
1210
1234
  function trim(value) {
1211
1235
  if (value === void 0) return void 0;
1212
1236
  const t = value.trim();
@@ -1300,7 +1324,8 @@ async function buildClient(opts) {
1300
1324
  return new ApiClient({
1301
1325
  apiBaseUrl: cfg.apiBaseUrl,
1302
1326
  accessToken: cfg.accessToken,
1303
- signal: opts.signal
1327
+ signal: opts.signal,
1328
+ cloudflareAccess: cfg.cloudflareAccess
1304
1329
  });
1305
1330
  }
1306
1331
  async function postAssetMetadata(args) {
@@ -1466,7 +1491,8 @@ async function buildGenerateClient(opts) {
1466
1491
  return new ApiClient({
1467
1492
  apiBaseUrl: cfg.apiBaseUrl,
1468
1493
  accessToken: cfg.accessToken,
1469
- signal: opts.signal
1494
+ signal: opts.signal,
1495
+ cloudflareAccess: cfg.cloudflareAccess
1470
1496
  });
1471
1497
  }
1472
1498
  function isUrlLike(value) {
@@ -2073,7 +2099,12 @@ async function logout(options) {
2073
2099
  let serverRevoked = false;
2074
2100
  let revokeFailureMessage;
2075
2101
  if (options.revoke) {
2076
- const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({ apiBaseUrl, accessToken, signal: options.signal }));
2102
+ const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({
2103
+ apiBaseUrl,
2104
+ accessToken,
2105
+ signal: options.signal,
2106
+ cloudflareAccess: config.cloudflareAccess
2107
+ }));
2077
2108
  const client = factory(profile.apiBaseUrl, profile.accessToken);
2078
2109
  try {
2079
2110
  await client.request({
@@ -2189,9 +2220,7 @@ var TokenRowSchema = z6.object({
2189
2220
  expiresAt: z6.string(),
2190
2221
  createdAt: z6.string()
2191
2222
  }).strict();
2192
- var TokensListResponseSchema = z6.object({
2193
- tokens: z6.array(TokenRowSchema)
2194
- }).strict();
2223
+ var TokensListResponseSchema = z6.array(TokenRowSchema);
2195
2224
  async function listTokens(options) {
2196
2225
  const config = await loadConfig({
2197
2226
  flagProfile: options.profile,
@@ -2200,7 +2229,12 @@ async function listTokens(options) {
2200
2229
  if (!config.accessToken) {
2201
2230
  throw new AuthError("AUTH_TOKEN_MISSING", `Not authenticated. Run \`clickraft login\` first.`);
2202
2231
  }
2203
- const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({ apiBaseUrl, accessToken, signal: options.signal }));
2232
+ const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({
2233
+ apiBaseUrl,
2234
+ accessToken,
2235
+ signal: options.signal,
2236
+ cloudflareAccess: config.cloudflareAccess
2237
+ }));
2204
2238
  const client = factory(config.apiBaseUrl, config.accessToken);
2205
2239
  const data = await client.request({
2206
2240
  method: "GET",
@@ -2208,7 +2242,7 @@ async function listTokens(options) {
2208
2242
  responseSchema: TokensListResponseSchema,
2209
2243
  signal: options.signal
2210
2244
  });
2211
- return data.tokens;
2245
+ return data;
2212
2246
  }
2213
2247
 
2214
2248
  // src/errors/format.ts