@hasna/instructions 0.7.0 → 0.7.2

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 (74) hide show
  1. package/README.md +52 -11
  2. package/dist/chunks/{apply-7svah2qm.js → apply-ps6jjt6k.js} +3 -3
  3. package/dist/chunks/{apply-jp13sa1e.js → apply-wj7wh5km.js} +3 -3
  4. package/dist/chunks/{index-1cnyjnd7.js → index-18j7x3xw.js} +2 -2
  5. package/dist/chunks/{index-3djthcm6.js → index-27az1qb1.js} +28 -1
  6. package/dist/chunks/{index-fwbs6tma.js → index-ape24zc0.js} +2 -2
  7. package/dist/chunks/{index-bzyxvwcd.js → index-ek15201v.js} +2 -2
  8. package/dist/chunks/{index-arrrx0ed.js → index-mvckkf15.js} +39 -20
  9. package/dist/chunks/{index-7m4j8a33.js → index-n6nh6f5r.js} +235 -20
  10. package/dist/chunks/{index-4pfq42t5.js → index-pq18fdbj.js} +146 -32
  11. package/dist/chunks/{index-n3fjspr0.js → index-preyvj8n.js} +28 -1
  12. package/dist/chunks/{index-z3ma4aqk.js → index-y8410ve0.js} +3 -3
  13. package/dist/chunks/{local-5wvv5xv3.js → local-1c1qxzvx.js} +155 -4
  14. package/dist/chunks/{local-zxa5dpf4.js → local-hc67bynj.js} +155 -4
  15. package/dist/chunks/{s3-backup-n2z4fwyh.js → s3-backup-hxe3zpfz.js} +1 -1
  16. package/dist/chunks/{s3-object-store-90tgbh6k.js → s3-object-store-n0s82swt.js} +1 -1
  17. package/dist/chunks/{sync-r8t7zsp3.js → sync-12m3vs6g.js} +4 -4
  18. package/dist/chunks/{sync-2075deve.js → sync-azd9t4p8.js} +4 -4
  19. package/dist/chunks/{template-wgnzdn7h.js → template-e4qrxs8p.js} +1 -1
  20. package/dist/chunks/{template-fsfxpe4p.js → template-nhn124zd.js} +1 -1
  21. package/dist/cli/index.js +860 -181
  22. package/dist/data/config-store.d.ts +22 -6
  23. package/dist/data/config-store.d.ts.map +1 -1
  24. package/dist/db/configs.d.ts +4 -1
  25. package/dist/db/configs.d.ts.map +1 -1
  26. package/dist/db/local.d.ts +1 -1
  27. package/dist/db/local.d.ts.map +1 -1
  28. package/dist/index.d.ts +5 -3
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +940 -134
  31. package/dist/lib/asset-plan.d.ts.map +1 -1
  32. package/dist/lib/codewith-shared-todos-storage-standard.d.ts.map +1 -1
  33. package/dist/lib/compact-output.d.ts +2 -18
  34. package/dist/lib/compact-output.d.ts.map +1 -1
  35. package/dist/lib/dangerous-operation-guard-standard.d.ts.map +1 -1
  36. package/dist/lib/export.d.ts.map +1 -1
  37. package/dist/lib/global-agent-rules-standard.d.ts.map +1 -1
  38. package/dist/lib/global-source-coverage.d.ts +12 -7
  39. package/dist/lib/global-source-coverage.d.ts.map +1 -1
  40. package/dist/lib/import.d.ts.map +1 -1
  41. package/dist/lib/instruction-graph.d.ts +1 -1
  42. package/dist/lib/instruction-graph.d.ts.map +1 -1
  43. package/dist/lib/instruction-source-policy.d.ts +6 -0
  44. package/dist/lib/instruction-source-policy.d.ts.map +1 -0
  45. package/dist/lib/platform-profiles.d.ts.map +1 -1
  46. package/dist/lib/project-dashboard-standard.d.ts.map +1 -1
  47. package/dist/lib/session-apply.d.ts +24 -0
  48. package/dist/lib/session-apply.d.ts.map +1 -1
  49. package/dist/lib/session-refresh.d.ts +30 -0
  50. package/dist/lib/session-refresh.d.ts.map +1 -0
  51. package/dist/lib/session-render.d.ts +34 -1
  52. package/dist/lib/session-render.d.ts.map +1 -1
  53. package/dist/lib/transforms.d.ts +1 -0
  54. package/dist/lib/transforms.d.ts.map +1 -1
  55. package/dist/mcp/index.js +23 -22
  56. package/dist/mcp/server.d.ts.map +1 -1
  57. package/dist/sdk/index.js +46 -3
  58. package/dist/sdk/v1-client.d.ts +10 -3
  59. package/dist/sdk/v1-client.d.ts.map +1 -1
  60. package/dist/sdk/v1.generated.d.ts +52 -2
  61. package/dist/sdk/v1.generated.d.ts.map +1 -1
  62. package/dist/server/index.d.ts +1 -1
  63. package/dist/server/index.d.ts.map +1 -1
  64. package/dist/server/index.js +215 -15
  65. package/dist/server/openapi.d.ts +240 -3
  66. package/dist/server/openapi.d.ts.map +1 -1
  67. package/dist/server/v1.d.ts.map +1 -1
  68. package/dist/storage/cloud-store.d.ts +2 -15
  69. package/dist/storage/cloud-store.d.ts.map +1 -1
  70. package/dist/storage/index.js +39 -20
  71. package/dist/storage/s3-object-store.d.ts.map +1 -1
  72. package/dist/types/index.d.ts +49 -2
  73. package/dist/types/index.d.ts.map +1 -1
  74. package/package.json +3 -3
