@raster-app/cli 0.1.0 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +86 -22
  2. package/dist/index.js +140 -96
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,10 +8,10 @@ Command-line client for the [Raster](https://raster.app) API. Browse, search, up
8
8
  npm install -g @raster-app/cli
9
9
  ```
10
10
 
11
- Run ad hoc without installing:
11
+ Run a single command without installing — the binary is `raster`:
12
12
 
13
13
  ```sh
14
- npx @raster-app/cli whoami
14
+ npx -p @raster-app/cli raster whoami
15
15
  ```
16
16
 
17
17
  A standalone macOS (Apple Silicon) binary ships with each [GitHub release](https://github.com/raster-app/raster-cli/releases) — no Node required.
@@ -24,38 +24,102 @@ The CLI uses your organization's API key (create one in Raster under Settings
24
24
  raster auth login
25
25
  ```
26
26
 
27
- Validates the key against the API and stores it in `~/.config/raster/config.json` (mode `0600`). In CI, set `RASTER_API_KEY` instead — no login needed.
27
+ The command validates the key against the API and stores it in `~/.config/raster/config.json` with owner-only permissions. In CI, set `RASTER_API_KEY` instead.
28
28
 
29
- Key resolution order: `--api-key` flag, then `RASTER_API_KEY`, then the config file. `raster auth status` shows which source is active.
29
+ The key resolves from the `--api-key` flag, then `RASTER_API_KEY`, then the config file. `raster auth status` shows which source is active.
30
+
31
+ ## Org and library are derived from the key
32
+
33
+ An API key is scoped to one organization and a set of libraries. `raster auth login` captures that scope into the config file, so commands resolve it locally — no extra request per command:
34
+
35
+ - **`--org` is optional** — the organization comes from the key.
36
+ - **`--library` is optional when the key has a single library.** When the key can reach several, commands that act on one library ask you to pass `--library <id>` and list the choices.
37
+
38
+ Pass `--org` / `--library` to override, or to pick a library when the key spans more than one. A key supplied via `RASTER_API_KEY` with no prior login resolves its scope from the API on first use. If a key's library access changes in Raster, re-run `raster auth login` to refresh the cached list, or pass `--library`.
39
+
40
+ ## Global flags
41
+
42
+ These work on every command, before or after the subcommand.
43
+
44
+ | Flag | Purpose |
45
+ | --- | --- |
46
+ | `--api-key <key>` | API key (overrides `RASTER_API_KEY` and the config file) |
47
+ | `--org <organizationId>` | Organization id (derived from the key when omitted) |
48
+ | `--library <libraryId>` | Library id (derived from the key when it has one library) |
49
+ | `--json` | Print the raw API payload to stdout and nothing else |
50
+ | `--verbose` | Log each request to stderr (method, path, masked key, status, duration) |
30
51
 
31
52
  ## Commands
32
53
 
33
- ```text
34
- raster auth login|logout|status
54
+ ### `auth`
55
+
56
+ | Command | Description |
57
+ | --- | --- |
58
+ | `raster auth login` | Validate an API key and store it. Pass `--api-key <key>`, or run interactively to be prompted. |
59
+ | `raster auth logout` | Remove the stored API key. |
60
+ | `raster auth status` | Show which key is in use, its source, and the organization it reaches. |
61
+
62
+ ### `whoami`
63
+
64
+ ```sh
35
65
  raster whoami
36
- raster libraries ls|create|rename
37
- raster assets ls|get|search|download|upload|rm|describe|transfer
38
- raster tags ls|add|rm
39
- raster orgs create
40
66
  ```
41
67
 
42
- Examples:
68
+ Show the organization, plan, and libraries the API key can access.
69
+
70
+ ### `libraries`
71
+
72
+ | Command | Description | Flags |
73
+ | --- | --- | --- |
74
+ | `raster libraries ls` | List libraries in the organization. | `--page <n>`, `--page-size <n>` |
75
+ | `raster libraries create` | Create a library. | `--name <name>` (required), `--slug <slug>` |
76
+ | `raster libraries rename` | Rename the library (`--library`, or the key's only one). | `--name <name>` (required) |
77
+
78
+ ### `assets`
79
+
80
+ Asset ids are always positional. `--library` selects the library when the key has more than one.
81
+
82
+ | Command | Description | Flags |
83
+ | --- | --- | --- |
84
+ | `raster assets ls` | List assets in the library. | `--page <n>`, `--page-size <n>`, `--tag <tag...>` (repeatable, up to 5) |
85
+ | `raster assets get <assetId>` | Show one asset's metadata. | — |
86
+ | `raster assets search <query>` | Search assets across the organization (`--library` scopes to one). | `--page <n>`, `--page-size <n>` |
87
+ | `raster assets download <assetId>` | Download the asset's file to disk. | `-o, --output <path>`, `--force` |
88
+ | `raster assets upload <files...>` | Upload local files (batched at 20 per request). | — |
89
+ | `raster assets rm <assetIds...>` | Move assets to trash (recoverable). | `--yes` (skip the confirm prompt) |
90
+ | `raster assets describe <assetId>` | Set an asset's description. | `--text <description>` (required) |
91
+ | `raster assets transfer <assetIds...>` | Move assets to another library. | `--to <libraryId>` (required) |
92
+
93
+ ### `tags`
94
+
95
+ | Command | Description | Flags |
96
+ | --- | --- | --- |
97
+ | `raster tags ls` | List tags in the library. | `--limit <n>` |
98
+ | `raster tags add <assetIds...>` | Add tags to assets. | `--tag <tag...>` (required, repeatable) |
99
+ | `raster tags rm <assetIds...>` | Remove tags from assets. | `--tag <tag...>` (required, repeatable) |
100
+
101
+ ### `orgs`
43
102
 
44
103
  ```sh
45
- raster libraries ls --org org_123
46
- raster assets ls --org org_123 --library lib_456 --tag sunset
47
- raster assets upload --org org_123 --library lib_456 ./photos/*.png
48
- raster assets search --org org_123 "golden hour" --json | jq '.hits[].id'
49
- raster orgs create --email you@example.com --save
104
+ raster orgs create --email <email> [--name <name>] [--save]
50
105
  ```
51
106
 
52
- `raster <command> --help` documents every flag.
107
+ Create a library with no account (held 30 days until claimed). Prints the claim URL; `--save` stores the minted key. Anonymous — sends no API key.
108
+
109
+ ## Examples
110
+
111
+ ```sh
112
+ raster libraries ls
113
+ raster assets ls --library brand --tag sunset
114
+ raster assets upload --library brand ./photos/*.png
115
+ raster assets search "golden hour" --json | jq '.hits[].id'
116
+ raster tags add asset_123 asset_456 --tag launch --library brand
117
+ raster assets transfer asset_123 --to archive --library brand
118
+ ```
53
119
 
54
- ## Output
120
+ ## Output and scripting
55
121
 
56
- - Human-readable tables on stdout; progress and notes on stderr, so stdout stays pipeable.
57
- - `--json` prints the raw API payload to stdout and nothing else.
58
- - `--verbose` logs each request (method, path, masked key, status, duration) to stderr.
122
+ Human-readable tables print to stdout; progress and notes go to stderr, so stdout stays pipeable. `--json` prints only the raw API payload to stdout. `--verbose` logs each request to stderr with the key masked.
59
123
 
60
124
  ## Exit codes
61
125
 
@@ -88,7 +152,7 @@ bun run gen:types # regenerate src/api/openapi.d.ts from the live OpenAPI docu
88
152
  bun run build # bundle dist/index.js
89
153
  ```
90
154
 
91
- Types are generated from the public OpenAPI document at `https://api.raster.app/openapi.json`; CI fails when the committed types drift from production. The published package bundles its three libraries (`commander`, `zod`, `openapi-fetch`) into `dist/index.js` — installing the CLI pulls zero dependencies. Releases run `scripts/check-tarball.ts`, which fails if the tarball contains anything beyond `dist/`, `package.json`, `README.md`, and `LICENSE`.
155
+ Types are generated from the public OpenAPI document at `https://api.raster.app/openapi.json`; CI fails when the committed types drift from production. The published package bundles its three libraries (`commander`, `zod`, `openapi-fetch`) into `dist/index.js` — installing the CLI pulls zero dependencies.
92
156
 
93
157
  ## License
94
158
 
package/dist/index.js CHANGED
@@ -16337,7 +16337,7 @@ config(en_default());
16337
16337
  // package.json
16338
16338
  var package_default = {
16339
16339
  name: "@raster-app/cli",
16340
- version: "0.1.0",
16340
+ version: "0.2.0",
16341
16341
  description: "Command-line client for the Raster API",
16342
16342
  license: "Apache-2.0",
16343
16343
  type: "module",
@@ -17032,7 +17032,9 @@ var CONFIG_FILE_NAME = "config.json";
17032
17032
  var CONFIG_FILE_MODE = 384;
17033
17033
  var CONFIG_DIR_MODE = 448;
17034
17034
  var configSchema = exports_external.object({
17035
- apiKey: exports_external.string().min(1).optional()
17035
+ apiKey: exports_external.string().min(1).optional(),
17036
+ organizationId: exports_external.string().min(1).optional(),
17037
+ libraries: exports_external.array(exports_external.string()).optional()
17036
17038
  });
17037
17039
  function configDir(env) {
17038
17040
  if (env.RASTER_CONFIG_HOME)
@@ -17204,6 +17206,22 @@ async function createCommandContext(globals, overrides = {}) {
17204
17206
  const baseUrl = validateBaseUrl(env.RASTER_API_BASE_URL ?? DEFAULT_BASE_URL);
17205
17207
  const baseFetch = overrides.fetch ?? globalThis.fetch;
17206
17208
  const instrumentedFetch = instrumentFetch(baseFetch, output, apiKey ? maskApiKey(apiKey.key) : null);
17209
+ const clientFactory = (clientOverrides) => createApiClient({
17210
+ baseUrl,
17211
+ apiKey: clientOverrides ? clientOverrides.apiKey : apiKey?.key ?? null,
17212
+ fetch: instrumentedFetch
17213
+ });
17214
+ let viewerPromise = null;
17215
+ const loadViewer = () => {
17216
+ if (!viewerPromise) {
17217
+ if (!apiKey) {
17218
+ throw new AuthSetupError("No API key found. Run `raster auth login` or set RASTER_API_KEY.");
17219
+ }
17220
+ viewerPromise = unwrapResult(() => clientFactory({ apiKey: apiKey.key }).GET("/me", { params: { header: VERSION_HEADER } }));
17221
+ }
17222
+ return viewerPromise;
17223
+ };
17224
+ const cacheMatchesKey = config2.apiKey !== undefined && apiKey?.key === config2.apiKey;
17207
17225
  return {
17208
17226
  ...output,
17209
17227
  env,
@@ -17211,13 +17229,35 @@ async function createCommandContext(globals, overrides = {}) {
17211
17229
  isInteractive: overrides.isInteractive ?? process.stdin.isTTY === true,
17212
17230
  apiKey,
17213
17231
  fetch: instrumentedFetch,
17214
- clientFactory: (clientOverrides) => createApiClient({
17215
- baseUrl,
17216
- apiKey: clientOverrides ? clientOverrides.apiKey : apiKey?.key ?? null,
17217
- fetch: instrumentedFetch
17218
- }),
17232
+ clientFactory,
17219
17233
  confirm: overrides.confirm ?? promptConfirm,
17220
- promptSecret: overrides.promptSecret ?? promptSecret
17234
+ promptSecret: overrides.promptSecret ?? promptSecret,
17235
+ explicitLibrary: globals.library,
17236
+ resolveOrg: async () => {
17237
+ if (globals.org)
17238
+ return globals.org;
17239
+ if (cacheMatchesKey && config2.organizationId)
17240
+ return config2.organizationId;
17241
+ const viewer = await loadViewer();
17242
+ if (!viewer.organizationId) {
17243
+ throw new CliError("Could not determine the organization from the API key.", EXIT_CODES.generic);
17244
+ }
17245
+ return viewer.organizationId;
17246
+ },
17247
+ resolveLibrary: async () => {
17248
+ if (globals.library)
17249
+ return globals.library;
17250
+ const cachedLibraries = cacheMatchesKey ? config2.libraries : undefined;
17251
+ const source = cachedLibraries ?? (await loadViewer()).libraries ?? [];
17252
+ const libraries = source.filter((value) => typeof value === "string");
17253
+ const onlyLibrary = libraries[0];
17254
+ if (libraries.length === 1 && onlyLibrary)
17255
+ return onlyLibrary;
17256
+ if (libraries.length === 0) {
17257
+ throw new AuthSetupError("The API key has no library access.");
17258
+ }
17259
+ throw new UsageError(`The API key can access ${libraries.length} libraries (${libraries.join(", ")}). Pass --library <id>.`);
17260
+ }
17221
17261
  };
17222
17262
  }
17223
17263
  function instrumentFetch(baseFetch, output, maskedKey) {
@@ -17284,11 +17324,7 @@ var SEARCH_HIT_COLUMNS = [
17284
17324
  { header: "NAME", value: (hit) => hit.name ?? "" },
17285
17325
  { header: "LIBRARY", value: (hit) => hit.libraryId ?? "" }
17286
17326
  ];
17287
- var inLibraryOptionsSchema = exports_external.object({
17288
- org: exports_external.string().min(1),
17289
- library: exports_external.string().min(1)
17290
- });
17291
- var listOptionsSchema = inLibraryOptionsSchema.extend({
17327
+ var listOptionsSchema = exports_external.object({
17292
17328
  page: exports_external.coerce.number().int().min(1).optional(),
17293
17329
  pageSize: exports_external.coerce.number().int().min(1).optional(),
17294
17330
  tag: exports_external.array(exports_external.string().min(1)).default([])
@@ -17298,10 +17334,12 @@ async function listAssetsCommand(context, options) {
17298
17334
  throw new ValidationError(`At most ${MAX_TAGS_PER_QUERY} --tag filters are supported per query.`);
17299
17335
  }
17300
17336
  requireApiKey(context);
17337
+ const organizationId = await context.resolveOrg();
17338
+ const libraryId = await context.resolveLibrary();
17301
17339
  const client = context.clientFactory();
17302
17340
  const assets = await unwrapResult(() => client.GET("/organizations/{organizationId}/libraries/{libraryId}/assets", {
17303
17341
  params: {
17304
- path: { organizationId: options.org, libraryId: options.library },
17342
+ path: { organizationId, libraryId },
17305
17343
  header: VERSION_HEADER,
17306
17344
  query: {
17307
17345
  page: options.page,
@@ -17316,10 +17354,9 @@ async function listAssetsCommand(context, options) {
17316
17354
  }
17317
17355
  renderTable(context, assets, ASSET_COLUMNS);
17318
17356
  }
17319
- var getOptionsSchema = inLibraryOptionsSchema;
17320
- async function getAssetCommand(context, assetId, options) {
17357
+ async function getAssetCommand(context, assetId) {
17321
17358
  requireApiKey(context);
17322
- const asset = await fetchAsset(context, options.org, options.library, assetId);
17359
+ const asset = await fetchAsset(context, assetId);
17323
17360
  if (context.json) {
17324
17361
  printJson(context, asset);
17325
17362
  return;
@@ -17335,28 +17372,29 @@ async function getAssetCommand(context, assetId, options) {
17335
17372
  { label: "url", value: asset.url ?? "" }
17336
17373
  ]);
17337
17374
  }
17338
- async function fetchAsset(context, organizationId, libraryId, assetId) {
17375
+ async function fetchAsset(context, assetId) {
17376
+ const organizationId = await context.resolveOrg();
17377
+ const libraryId = await context.resolveLibrary();
17339
17378
  const client = context.clientFactory();
17340
17379
  return unwrapResult(() => client.GET("/organizations/{organizationId}/libraries/{libraryId}/assets/{assetId}", {
17341
17380
  params: { path: { organizationId, libraryId, assetId }, header: VERSION_HEADER }
17342
17381
  }));
17343
17382
  }
17344
17383
  var searchOptionsSchema = exports_external.object({
17345
- org: exports_external.string().min(1),
17346
- library: exports_external.array(exports_external.string().min(1)).default([]),
17347
17384
  page: exports_external.coerce.number().int().min(1).optional(),
17348
17385
  pageSize: exports_external.coerce.number().int().min(1).optional()
17349
17386
  });
17350
17387
  async function searchAssetsCommand(context, query, options) {
17351
17388
  requireApiKey(context);
17389
+ const organizationId = await context.resolveOrg();
17352
17390
  const client = context.clientFactory();
17353
17391
  const result = await unwrapResult(() => client.GET("/organizations/{organizationId}/search/assets", {
17354
17392
  params: {
17355
- path: { organizationId: options.org },
17393
+ path: { organizationId },
17356
17394
  header: VERSION_HEADER,
17357
17395
  query: {
17358
17396
  q: query,
17359
- libraries: options.library.length > 0 ? options.library.join(",") : undefined,
17397
+ libraries: context.explicitLibrary,
17360
17398
  page: options.page,
17361
17399
  pageSize: options.pageSize
17362
17400
  }
@@ -17369,13 +17407,13 @@ async function searchAssetsCommand(context, query, options) {
17369
17407
  note(context, `${result.found} result(s), page ${result.page}.`);
17370
17408
  renderTable(context, result.hits, SEARCH_HIT_COLUMNS);
17371
17409
  }
17372
- var downloadOptionsSchema = inLibraryOptionsSchema.extend({
17410
+ var downloadOptionsSchema = exports_external.object({
17373
17411
  output: exports_external.string().min(1).optional(),
17374
17412
  force: exports_external.boolean().default(false)
17375
17413
  });
17376
17414
  async function downloadAssetCommand(context, assetId, options) {
17377
17415
  requireApiKey(context);
17378
- const asset = await fetchAsset(context, options.org, options.library, assetId);
17416
+ const asset = await fetchAsset(context, assetId);
17379
17417
  const fileUrl = asset.url;
17380
17418
  if (!fileUrl)
17381
17419
  throw new CliError("Asset has no file URL to download.", EXIT_CODES.generic);
@@ -17399,11 +17437,12 @@ async function downloadAssetCommand(context, assetId, options) {
17399
17437
  var uploadedAssetsSchema = exports_external.object({
17400
17438
  assets: exports_external.array(exports_external.looseObject({ id: exports_external.string().optional(), name: exports_external.string().nullish() }))
17401
17439
  });
17402
- var uploadOptionsSchema = inLibraryOptionsSchema;
17403
- async function uploadAssetsCommand(context, files, options) {
17440
+ async function uploadAssetsCommand(context, files) {
17404
17441
  if (files.length === 0)
17405
17442
  throw new UsageError("Provide at least one file to upload.");
17406
17443
  const { key } = requireApiKey(context);
17444
+ const organizationId = await context.resolveOrg();
17445
+ const libraryId = await context.resolveLibrary();
17407
17446
  const batches = chunk(files, MAX_UPLOAD_FILES);
17408
17447
  const uploadedNames = [];
17409
17448
  const payloads = [];
@@ -17421,7 +17460,7 @@ async function uploadAssetsCommand(context, files, options) {
17421
17460
  if (batches.length > 1) {
17422
17461
  note(context, `Uploading batch ${batchIndex + 1}/${batches.length} (${batch.length} files)…`);
17423
17462
  }
17424
- const response = await context.fetch(`${context.baseUrl}/organizations/${options.org}/libraries/${options.library}/assets`, { method: "POST", headers: buildRequestHeaders(key), body: formData });
17463
+ const response = await context.fetch(`${context.baseUrl}/organizations/${organizationId}/libraries/${libraryId}/assets`, { method: "POST", headers: buildRequestHeaders(key), body: formData });
17425
17464
  const payload = await parseEnvelope(response);
17426
17465
  payloads.push(payload);
17427
17466
  const parsed = uploadedAssetsSchema.safeParse(payload);
@@ -17439,7 +17478,7 @@ async function uploadAssetsCommand(context, files, options) {
17439
17478
  `);
17440
17479
  }
17441
17480
  }
17442
- var removeOptionsSchema = inLibraryOptionsSchema.extend({
17481
+ var removeOptionsSchema = exports_external.object({
17443
17482
  yes: exports_external.boolean().default(false)
17444
17483
  });
17445
17484
  async function removeAssetsCommand(context, assetIds, options) {
@@ -17449,6 +17488,8 @@ async function removeAssetsCommand(context, assetIds, options) {
17449
17488
  throw new ValidationError(`At most ${MAX_DELETE_IDS} asset ids are supported per call.`);
17450
17489
  }
17451
17490
  requireApiKey(context);
17491
+ const organizationId = await context.resolveOrg();
17492
+ const libraryId = await context.resolveLibrary();
17452
17493
  if (!options.yes && context.isInteractive) {
17453
17494
  const confirmed = await context.confirm(`Move ${assetIds.length} asset(s) to trash?`);
17454
17495
  if (!confirmed) {
@@ -17458,10 +17499,7 @@ async function removeAssetsCommand(context, assetIds, options) {
17458
17499
  }
17459
17500
  const client = context.clientFactory();
17460
17501
  const result = await unwrapResult(() => client.DELETE("/organizations/{organizationId}/libraries/{libraryId}/assets", {
17461
- params: {
17462
- path: { organizationId: options.org, libraryId: options.library },
17463
- header: VERSION_HEADER
17464
- },
17502
+ params: { path: { organizationId, libraryId }, header: VERSION_HEADER },
17465
17503
  body: { ids: assetIds }
17466
17504
  }));
17467
17505
  if (context.json) {
@@ -17470,15 +17508,17 @@ async function removeAssetsCommand(context, assetIds, options) {
17470
17508
  }
17471
17509
  note(context, `Moved ${assetIds.length} asset(s) to trash. They are recoverable from the library trash.`);
17472
17510
  }
17473
- var describeOptionsSchema = inLibraryOptionsSchema.extend({
17511
+ var describeOptionsSchema = exports_external.object({
17474
17512
  text: exports_external.string().min(1)
17475
17513
  });
17476
17514
  async function describeAssetCommand(context, assetId, options) {
17477
17515
  requireApiKey(context);
17516
+ const organizationId = await context.resolveOrg();
17517
+ const libraryId = await context.resolveLibrary();
17478
17518
  const client = context.clientFactory();
17479
17519
  const asset = await unwrapResult(() => client.PATCH("/organizations/{organizationId}/libraries/{libraryId}/assets/{assetId}/description", {
17480
17520
  params: {
17481
- path: { organizationId: options.org, libraryId: options.library, assetId },
17521
+ path: { organizationId, libraryId, assetId },
17482
17522
  header: VERSION_HEADER
17483
17523
  },
17484
17524
  body: { description: options.text }
@@ -17489,36 +17529,36 @@ async function describeAssetCommand(context, assetId, options) {
17489
17529
  }
17490
17530
  note(context, "Description updated.");
17491
17531
  }
17492
- var transferOptionsSchema = inLibraryOptionsSchema.extend({
17493
- to: exports_external.string().min(1),
17494
- asset: exports_external.array(exports_external.string().min(1)).min(1)
17532
+ var transferOptionsSchema = exports_external.object({
17533
+ to: exports_external.string().min(1)
17495
17534
  });
17496
- async function transferAssetsCommand(context, options) {
17535
+ async function transferAssetsCommand(context, assetIds, options) {
17536
+ if (assetIds.length === 0)
17537
+ throw new UsageError("Provide at least one asset id.");
17497
17538
  requireApiKey(context);
17539
+ const organizationId = await context.resolveOrg();
17540
+ const libraryId = await context.resolveLibrary();
17498
17541
  const client = context.clientFactory();
17499
17542
  const result = await unwrapResult(() => client.POST("/organizations/{organizationId}/libraries/{libraryId}/assets/transfer", {
17500
- params: {
17501
- path: { organizationId: options.org, libraryId: options.library },
17502
- header: VERSION_HEADER
17503
- },
17504
- body: { targetLibraryId: options.to, assetIds: options.asset }
17543
+ params: { path: { organizationId, libraryId }, header: VERSION_HEADER },
17544
+ body: { targetLibraryId: options.to, assetIds }
17505
17545
  }));
17506
17546
  if (context.json) {
17507
17547
  printJson(context, result);
17508
17548
  return;
17509
17549
  }
17510
- note(context, `Transferred ${options.asset.length} asset(s) to library ${options.to}.`);
17550
+ note(context, `Transferred ${assetIds.length} asset(s) to library ${options.to}.`);
17511
17551
  }
17512
17552
  function registerAssetCommands(program2, runAction) {
17513
17553
  const assets = program2.command("assets").description("Work with assets in a library");
17514
- assets.command("ls").description("List assets in a library").requiredOption("--org <organizationId>", "Organization id").requiredOption("--library <libraryId>", "Library id").option("--page <number>", "Page number (1-based)").option("--page-size <number>", "Results per page").option("--tag <tag...>", "Filter by tag (repeatable, up to 5)").action((options, command) => runAction(command, (context) => listAssetsCommand(context, parseOptions(listOptionsSchema, options))));
17515
- assets.command("get").description("Show a single asset").argument("<assetId>", "Asset id").requiredOption("--org <organizationId>", "Organization id").requiredOption("--library <libraryId>", "Library id").action((assetId, options, command) => runAction(command, (context) => getAssetCommand(context, assetId, parseOptions(getOptionsSchema, options))));
17516
- assets.command("search").description("Search assets across the organization").argument("<query>", "Search query").requiredOption("--org <organizationId>", "Organization id").option("--library <libraryId...>", "Restrict to specific libraries (repeatable)").option("--page <number>", "Page number (1-based)").option("--page-size <number>", "Results per page").action((query, options, command) => runAction(command, (context) => searchAssetsCommand(context, query, parseOptions(searchOptionsSchema, options))));
17517
- assets.command("download").description("Download an asset's file").argument("<assetId>", "Asset id").requiredOption("--org <organizationId>", "Organization id").requiredOption("--library <libraryId>", "Library id").option("-o, --output <path>", "Target file path").option("--force", "Overwrite the target file if it exists").action((assetId, options, command) => runAction(command, (context) => downloadAssetCommand(context, assetId, parseOptions(downloadOptionsSchema, options))));
17518
- assets.command("upload").description("Upload local files as assets").argument("<files...>", "Local file paths").requiredOption("--org <organizationId>", "Organization id").requiredOption("--library <libraryId>", "Library id").action((files, options, command) => runAction(command, (context) => uploadAssetsCommand(context, files, parseOptions(uploadOptionsSchema, options))));
17519
- assets.command("rm").description("Move assets to trash (recoverable)").argument("<assetIds...>", "Asset ids").requiredOption("--org <organizationId>", "Organization id").requiredOption("--library <libraryId>", "Library id").option("--yes", "Skip the confirmation prompt").action((assetIds, options, command) => runAction(command, (context) => removeAssetsCommand(context, assetIds, parseOptions(removeOptionsSchema, options))));
17520
- assets.command("describe").description("Set an asset's description").argument("<assetId>", "Asset id").requiredOption("--org <organizationId>", "Organization id").requiredOption("--library <libraryId>", "Library id").requiredOption("--text <description>", "Description text").action((assetId, options, command) => runAction(command, (context) => describeAssetCommand(context, assetId, parseOptions(describeOptionsSchema, options))));
17521
- assets.command("transfer").description("Move assets to another library").requiredOption("--org <organizationId>", "Organization id").requiredOption("--library <libraryId>", "Source library id").requiredOption("--to <libraryId>", "Target library id").requiredOption("--asset <assetId...>", "Asset ids (repeatable)").action((options, command) => runAction(command, (context) => transferAssetsCommand(context, parseOptions(transferOptionsSchema, options))));
17554
+ assets.command("ls").description("List assets in the library").option("--page <number>", "Page number (1-based)").option("--page-size <number>", "Results per page").option("--tag <tag...>", "Filter by tag (repeatable, up to 5)").action((options, command) => runAction(command, (context) => listAssetsCommand(context, parseOptions(listOptionsSchema, options))));
17555
+ assets.command("get").description("Show a single asset").argument("<assetId>", "Asset id").action((assetId, _options, command) => runAction(command, (context) => getAssetCommand(context, assetId)));
17556
+ assets.command("search").description("Search assets across the organization (pass --library to scope to one)").argument("<query>", "Search query").option("--page <number>", "Page number (1-based)").option("--page-size <number>", "Results per page").action((query, options, command) => runAction(command, (context) => searchAssetsCommand(context, query, parseOptions(searchOptionsSchema, options))));
17557
+ assets.command("download").description("Download an asset's file").argument("<assetId>", "Asset id").option("-o, --output <path>", "Target file path").option("--force", "Overwrite the target file if it exists").action((assetId, options, command) => runAction(command, (context) => downloadAssetCommand(context, assetId, parseOptions(downloadOptionsSchema, options))));
17558
+ assets.command("upload").description("Upload local files as assets").argument("<files...>", "Local file paths").action((files, _options, command) => runAction(command, (context) => uploadAssetsCommand(context, files)));
17559
+ assets.command("rm").description("Move assets to trash (recoverable)").argument("<assetIds...>", "Asset ids").option("--yes", "Skip the confirmation prompt").action((assetIds, options, command) => runAction(command, (context) => removeAssetsCommand(context, assetIds, parseOptions(removeOptionsSchema, options))));
17560
+ assets.command("describe").description("Set an asset's description").argument("<assetId>", "Asset id").requiredOption("--text <description>", "Description text").action((assetId, options, command) => runAction(command, (context) => describeAssetCommand(context, assetId, parseOptions(describeOptionsSchema, options))));
17561
+ assets.command("transfer").description("Move assets to another library").argument("<assetIds...>", "Asset ids to move").requiredOption("--to <libraryId>", "Target library id").action((assetIds, options, command) => runAction(command, (context) => transferAssetsCommand(context, assetIds, parseOptions(transferOptionsSchema, options))));
17522
17562
  }
17523
17563
 
17524
17564
  // src/commands/auth.ts
@@ -17526,23 +17566,26 @@ async function fetchViewer(context, apiKey) {
17526
17566
  const client = context.clientFactory({ apiKey });
17527
17567
  return unwrapResult(() => client.GET("/me", { params: { header: VERSION_HEADER } }));
17528
17568
  }
17529
- var loginOptionsSchema = exports_external.object({
17530
- apiKey: exports_external.string().min(1).optional()
17531
- });
17532
- async function loginCommand(context, options) {
17533
- const promptedKey = options.apiKey ?? (context.isInteractive ? await context.promptSecret("Paste your Raster API key") : null);
17534
- if (!promptedKey) {
17569
+ async function loginCommand(context) {
17570
+ const flagKey = context.apiKey?.source === "flag" ? context.apiKey.key : null;
17571
+ const key = flagKey ?? (context.isInteractive ? await context.promptSecret("Paste your Raster API key") : null);
17572
+ if (!key) {
17535
17573
  throw new UsageError("Provide a key with --api-key, or run `raster auth login` in an interactive terminal.");
17536
17574
  }
17537
- const viewer = await fetchViewer(context, promptedKey);
17575
+ const viewer = await fetchViewer(context, key);
17538
17576
  const config2 = await readConfig(context.env);
17539
- await writeConfig({ ...config2, apiKey: promptedKey }, context.env);
17577
+ await writeConfig({
17578
+ ...config2,
17579
+ apiKey: key,
17580
+ organizationId: viewer?.organizationId,
17581
+ libraries: (viewer?.libraries ?? []).filter((library) => typeof library === "string")
17582
+ }, context.env);
17540
17583
  if (context.json) {
17541
17584
  printJson(context, viewer);
17542
17585
  return;
17543
17586
  }
17544
17587
  const organizationLabel = viewer?.organizationName ?? viewer?.organizationId ?? "your organization";
17545
- note(context, `Logged in to ${organizationLabel} as ${maskApiKey(promptedKey)}. Key stored in ${configPath(context.env)}.`);
17588
+ note(context, `Logged in to ${organizationLabel} as ${maskApiKey(key)}. Key stored in ${configPath(context.env)}.`);
17546
17589
  }
17547
17590
  async function logoutCommand(context) {
17548
17591
  const config2 = await readConfig(context.env);
@@ -17599,7 +17642,7 @@ async function whoamiCommand(context) {
17599
17642
  }
17600
17643
  function registerAuthCommands(program2, runAction) {
17601
17644
  const auth = program2.command("auth").description("Manage CLI authentication");
17602
- auth.command("login").description("Validate an API key against the API and store it").option("--api-key <key>", "API key to store (prompts when omitted)").action((options, command) => runAction(command, (context) => loginCommand(context, parseOptions(loginOptionsSchema, options))));
17645
+ auth.command("login").description("Validate an API key against the API and store it (pass --api-key, or run interactively to be prompted)").action((_options, command) => runAction(command, (context) => loginCommand(context)));
17603
17646
  auth.command("logout").description("Remove the stored API key").action((_options, command) => runAction(command, (context) => logoutCommand(context)));
17604
17647
  auth.command("status").description("Show which API key is in use and where it came from").action((_options, command) => runAction(command, (context) => statusCommand(context)));
17605
17648
  program2.command("whoami").description("Show the organization and libraries the API key can access").action((_options, command) => runAction(command, (context) => whoamiCommand(context)));
@@ -17614,16 +17657,16 @@ var LIBRARY_COLUMNS = [
17614
17657
  { header: "TAGS", value: (library) => String((library.tags ?? []).length) }
17615
17658
  ];
17616
17659
  var listOptionsSchema2 = exports_external.object({
17617
- org: exports_external.string().min(1),
17618
17660
  page: exports_external.coerce.number().int().min(1).optional(),
17619
17661
  pageSize: exports_external.coerce.number().int().min(1).optional()
17620
17662
  });
17621
17663
  async function listLibrariesCommand(context, options) {
17622
17664
  requireApiKey(context);
17665
+ const organizationId = await context.resolveOrg();
17623
17666
  const client = context.clientFactory();
17624
17667
  const libraries = await unwrapResult(() => client.GET("/organizations/{organizationId}/libraries", {
17625
17668
  params: {
17626
- path: { organizationId: options.org },
17669
+ path: { organizationId },
17627
17670
  header: VERSION_HEADER,
17628
17671
  query: { page: options.page, pageSize: options.pageSize }
17629
17672
  }
@@ -17635,15 +17678,15 @@ async function listLibrariesCommand(context, options) {
17635
17678
  renderTable(context, libraries, LIBRARY_COLUMNS);
17636
17679
  }
17637
17680
  var createOptionsSchema = exports_external.object({
17638
- org: exports_external.string().min(1),
17639
17681
  name: exports_external.string().min(1),
17640
17682
  slug: exports_external.string().min(1).optional()
17641
17683
  });
17642
17684
  async function createLibraryCommand(context, options) {
17643
17685
  requireApiKey(context);
17686
+ const organizationId = await context.resolveOrg();
17644
17687
  const client = context.clientFactory();
17645
17688
  const library = await unwrapResult(() => client.POST("/organizations/{organizationId}/libraries", {
17646
- params: { path: { organizationId: options.org }, header: VERSION_HEADER },
17689
+ params: { path: { organizationId }, header: VERSION_HEADER },
17647
17690
  body: { name: options.name, slug: options.slug }
17648
17691
  }));
17649
17692
  if (context.json) {
@@ -17657,16 +17700,16 @@ async function createLibraryCommand(context, options) {
17657
17700
  note(context, "Library created.");
17658
17701
  }
17659
17702
  var renameOptionsSchema = exports_external.object({
17660
- org: exports_external.string().min(1),
17661
- library: exports_external.string().min(1),
17662
17703
  name: exports_external.string().min(1)
17663
17704
  });
17664
17705
  async function renameLibraryCommand(context, options) {
17665
17706
  requireApiKey(context);
17707
+ const organizationId = await context.resolveOrg();
17708
+ const libraryId = await context.resolveLibrary();
17666
17709
  const client = context.clientFactory();
17667
17710
  const library = await unwrapResult(() => client.PATCH("/organizations/{organizationId}/libraries/{libraryId}", {
17668
17711
  params: {
17669
- path: { organizationId: options.org, libraryId: options.library },
17712
+ path: { organizationId, libraryId },
17670
17713
  header: VERSION_HEADER
17671
17714
  },
17672
17715
  body: { name: options.name }
@@ -17679,9 +17722,9 @@ async function renameLibraryCommand(context, options) {
17679
17722
  }
17680
17723
  function registerLibraryCommands(program2, runAction) {
17681
17724
  const libraries = program2.command("libraries").description("Work with libraries");
17682
- libraries.command("ls").description("List libraries in an organization").requiredOption("--org <organizationId>", "Organization id").option("--page <number>", "Page number (1-based)").option("--page-size <number>", "Results per page").action((options, command) => runAction(command, (context) => listLibrariesCommand(context, parseOptions(listOptionsSchema2, options))));
17683
- libraries.command("create").description("Create a library").requiredOption("--org <organizationId>", "Organization id").requiredOption("--name <name>", "Library name").option("--slug <slug>", "URL slug for the library").action((options, command) => runAction(command, (context) => createLibraryCommand(context, parseOptions(createOptionsSchema, options))));
17684
- libraries.command("rename").description("Rename a library").requiredOption("--org <organizationId>", "Organization id").requiredOption("--library <libraryId>", "Library id").requiredOption("--name <name>", "New library name").action((options, command) => runAction(command, (context) => renameLibraryCommand(context, parseOptions(renameOptionsSchema, options))));
17725
+ libraries.command("ls").description("List libraries in the organization").option("--page <number>", "Page number (1-based)").option("--page-size <number>", "Results per page").action((options, command) => runAction(command, (context) => listLibrariesCommand(context, parseOptions(listOptionsSchema2, options))));
17726
+ libraries.command("create").description("Create a library").requiredOption("--name <name>", "Library name").option("--slug <slug>", "URL slug for the library").action((options, command) => runAction(command, (context) => createLibraryCommand(context, parseOptions(createOptionsSchema, options))));
17727
+ libraries.command("rename").description("Rename the library (the one the key scopes to, or pass --library)").requiredOption("--name <name>", "New library name").action((options, command) => runAction(command, (context) => renameLibraryCommand(context, parseOptions(renameOptionsSchema, options))));
17685
17728
  }
17686
17729
 
17687
17730
  // src/commands/orgs.ts
@@ -17733,16 +17776,16 @@ var TAG_COLUMNS = [
17733
17776
  { header: "TYPE", value: (tag) => tag.type ?? "" }
17734
17777
  ];
17735
17778
  var listOptionsSchema3 = exports_external.object({
17736
- org: exports_external.string().min(1),
17737
- library: exports_external.string().min(1),
17738
17779
  limit: exports_external.coerce.number().int().min(1).optional()
17739
17780
  });
17740
17781
  async function listTagsCommand(context, options) {
17741
17782
  requireApiKey(context);
17783
+ const organizationId = await context.resolveOrg();
17784
+ const libraryId = await context.resolveLibrary();
17742
17785
  const client = context.clientFactory();
17743
17786
  const tags = await unwrapResult(() => client.GET("/organizations/{organizationId}/libraries/{libraryId}/tags", {
17744
17787
  params: {
17745
- path: { organizationId: options.org, libraryId: options.library },
17788
+ path: { organizationId, libraryId },
17746
17789
  header: VERSION_HEADER,
17747
17790
  query: { limit: options.limit }
17748
17791
  }
@@ -17754,53 +17797,54 @@ async function listTagsCommand(context, options) {
17754
17797
  renderTable(context, tags, TAG_COLUMNS);
17755
17798
  }
17756
17799
  var mutateOptionsSchema = exports_external.object({
17757
- org: exports_external.string().min(1),
17758
- library: exports_external.string().min(1),
17759
- asset: exports_external.array(exports_external.string().min(1)).min(1),
17760
17800
  tag: exports_external.array(exports_external.string().min(1)).min(1)
17761
17801
  });
17762
- async function tagAssetsCommand(context, options) {
17802
+ async function tagAssetsCommand(context, assetIds, options) {
17803
+ if (assetIds.length === 0)
17804
+ throw new UsageError("Provide at least one asset id.");
17763
17805
  requireApiKey(context);
17806
+ const organizationId = await context.resolveOrg();
17807
+ const libraryId = await context.resolveLibrary();
17764
17808
  const client = context.clientFactory();
17765
17809
  const result = await unwrapResult(() => client.POST("/organizations/{organizationId}/libraries/{libraryId}/assets/tag", {
17766
- params: {
17767
- path: { organizationId: options.org, libraryId: options.library },
17768
- header: VERSION_HEADER
17769
- },
17770
- body: { assetIds: options.asset, tags: options.tag }
17810
+ params: { path: { organizationId, libraryId }, header: VERSION_HEADER },
17811
+ body: { assetIds, tags: options.tag }
17771
17812
  }));
17772
17813
  if (context.json) {
17773
17814
  printJson(context, result);
17774
17815
  return;
17775
17816
  }
17776
- note(context, `Tagged ${options.asset.length} asset(s) with ${options.tag.join(", ")}.`);
17817
+ note(context, `Tagged ${assetIds.length} asset(s) with ${options.tag.join(", ")}.`);
17777
17818
  }
17778
- async function untagAssetsCommand(context, options) {
17819
+ async function untagAssetsCommand(context, assetIds, options) {
17820
+ if (assetIds.length === 0)
17821
+ throw new UsageError("Provide at least one asset id.");
17779
17822
  requireApiKey(context);
17823
+ const organizationId = await context.resolveOrg();
17824
+ const libraryId = await context.resolveLibrary();
17780
17825
  const client = context.clientFactory();
17781
17826
  const result = await unwrapResult(() => client.POST("/organizations/{organizationId}/libraries/{libraryId}/assets/untag", {
17782
- params: {
17783
- path: { organizationId: options.org, libraryId: options.library },
17784
- header: VERSION_HEADER
17785
- },
17786
- body: { assetIds: options.asset, tags: options.tag }
17827
+ params: { path: { organizationId, libraryId }, header: VERSION_HEADER },
17828
+ body: { assetIds, tags: options.tag }
17787
17829
  }));
17788
17830
  if (context.json) {
17789
17831
  printJson(context, result);
17790
17832
  return;
17791
17833
  }
17792
- note(context, `Removed ${options.tag.join(", ")} from ${options.asset.length} asset(s).`);
17834
+ note(context, `Removed ${options.tag.join(", ")} from ${assetIds.length} asset(s).`);
17793
17835
  }
17794
17836
  function registerTagCommands(program2, runAction) {
17795
17837
  const tags = program2.command("tags").description("Work with tags in a library");
17796
- tags.command("ls").description("List tags in a library").requiredOption("--org <organizationId>", "Organization id").requiredOption("--library <libraryId>", "Library id").option("--limit <number>", "Maximum number of tags to return").action((options, command) => runAction(command, (context) => listTagsCommand(context, parseOptions(listOptionsSchema3, options))));
17797
- tags.command("add").description("Add tags to assets").requiredOption("--org <organizationId>", "Organization id").requiredOption("--library <libraryId>", "Library id").requiredOption("--asset <assetId...>", "Asset ids (repeatable)").requiredOption("--tag <tag...>", "Tags to add (repeatable)").action((options, command) => runAction(command, (context) => tagAssetsCommand(context, parseOptions(mutateOptionsSchema, options))));
17798
- tags.command("rm").description("Remove tags from assets").requiredOption("--org <organizationId>", "Organization id").requiredOption("--library <libraryId>", "Library id").requiredOption("--asset <assetId...>", "Asset ids (repeatable)").requiredOption("--tag <tag...>", "Tags to remove (repeatable)").action((options, command) => runAction(command, (context) => untagAssetsCommand(context, parseOptions(mutateOptionsSchema, options))));
17838
+ tags.command("ls").description("List tags in the library").option("--limit <number>", "Maximum number of tags to return").action((options, command) => runAction(command, (context) => listTagsCommand(context, parseOptions(listOptionsSchema3, options))));
17839
+ tags.command("add").description("Add tags to assets").argument("<assetIds...>", "Asset ids to tag").requiredOption("--tag <tag...>", "Tags to add (repeatable)").action((assetIds, options, command) => runAction(command, (context) => tagAssetsCommand(context, assetIds, parseOptions(mutateOptionsSchema, options))));
17840
+ tags.command("rm").description("Remove tags from assets").argument("<assetIds...>", "Asset ids to untag").requiredOption("--tag <tag...>", "Tags to remove (repeatable)").action((assetIds, options, command) => runAction(command, (context) => untagAssetsCommand(context, assetIds, parseOptions(mutateOptionsSchema, options))));
17799
17841
  }
17800
17842
 
17801
17843
  // src/index.ts
17802
17844
  var globalOptionsSchema = exports_external.object({
17803
17845
  apiKey: exports_external.string().optional(),
17846
+ org: exports_external.string().optional(),
17847
+ library: exports_external.string().optional(),
17804
17848
  json: exports_external.boolean().optional(),
17805
17849
  verbose: exports_external.boolean().optional()
17806
17850
  });
@@ -17830,7 +17874,7 @@ function exitCodeForCommanderError(error51) {
17830
17874
  return EXIT_CODES.generic;
17831
17875
  }
17832
17876
  var program2 = new Command;
17833
- program2.name("raster").description("Command-line client for the Raster API").version(`${package_default.version} (Api-Version ${API_VERSION})`, "-V, --version", "Print the CLI version and the pinned Api-Version").option("--api-key <key>", "API key (overrides RASTER_API_KEY and the config file)").option("--json", "Print machine-readable JSON to stdout").option("--verbose", "Log requests to stderr (keys masked)").exitOverride();
17877
+ program2.name("raster").description("Command-line client for the Raster API").version(`${package_default.version} (Api-Version ${API_VERSION})`, "-V, --version", "Print the CLI version and the pinned Api-Version").option("--api-key <key>", "API key (overrides RASTER_API_KEY and the config file)").option("--org <organizationId>", "Organization id (derived from the API key when omitted)").option("--library <libraryId>", "Library id (derived from the API key when it has one library)").option("--json", "Print machine-readable JSON to stdout").option("--verbose", "Log requests to stderr (keys masked)").exitOverride();
17834
17878
  registerAuthCommands(program2, runAction);
17835
17879
  registerLibraryCommands(program2, runAction);
17836
17880
  registerAssetCommands(program2, runAction);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raster-app/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Command-line client for the Raster API",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",