package/README.md CHANGED
@@ -52,9 +52,13 @@ existing row.
52
52
 
53
53
  ```bash
54
54
  instructions --help
55
- instructions list # compact, paged summary
56
- instructions list --verbose # expanded metadata, still paged
57
- instructions list --json # full machine-readable records
55
+ instructions list # compact, paged summary
56
+ instructions list --verbose # expanded metadata, still paged
57
+ instructions list --json # legacy complete full-record array
58
+ instructions list --json --detail compact --limit 20 # bounded identity envelope
59
+ instructions list --json --detail compact --fields id,slug,name,version
60
+ instructions list --json --detail full --limit 20 # explicit bounded content read
61
+ instructions list --json --detail compact --all # explicit complete metadata read
58
62
  instructions show <slug> # full metadata + content
59
63
  instructions inspect <slug> # alias for show
60
64
  instructions profile resolve
@@ -68,7 +72,9 @@ small. Human output is capped at 20 rows unless you pass `--limit`; use
68
72
  `--cursor` to continue from the next page. Detail is explicit:
69
73
 
70
74
  - `--verbose` expands list rows with descriptions, tags, and paths.
71
- - `--json` preserves full machine-readable records for automation.
75
+ - Legacy `--json` with no modern detail flags preserves the complete full-record array for automation. Explicit `--limit`/`--cursor` now bound that legacy array.
76
+ - `--detail compact` returns a content-free `{ configs, _meta }` identity envelope through the already-deployed `/v1/configs?view=identity` projection. Add `--fields` (including immutable `id`), `--all`, or `--pretty` as needed.
77
+ - `--detail full` is the explicit content-bearing envelope; it is bounded unless `--all` is supplied.
72
78
  - `show`/`inspect` and `snapshot show` print full config or snapshot content.
73
79
 
74
80
  `instructions report --json` emits the stable `schema_version: 1` report
@@ -77,6 +83,24 @@ envelope. Its top-level fields are `configs`, `profiles`, `drift`, `secrets`,
77
83
  `secrets.policy` is `redacted_on_ingest`. Run `instructions report` without
78
84
  `--json` for the human-readable report.
79
85
 
86
+ ### Conditional config updates
87
+
88
+ Read the current config version before preparing an edit, then pass that
89
+ `expected_version` to `updateConfig` (store/public SDK), `update_config` (MCP), or:
90
+
91
+ ```bash
92
+ instructions add ./rule.md --kind reference --name example-rule --update --expected-version 7
93
+ ```
94
+
95
+ Guarded clients use `POST /v1/configs/:id/conditional-update`, which requires
96
+ `expected_version`. The generated SDK also exposes `conditionalUpdateConfig` for
97
+ this operation. An older server returns 404 without mutation; clients never fall
98
+ back to an unconditional update. A version mismatch returns HTTP 409 with code
99
+ `CONFIG_VERSION_CONFLICT`, changing neither the config nor its snapshots. Refresh
100
+ and review the newer version before trying again. Calls without a precondition
101
+ retain their existing behavior. New servers also accept `expected_version` on
102
+ PATCH/PUT; use the dedicated conditional route when server capabilities are unknown.
103
+
80
104
  ## Package-Manager Secret Guard
81
105
 
82
106
  `instructions package-manager-scan` blocks package-manager credential ingress without
@@ -330,6 +354,14 @@ credentials may come from Bun's standard AWS chain (including an ECS task role);
330
354
  explicit static credentials are optional and must be a complete pair. Status
331
355
  never prints credential values or the bucket name.
332
356
 
357
+ The production deployment runs the immutable predeploy backup push and verify
358
+ as direct Bun commands in the exact candidate image. The GitHub runner alone
359
+ parses the push receipt, then supplies the recorded payload and manifest version
360
+ IDs to verification; it also checks the receipt digest and size, COMPLIANCE
361
+ Object Lock retention for both versions, and an exact versioned-payload
362
+ readback. Every backup gate completes before any ECR push, database migration,
363
+ or ECS update.
364
+
333
365
  The public importable surface is available at `@hasna/instructions/storage`.
334
366
 
335
367
  ## Data Directory
@@ -347,13 +379,20 @@ upgrade.
347
379
 
348
380
  `instructions session plan` and `instructions session apply` render
349
381
  OpenIdentities and instruction sources into provider-native files for Claude,
350
- Codex, Cursor, OpenCode, Codewith, Qwen, aicopilot, and Google Antigravity.
382
+ Codex, Cursor, OpenCode, Sumi, Codewith, Qwen, aicopilot, and Google Antigravity.
351
383
  The old Google agent target is removed; Antigravity is the only Google coding
352
384
  agent render target. Antigravity workspace rules are rendered to
353
385
  `.agents/rules/*.md`; its current global rules and MCP files use Google's
354
386
  legacy-named `~/.gemini/GEMINI.md` and `~/.gemini/config/mcp_config.json`
355
387
  paths but remain owned by the `antigravity` target.
356
388
 
389
+ Sumi renders one flattened `AGENTS.md`. Pass its actual config directory from
390
+ `sumi debug paths config` as `--target-home`, or use `--project-root` for
391
+ repository instructions. Global plans return `SUMI_CONFIG_DIR`; project plans
392
+ do not change the global config environment. No OpenCode config or fragment
393
+ imports are written. See [session rendering](docs/session-rendering.md) for
394
+ capability limits and examples.
395
+
357
396
  Qwen Code session rendering writes `QWEN.md` instructional context with
358
397
  `QWEN_HOME` pointing at the rendered profile home. Known config sync also
359
398
  tracks Qwen Code `QWEN.md` and `settings.json` files at `~/.qwen/...` and
@@ -522,14 +561,16 @@ and prior on-box-only evidence must be explicitly reconciled before use.
522
561
 
523
562
  ## Machine-aware Profiles
524
563
 
525
- `instructions init` seeds two platform profiles:
564
+ `instructions init` seeds two empty platform profiles:
526
565
 
527
- - `linux-arm64` for `linux-node-a` / `linux-node-b`
528
- - `macos-arm64` for `macos-node-a` / `macos-node-b`
566
+ - `linux-arm64` for Linux arm64
567
+ - `macos-arm64` for macOS arm64
529
568
 
530
- These profiles resolve machine variables like `{{WORKSPACE_ROOT}}`,
531
- `{{BUN_BIN_DIR}}`, `{{BUN_PATH}}`, and `{{PATH_PREFIX}}`, so synced configs can be
532
- portable across Linux and macOS arm64 machines.
569
+ The public presets contain no fleet hostnames or workspace paths. They resolve
570
+ portable tool variables such as `{{BUN_BIN_DIR}}`, `{{BUN_PATH}}`, and
571
+ `{{PATH_PREFIX}}`. Instruction membership is never bulk-copied from the config
572
+ registry; bind reviewed provider/project/role sources explicitly before compiling
573
+ a prompt profile.
533
574
 
534
575
  They also include the project channel variable used by agent-managed project
535
576
  workflows:
@@ -8,10 +8,10 @@ import {
8
8
  getConfigHome,
9
9
  normalizeTargetPath,
10
10
  previewConfigs
11
- } from "./index-fwbs6tma.js";
12
- import"./index-4pfq42t5.js";
11
+ } from "./index-ape24zc0.js";
12
+ import"./index-pq18fdbj.js";
13
13
  import"./index-35z65jsa.js";
14
- import"./index-3djthcm6.js";
14
+ import"./index-27az1qb1.js";
15
15
  import"./index-nnwp92tk.js";
16
16
  export {
17
17
  previewConfigs,
@@ -8,9 +8,9 @@ import {
8
8
  getConfigHome,
9
9
  normalizeTargetPath,
10
10
  previewConfigs
11
- } from "./index-z3ma4aqk.js";
12
- import"./index-7m4j8a33.js";
13
- import"./index-n3fjspr0.js";
11
+ } from "./index-y8410ve0.js";
12
+ import"./index-n6nh6f5r.js";
13
+ import"./index-preyvj8n.js";
14
14
  import"./index-fpeyzn3n.js";
15
15
  import"./index-bsan4c16.js";
16
16
  export {
@@ -5,7 +5,7 @@ import {
5
5
  expandPath,
6
6
  getConfigHome,
7
7
  normalizeTargetPath
8
- } from "./index-z3ma4aqk.js";
8
+ } from "./index-y8410ve0.js";
9
9
  import {
10
10
  applyTransform,
11
11
  detectMachineContext,
@@ -16,7 +16,7 @@ import {
16
16
  resolveConfigStore,
17
17
  retiredOrUnsupportedAgentReason,
18
18
  templateizeMachineContent
19
- } from "./index-7m4j8a33.js";
19
+ } from "./index-n6nh6f5r.js";
20
20
 
21
21
  // src/lib/sync.ts
22
22
  import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
@@ -4,6 +4,7 @@ var CONFIG_AGENTS = [
4
4
  "claude",
5
5
  "codex",
6
6
  "opencode",
7
+ "sumi",
7
8
  "cursor",
8
9
  "codewith",
9
10
  "aicopilot",
@@ -28,6 +29,32 @@ var ASSET_DESTINATION_STRATEGIES = ["emit-file", "install-local", "install-marke
28
29
  var ASSET_SCOPES = ["global", "project", "session"];
29
30
  var ASSET_UNINSTALL_POLICIES = ["remove-managed", "retain"];
30
31
  var ASSET_ROLLBACK_POLICIES = ["snapshot", "installer-receipt", "none"];
32
+ class InvalidExpectedVersionError extends Error {
33
+ code = "INVALID_EXPECTED_VERSION";
34
+ constructor() {
35
+ super("expected_version must be a positive safe integer");
36
+ this.name = "InvalidExpectedVersionError";
37
+ }
38
+ }
39
+ function validateExpectedConfigVersion(value) {
40
+ if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)) {
41
+ throw new InvalidExpectedVersionError;
42
+ }
43
+ }
44
+
45
+ class ConfigVersionConflictError extends Error {
46
+ config_id;
47
+ expected_version;
48
+ code = "CONFIG_VERSION_CONFLICT";
49
+ status = 409;
50
+ constructor(config_id, expected_version) {
51
+ super(`Config version conflict: ${config_id} no longer has expected version ${expected_version}`);
52
+ this.config_id = config_id;
53
+ this.expected_version = expected_version;
54
+ this.name = "ConfigVersionConflictError";
55
+ }
56
+ }
57
+
31
58
  class ConfigNotFoundError extends Error {
32
59
  constructor(id) {
33
60
  super(`Config not found: ${id}`);
@@ -114,4 +141,4 @@ function isTemplate(content) {
114
141
  return VAR_PATTERN.test(content);
115
142
  }
116
143
 
117
- export { CONFIG_AGENTS, PROFILE_CONFIG_BINDING_SCHEMA, INSTRUCTION_ACTIVATION_MODES, INSTRUCTION_FALLBACKS, PROFILE_ASSET_BINDING_SCHEMA, ASSET_KINDS, ASSET_DESTINATION_STRATEGIES, ASSET_SCOPES, ASSET_UNINSTALL_POLICIES, ASSET_ROLLBACK_POLICIES, ConfigNotFoundError, ProfileNotFoundError, ConfigApplyError, parseTemplateVars, extractTemplateVars, renderTemplate, renderTemplatePreview, isTemplate };
144
+ export { CONFIG_AGENTS, PROFILE_CONFIG_BINDING_SCHEMA, INSTRUCTION_ACTIVATION_MODES, INSTRUCTION_FALLBACKS, PROFILE_ASSET_BINDING_SCHEMA, ASSET_KINDS, ASSET_DESTINATION_STRATEGIES, ASSET_SCOPES, ASSET_UNINSTALL_POLICIES, ASSET_ROLLBACK_POLICIES, validateExpectedConfigVersion, ConfigVersionConflictError, ConfigNotFoundError, ProfileNotFoundError, ConfigApplyError, parseTemplateVars, extractTemplateVars, renderTemplate, renderTemplatePreview, isTemplate };
@@ -15,7 +15,7 @@ import {
15
15
  resolveConfigStore,
16
16
  resolveProfileVariables,
17
17
  stampCursorGlobalAuthorityMarker
18
- } from "./index-4pfq42t5.js";
18
+ } from "./index-pq18fdbj.js";
19
19
  import {
20
20
  isSecretVarName,
21
21
  redactFormatForTarget,
@@ -24,7 +24,7 @@ import {
24
24
  import {
25
25
  ConfigApplyError,
26
26
  parseTemplateVars
27
- } from "./index-3djthcm6.js";
27
+ } from "./index-27az1qb1.js";
28
28
 
29
29
  // src/lib/apply.ts
30
30
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "fs";
@@ -5,7 +5,7 @@ import {
5
5
  expandPath,
6
6
  getConfigHome,
7
7
  normalizeTargetPath
8
- } from "./index-fwbs6tma.js";
8
+ } from "./index-ape24zc0.js";
9
9
  import {
10
10
  applyTransform,
11
11
  detectMachineContext,
@@ -13,7 +13,7 @@ import {
13
13
  resolveConfigStore,
14
14
  retiredOrUnsupportedAgentReason,
15
15
  templateizeMachineContent
16
- } from "./index-4pfq42t5.js";
16
+ } from "./index-pq18fdbj.js";
17
17
  import {
18
18
  isSecretVarName,
19
19
  redactContent,
@@ -73,18 +73,20 @@ function createInstructionsS3ObjectStore(config, createClient = (options) => new
73
73
  return {
74
74
  async putIfAbsent(key, bytes, options) {
75
75
  assertSafeInstructionsObjectKey(key);
76
+ let credentials;
77
+ try {
78
+ credentials = resolveRequestCredentials(config);
79
+ } catch {
80
+ throw new Error("Instructions S3 conditional object creation failed");
81
+ }
76
82
  for (let attempt = 1;attempt <= CONDITIONAL_CREATE_ATTEMPTS; attempt += 1) {
77
83
  let response;
78
84
  const controller = new AbortController;
79
85
  try {
80
- const url = client.presign(key, { method: "PUT", expiresIn: 60 });
81
- response = await runWithDeadline(() => requestFetch(url, {
86
+ const signed = signConditionalCreateRequest(config, key, bytes, options.contentType, credentials, new Date);
87
+ response = await runWithDeadline(() => requestFetch(signed.url, {
82
88
  method: "PUT",
83
- headers: {
84
- "content-type": options.contentType,
85
- "content-md5": createHash("md5").update(bytes).digest("base64"),
86
- "if-none-match": "*"
87
- },
89
+ headers: signed.headers,
88
90
  body: Uint8Array.from(bytes),
89
91
  redirect: "error",
90
92
  signal: controller.signal
@@ -220,7 +222,7 @@ function resolveRequestCredentials(config) {
220
222
  const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY?.trim();
221
223
  const sessionToken = process.env.AWS_SESSION_TOKEN?.trim();
222
224
  if (!accessKeyId || !secretAccessKey) {
223
- throw new Error("Instructions S3 exact-version reads require explicit or AWS environment credentials");
225
+ throw new Error("Instructions S3 signed requests require explicit or AWS environment credentials");
224
226
  }
225
227
  return { accessKeyId, secretAccessKey, ...sessionToken ? { sessionToken } : {} };
226
228
  }
@@ -235,25 +237,43 @@ function signVersionedRequest(config, key, versionId, method, credentials, now)
235
237
  "x-amz-date": amzDate,
236
238
  ...credentials.sessionToken ? { "x-amz-security-token": credentials.sessionToken } : {}
237
239
  });
238
- const signedHeaderNames = [
239
- "host",
240
- "x-amz-content-sha256",
241
- "x-amz-date",
242
- ...credentials.sessionToken ? ["x-amz-security-token"] : []
243
- ].sort();
244
- const canonicalHeaders = signedHeaderNames.map((name) => `${name}:${headers.get(name).trim()}
245
- `).join("");
246
240
  const canonicalQuery = `versionId=${awsEncode(versionId)}`;
241
+ authorizeRequest(method, url, canonicalQuery, headers, EMPTY_SHA256, credentials, config.region, shortDate, amzDate);
242
+ return { url: url.toString(), headers };
243
+ }
244
+ function signConditionalCreateRequest(config, key, bytes, contentType, credentials, now) {
245
+ const url = buildObjectUrl(config, key);
246
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
247
+ const shortDate = amzDate.slice(0, 8);
248
+ const payloadHash = createHash("sha256").update(bytes).digest("hex");
249
+ const headers = new Headers({
250
+ "content-md5": createHash("md5").update(bytes).digest("base64"),
251
+ "content-type": contentType,
252
+ host: url.host,
253
+ "if-none-match": "*",
254
+ "x-amz-content-sha256": payloadHash,
255
+ "x-amz-date": amzDate,
256
+ ...credentials.sessionToken ? { "x-amz-security-token": credentials.sessionToken } : {}
257
+ });
258
+ authorizeRequest("PUT", url, "", headers, payloadHash, credentials, config.region, shortDate, amzDate);
259
+ return { url: url.toString(), headers };
260
+ }
261
+ function authorizeRequest(method, url, canonicalQuery, headers, payloadHash, credentials, region, shortDate, amzDate) {
262
+ const signedHeaderNames = [];
263
+ headers.forEach((_value, name) => signedHeaderNames.push(name));
264
+ signedHeaderNames.sort();
265
+ const canonicalHeaders = signedHeaderNames.map((name) => `${name}:${headers.get(name).trim().replace(/\s+/g, " ")}
266
+ `).join("");
247
267
  const canonicalRequest = [
248
268
  method,
249
269
  url.pathname,
250
270
  canonicalQuery,
251
271
  canonicalHeaders,
252
272
  signedHeaderNames.join(";"),
253
- EMPTY_SHA256
273
+ payloadHash
254
274
  ].join(`
255
275
  `);
256
- const scope = `${shortDate}/${config.region}/s3/aws4_request`;
276
+ const scope = `${shortDate}/${region}/s3/aws4_request`;
257
277
  const stringToSign = [
258
278
  "AWS4-HMAC-SHA256",
259
279
  amzDate,
@@ -262,12 +282,11 @@ function signVersionedRequest(config, key, versionId, method, credentials, now)
262
282
  ].join(`
263
283
  `);
264
284
  const dateKey = hmac(`AWS4${credentials.secretAccessKey}`, shortDate);
265
- const regionKey = hmac(dateKey, config.region);
285
+ const regionKey = hmac(dateKey, region);
266
286
  const serviceKey = hmac(regionKey, "s3");
267
287
  const signingKey = hmac(serviceKey, "aws4_request");
268
288
  const signature = createHmac("sha256", signingKey).update(stringToSign).digest("hex");
269
289
  headers.set("authorization", `AWS4-HMAC-SHA256 Credential=${credentials.accessKeyId}/${scope}, SignedHeaders=${signedHeaderNames.join(";")}, Signature=${signature}`);
270
- return { url: url.toString(), headers };
271
290
  }
272
291
  function buildObjectUrl(config, key) {
273
292
  const encodedKey = key.split("/").map(awsEncode).join("/");