@hasna/instructions 0.6.1 → 0.7.1

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 (85) hide show
  1. package/README.md +141 -10
  2. package/dist/chunks/{apply-ynfn1av7.js → apply-c3sd4agz.js} +5 -3
  3. package/dist/chunks/{apply-sfzja1f3.js → apply-pnn7a3y7.js} +6 -3
  4. package/dist/chunks/database-wstmr56z.js +23 -0
  5. package/dist/chunks/{index-pkscjnj5.js → index-3djthcm6.js} +0 -1
  6. package/dist/chunks/index-5d7zdftc.js +186 -0
  7. package/dist/chunks/{index-1hpwskwd.js → index-6jncnnw7.js} +318 -329
  8. package/dist/chunks/index-8rb7ng2y.js +148 -0
  9. package/dist/chunks/{index-v7dkb384.js → index-aj66f6xr.js} +243 -100
  10. package/dist/chunks/index-fpeyzn3n.js +186 -0
  11. package/dist/chunks/{index-9p9gn7gh.js → index-jg8ptcwh.js} +5 -7
  12. package/dist/chunks/index-mvckkf15.js +363 -0
  13. package/dist/chunks/index-n3fjspr0.js +119 -0
  14. package/dist/chunks/{index-85n5wnd8.js → index-pdpg8a44.js} +5 -7
  15. package/dist/chunks/{index-e9epbsvs.js → index-r7nfef2g.js} +15 -4
  16. package/dist/chunks/{index-axhwg61p.js → index-ypcef3a2.js} +15 -4
  17. package/dist/chunks/legacy-store-migration-z2m5yzyk.js +322 -0
  18. package/dist/chunks/{local-a9nen65z.js → local-0a9gja58.js} +142 -2
  19. package/dist/chunks/{local-0d8qrc92.js → local-969ye538.js} +153 -188
  20. package/dist/chunks/s3-backup-hxe3zpfz.js +240 -0
  21. package/dist/chunks/s3-config-hhght91k.js +14 -0
  22. package/dist/chunks/s3-object-store-n0s82swt.js +14 -0
  23. package/dist/chunks/{sync-vy1yjvs6.js → sync-q5hvwq6e.js} +4 -4
  24. package/dist/chunks/{sync-m46fc257.js → sync-xtkbm5br.js} +5 -4
  25. package/dist/chunks/{template-02wjvv05.js → template-fsfxpe4p.js} +1 -1
  26. package/dist/chunks/{template-ke972qqe.js → template-wgnzdn7h.js} +1 -1
  27. package/dist/cli/index.js +1236 -256
  28. package/dist/data/config-store.d.ts +10 -1
  29. package/dist/data/config-store.d.ts.map +1 -1
  30. package/dist/db/configs.d.ts +4 -1
  31. package/dist/db/configs.d.ts.map +1 -1
  32. package/dist/db/local.d.ts +1 -1
  33. package/dist/db/local.d.ts.map +1 -1
  34. package/dist/index.js +1086 -215
  35. package/dist/lib/apply.d.ts +7 -0
  36. package/dist/lib/apply.d.ts.map +1 -1
  37. package/dist/lib/compact-output.d.ts +3 -18
  38. package/dist/lib/compact-output.d.ts.map +1 -1
  39. package/dist/lib/export.d.ts +22 -3
  40. package/dist/lib/export.d.ts.map +1 -1
  41. package/dist/lib/import.d.ts +18 -0
  42. package/dist/lib/import.d.ts.map +1 -1
  43. package/dist/lib/legacy-store-migration.d.ts +39 -0
  44. package/dist/lib/legacy-store-migration.d.ts.map +1 -0
  45. package/dist/lib/managed-skill-runtimes.d.ts +1 -0
  46. package/dist/lib/managed-skill-runtimes.d.ts.map +1 -1
  47. package/dist/lib/sync-dir.d.ts.map +1 -1
  48. package/dist/mcp/index.js +41 -30
  49. package/dist/mcp/server.d.ts.map +1 -1
  50. package/dist/sdk/index.d.ts +2 -0
  51. package/dist/sdk/index.d.ts.map +1 -1
  52. package/dist/sdk/index.js +229 -10
  53. package/dist/sdk/resolve.d.ts +1 -1
  54. package/dist/sdk/resolve.d.ts.map +1 -1
  55. package/dist/sdk/v1-client.d.ts +58 -0
  56. package/dist/sdk/v1-client.d.ts.map +1 -0
  57. package/dist/sdk/v1.generated.d.ts +242 -21
  58. package/dist/sdk/v1.generated.d.ts.map +1 -1
  59. package/dist/server/cloud.d.ts.map +1 -1
  60. package/dist/server/index.d.ts +13 -5
  61. package/dist/server/index.d.ts.map +1 -1
  62. package/dist/server/index.js +1261 -427
  63. package/dist/server/openapi.d.ts +1379 -93
  64. package/dist/server/openapi.d.ts.map +1 -1
  65. package/dist/server/v1.d.ts +25 -1
  66. package/dist/server/v1.d.ts.map +1 -1
  67. package/dist/storage/cloud-store.d.ts +45 -2
  68. package/dist/storage/cloud-store.d.ts.map +1 -1
  69. package/dist/storage/index.d.ts +7 -0
  70. package/dist/storage/index.d.ts.map +1 -0
  71. package/dist/storage/index.js +744 -0
  72. package/dist/storage/s3-backup.d.ts +76 -0
  73. package/dist/storage/s3-backup.d.ts.map +1 -0
  74. package/dist/storage/s3-config.d.ts +46 -0
  75. package/dist/storage/s3-config.d.ts.map +1 -0
  76. package/dist/storage/s3-object-store.d.ts +70 -0
  77. package/dist/storage/s3-object-store.d.ts.map +1 -0
  78. package/dist/types/index.d.ts +98 -3
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/hasna.contract.json +110 -0
  81. package/migrations/0001_instructions.sql +63 -0
  82. package/migrations/0002_api_keys.sql +20 -0
  83. package/migrations/0003_idempotency_receipts.sql +24 -0
  84. package/package.json +24 -13
  85. package/assets/skills/inbox/SKILL.md +0 -86
@@ -0,0 +1,148 @@
1
+ // @bun
2
+ // src/storage/s3-config.ts
3
+ var INSTRUCTIONS_S3_ENV = {
4
+ bucket: "HASNA_INSTRUCTIONS_S3_BUCKET",
5
+ prefix: "HASNA_INSTRUCTIONS_S3_PREFIX",
6
+ region: "HASNA_INSTRUCTIONS_AWS_REGION",
7
+ endpoint: "HASNA_INSTRUCTIONS_S3_ENDPOINT",
8
+ forcePathStyle: "HASNA_INSTRUCTIONS_S3_FORCE_PATH_STYLE",
9
+ accessKeyId: "HASNA_INSTRUCTIONS_S3_ACCESS_KEY_ID",
10
+ secretAccessKey: "HASNA_INSTRUCTIONS_S3_SECRET_ACCESS_KEY",
11
+ sessionToken: "HASNA_INSTRUCTIONS_S3_SESSION_TOKEN"
12
+ };
13
+ var INSTRUCTIONS_S3_ALIAS_ENV = {
14
+ bucket: "INSTRUCTIONS_S3_BUCKET",
15
+ prefix: "INSTRUCTIONS_S3_PREFIX",
16
+ region: "INSTRUCTIONS_AWS_REGION",
17
+ endpoint: "INSTRUCTIONS_S3_ENDPOINT",
18
+ forcePathStyle: "INSTRUCTIONS_S3_FORCE_PATH_STYLE",
19
+ accessKeyId: "INSTRUCTIONS_S3_ACCESS_KEY_ID",
20
+ secretAccessKey: "INSTRUCTIONS_S3_SECRET_ACCESS_KEY",
21
+ sessionToken: "INSTRUCTIONS_S3_SESSION_TOKEN"
22
+ };
23
+ var BUCKET_PATTERN = /^(?!xn--)[a-z0-9](?:[a-z0-9.-]{1,61}[a-z0-9])?$/;
24
+ var REGION_PATTERN = /^[a-z]{2}(?:-gov)?-[a-z0-9-]+-\d$/;
25
+ var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
26
+ function loadInstructionsS3Config(env = process.env) {
27
+ const bucket = readEnv(env, "bucket");
28
+ const configuredWithoutBucket = Object.keys(INSTRUCTIONS_S3_ENV).filter((key) => key !== "bucket").some((key) => readEnv(env, key) !== undefined);
29
+ if (!bucket) {
30
+ if (configuredWithoutBucket) {
31
+ throw new Error(`${INSTRUCTIONS_S3_ENV.bucket} is required when any Instructions S3 setting is configured`);
32
+ }
33
+ return;
34
+ }
35
+ validateBucket(bucket);
36
+ const prefix = normalizeInstructionsS3Prefix(readEnv(env, "prefix") ?? "instructions/");
37
+ const region = readEnv(env, "region") ?? "us-east-1";
38
+ validateRegion(region);
39
+ const endpoint = normalizeEndpoint(readEnv(env, "endpoint"));
40
+ const forcePathStyle = parseBoolean(readEnv(env, "forcePathStyle"), false);
41
+ const accessKeyId = readEnv(env, "accessKeyId");
42
+ const secretAccessKey = readEnv(env, "secretAccessKey");
43
+ const sessionToken = readEnv(env, "sessionToken");
44
+ if (Boolean(accessKeyId) !== Boolean(secretAccessKey)) {
45
+ throw new Error(`${INSTRUCTIONS_S3_ENV.accessKeyId} and ${INSTRUCTIONS_S3_ENV.secretAccessKey} must be configured together`);
46
+ }
47
+ if (sessionToken && (!accessKeyId || !secretAccessKey)) {
48
+ throw new Error(`${INSTRUCTIONS_S3_ENV.sessionToken} requires a complete static credential pair`);
49
+ }
50
+ return {
51
+ provider: "s3",
52
+ bucket,
53
+ prefix,
54
+ region,
55
+ ...endpoint ? { endpoint } : {},
56
+ forcePathStyle,
57
+ ...accessKeyId && secretAccessKey ? {
58
+ credentials: {
59
+ accessKeyId,
60
+ secretAccessKey,
61
+ ...sessionToken ? { sessionToken } : {}
62
+ }
63
+ } : {}
64
+ };
65
+ }
66
+ function normalizeInstructionsS3Prefix(value) {
67
+ const trimmed = value.trim();
68
+ if (!trimmed || CONTROL_CHARACTERS.test(trimmed) || trimmed.includes("\\")) {
69
+ throw new Error("Instructions S3 prefix is invalid");
70
+ }
71
+ const withoutEdges = trimmed.replace(/^\/+|\/+$/g, "");
72
+ if (!withoutEdges || withoutEdges.includes("//")) {
73
+ throw new Error("Instructions S3 prefix is invalid");
74
+ }
75
+ const segments = withoutEdges.split("/");
76
+ for (const segment of segments) {
77
+ let decoded;
78
+ try {
79
+ decoded = decodeURIComponent(segment);
80
+ } catch {
81
+ throw new Error("Instructions S3 prefix contains invalid percent encoding");
82
+ }
83
+ if (!decoded || decoded === "." || decoded === ".." || decoded.includes("/") || decoded.includes("\\") || CONTROL_CHARACTERS.test(decoded)) {
84
+ throw new Error("Instructions S3 prefix contains an unsafe path segment");
85
+ }
86
+ }
87
+ return `${segments.join("/")}/`;
88
+ }
89
+ function readEnv(env, key) {
90
+ const canonical = INSTRUCTIONS_S3_ENV[key];
91
+ if (Object.prototype.hasOwnProperty.call(env, canonical))
92
+ return clean(env[canonical]);
93
+ return clean(env[INSTRUCTIONS_S3_ALIAS_ENV[key]]);
94
+ }
95
+ function clean(value) {
96
+ const trimmed = value?.trim();
97
+ return trimmed ? trimmed : undefined;
98
+ }
99
+ function parseBoolean(value, fallback) {
100
+ if (!value)
101
+ return fallback;
102
+ switch (value.toLowerCase()) {
103
+ case "1":
104
+ case "true":
105
+ case "yes":
106
+ case "on":
107
+ return true;
108
+ case "0":
109
+ case "false":
110
+ case "no":
111
+ case "off":
112
+ return false;
113
+ default:
114
+ throw new Error("Instructions S3 boolean setting is invalid");
115
+ }
116
+ }
117
+ function validateBucket(bucket) {
118
+ if (bucket.length < 3 || bucket.length > 63 || !BUCKET_PATTERN.test(bucket) || bucket.includes("..") || bucket.includes(".-") || bucket.includes("-.") || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(bucket)) {
119
+ throw new Error("Instructions S3 bucket is invalid");
120
+ }
121
+ }
122
+ function validateRegion(region) {
123
+ if (!REGION_PATTERN.test(region))
124
+ throw new Error("Instructions AWS region is invalid");
125
+ }
126
+ function normalizeEndpoint(value) {
127
+ if (!value)
128
+ return;
129
+ let endpoint;
130
+ try {
131
+ endpoint = new URL(value);
132
+ } catch {
133
+ throw new Error("Instructions S3 endpoint is invalid");
134
+ }
135
+ if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {
136
+ throw new Error("Instructions S3 endpoint must not contain credentials, query parameters, or fragments");
137
+ }
138
+ if (endpoint.pathname !== "/") {
139
+ throw new Error("Instructions S3 endpoint must be an origin without a path");
140
+ }
141
+ const localHttp = endpoint.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(endpoint.hostname);
142
+ if (endpoint.protocol !== "https:" && !localHttp) {
143
+ throw new Error("Instructions S3 endpoint must use HTTPS (HTTP is allowed only for loopback development)");
144
+ }
145
+ return endpoint.origin;
146
+ }
147
+
148
+ export { INSTRUCTIONS_S3_ENV, INSTRUCTIONS_S3_ALIAS_ENV, loadInstructionsS3Config, normalizeInstructionsS3Prefix };
@@ -15,7 +15,7 @@ import {
15
15
  isTemplate,
16
16
  renderTemplate,
17
17
  renderTemplatePreview
18
- } from "./index-pkscjnj5.js";
18
+ } from "./index-3djthcm6.js";
19
19
  import {
20
20
  __export,
21
21
  __require
@@ -203,6 +203,7 @@ import { randomUUID } from "crypto";
203
203
  // src/lib/compact-output.ts
204
204
  var DEFAULT_LIST_LIMIT = 20;
205
205
  var MAX_LIST_LIMIT = 100;
206
+ var MAX_LIST_CURSOR = 1e5;
206
207
  function parseLimit(value, fallback = DEFAULT_LIST_LIMIT, max = MAX_LIST_LIMIT) {
207
208
  const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
208
209
  if (!Number.isFinite(parsed) || parsed <= 0)
@@ -213,7 +214,7 @@ function parseCursor(value) {
213
214
  const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
214
215
  if (!Number.isFinite(parsed) || parsed < 0)
215
216
  return 0;
216
- return Math.floor(parsed);
217
+ return Math.min(Math.floor(parsed), MAX_LIST_CURSOR);
217
218
  }
218
219
  function paginate(items, opts = {}) {
219
220
  const limit = parseLimit(opts.limit, opts.defaultLimit ?? DEFAULT_LIST_LIMIT, opts.maxLimit ?? MAX_LIST_LIMIT);
@@ -243,28 +244,6 @@ function truncateText(value, max = 80) {
243
244
  return text.slice(0, max);
244
245
  return `${text.slice(0, max - 3)}...`;
245
246
  }
246
- function summarizeConfig(config, opts = {}) {
247
- const summary = {
248
- id: config.id,
249
- slug: config.slug,
250
- name: config.name,
251
- category: config.category,
252
- agent: config.agent,
253
- kind: config.kind,
254
- format: config.format,
255
- target_path: config.target_path,
256
- output_count: config.outputs.length,
257
- version: config.version,
258
- is_template: config.is_template
259
- };
260
- if (opts.verbose) {
261
- summary.updated_at = config.updated_at;
262
- summary.description = config.description;
263
- summary.tags = config.tags;
264
- summary.outputs = config.outputs;
265
- }
266
- return summary;
267
- }
268
247
  function summarizeProfile(profile, opts = {}) {
269
248
  const selectorCount = (profile.selectors.os?.length ?? 0) + (profile.selectors.arch?.length ?? 0) + (profile.selectors.hostnames?.length ?? 0);
270
249
  const summary = {
@@ -720,7 +699,7 @@ function optionalString(value, label) {
720
699
  return requiredString(value, label);
721
700
  }
722
701
 
723
- // ../contracts/dist/client/transport.js
702
+ // ../../node_modules/.bun/@hasna+contracts@1.0.2+e8014c875821e0be/node_modules/@hasna/contracts/dist/client/transport.js
724
703
  import { createRequire } from "module";
725
704
  function envToken(name) {
726
705
  return name.toUpperCase().replace(/-/g, "_");
@@ -754,7 +733,7 @@ var AUTHORITY_OVERRIDE_HEADERS = new Set([
754
733
  "x-original-host"
755
734
  ]);
756
735
 
757
- // ../contracts/dist/client/storage.js
736
+ // ../../node_modules/.bun/@hasna+contracts@1.0.2+e8014c875821e0be/node_modules/@hasna/contracts/dist/client/storage.js
758
737
  import { isIP } from "net";
759
738
  import { spawnSync } from "child_process";
760
739
  import { closeSync, fstatSync, openSync, readFileSync } from "fs";
@@ -2032,11 +2011,11 @@ function announceLocalInstructionsMode(write = (line) => process.stderr.write(`$
2032
2011
  }
2033
2012
 
2034
2013
  // src/data/config-store.ts
2035
- function parseBoundedPagePayload(value, label) {
2014
+ function parseBoundedPagePayload(value, label, expected) {
2036
2015
  const page = value;
2037
2016
  const consumed = Number(page?.cursor) + (page?.items?.length ?? 0);
2038
2017
  const complete = Boolean(page && Number.isSafeInteger(page.total) && consumed >= Number(page.total));
2039
- if (!page || !Array.isArray(page.items) || !Number.isSafeInteger(page.total) || Number(page.total) < 0 || !Number.isSafeInteger(page.limit) || Number(page.limit) < 1 || !Number.isSafeInteger(page.cursor) || Number(page.cursor) < 0 || page.items.length > Number(page.limit) || typeof page.has_more !== "boolean" || typeof page.complete !== "boolean" || page.truncated !== false || page.next_cursor !== null && !Number.isSafeInteger(page.next_cursor) || page.complete !== complete || page.has_more !== !complete || page.next_cursor !== (complete ? null : consumed)) {
2018
+ if (!page || !Array.isArray(page.items) || !Number.isSafeInteger(page.total) || Number(page.total) < 0 || !Number.isSafeInteger(page.limit) || Number(page.limit) < 1 || !Number.isSafeInteger(page.cursor) || Number(page.cursor) < 0 || page.items.length > Number(page.limit) || typeof page.has_more !== "boolean" || typeof page.complete !== "boolean" || page.truncated !== false || page.next_cursor !== null && !Number.isSafeInteger(page.next_cursor) || page.complete !== complete || page.has_more !== !complete || page.next_cursor !== (complete ? null : consumed) || expected !== undefined && Number(page.cursor) !== expected.cursor) {
2040
2019
  throw new Error(`${label} returned an invalid or truncated bounded-read envelope`);
2041
2020
  }
2042
2021
  return {
@@ -2048,7 +2027,7 @@ function parseBoundedOrLegacyPage(value, legacyItems, options, label) {
2048
2027
  if (value && typeof value === "object") {
2049
2028
  const candidate = value;
2050
2029
  if ("items" in candidate || "total" in candidate || "complete" in candidate || "truncated" in candidate || "next_cursor" in candidate) {
2051
- return parseBoundedPagePayload(value, label);
2030
+ return parseBoundedPagePayload(value, label, normalizeBoundedReadOptions(options));
2052
2031
  }
2053
2032
  }
2054
2033
  if (!Array.isArray(legacyItems)) {
@@ -2058,13 +2037,85 @@ function parseBoundedOrLegacyPage(value, legacyItems, options, label) {
2058
2037
  const page = boundedReadPage(legacyItems.slice(normalized.cursor, normalized.cursor + normalized.limit), legacyItems.length, normalized);
2059
2038
  return { ...page, source_bounded: false };
2060
2039
  }
2040
+ function projectBoundedPage(page, project) {
2041
+ return {
2042
+ items: page.items.map(project),
2043
+ total: page.total,
2044
+ limit: page.limit,
2045
+ cursor: page.cursor,
2046
+ next_cursor: page.next_cursor,
2047
+ has_more: page.has_more,
2048
+ complete: page.complete,
2049
+ truncated: page.truncated,
2050
+ source_bounded: page.source_bounded
2051
+ };
2052
+ }
2053
+
2054
+ class CollectionChangedWhilePagingError extends Error {
2055
+ constructor(label, detail) {
2056
+ super(`${label} changed while paging: ${detail}`);
2057
+ this.name = "CollectionChangedWhilePagingError";
2058
+ }
2059
+ }
2060
+ var COLLECTION_READ_ATTEMPTS = 2;
2061
+ async function aggregateBoundedCollection(label, readPage, identity, options = {}) {
2062
+ let lastError = null;
2063
+ for (let attempt = 0;attempt < COLLECTION_READ_ATTEMPTS; attempt += 1) {
2064
+ try {
2065
+ const items = [];
2066
+ const seen = new Set;
2067
+ let expectedTotal = null;
2068
+ let cursor = 0;
2069
+ let previousIdentity = null;
2070
+ while (true) {
2071
+ const page = await readPage(cursor);
2072
+ if (page.cursor !== cursor) {
2073
+ throw new CollectionChangedWhilePagingError(label, `server returned cursor ${page.cursor} for requested cursor ${cursor}`);
2074
+ }
2075
+ if (expectedTotal === null)
2076
+ expectedTotal = page.total;
2077
+ else if (page.total !== expectedTotal) {
2078
+ throw new CollectionChangedWhilePagingError(label, `total changed from ${expectedTotal} to ${page.total}`);
2079
+ }
2080
+ for (const item of page.items) {
2081
+ const key = identity(item);
2082
+ if (!key)
2083
+ throw new CollectionChangedWhilePagingError(label, "an item had no stable identity");
2084
+ if (seen.has(key))
2085
+ throw new CollectionChangedWhilePagingError(label, `duplicate identity ${key}`);
2086
+ if (options.requireAscendingIdentity && page.source_bounded && previousIdentity !== null && key <= previousIdentity) {
2087
+ throw new CollectionChangedWhilePagingError(label, `identity order was not strictly increasing at ${key}`);
2088
+ }
2089
+ seen.add(key);
2090
+ previousIdentity = key;
2091
+ items.push(item);
2092
+ }
2093
+ if (page.complete) {
2094
+ if (items.length !== expectedTotal) {
2095
+ throw new CollectionChangedWhilePagingError(label, `received ${items.length} unique rows for total ${expectedTotal}`);
2096
+ }
2097
+ return items;
2098
+ }
2099
+ if (page.next_cursor === null || page.next_cursor <= cursor) {
2100
+ throw new CollectionChangedWhilePagingError(label, `cursor did not advance from ${cursor}`);
2101
+ }
2102
+ cursor = page.next_cursor;
2103
+ }
2104
+ } catch (error) {
2105
+ if (!(error instanceof CollectionChangedWhilePagingError))
2106
+ throw error;
2107
+ lastError = error;
2108
+ }
2109
+ }
2110
+ throw lastError ?? new CollectionChangedWhilePagingError(label, "read did not stabilize");
2111
+ }
2061
2112
  function isNotFoundHttpError2(err) {
2062
2113
  return typeof err === "object" && err !== null && err.name === "HasnaHttpError" && err.status === 404;
2063
2114
  }
2064
2115
  var localStoreModulePromise = null;
2065
2116
  function localStoreModule() {
2066
2117
  if (!localStoreModulePromise)
2067
- localStoreModulePromise = import("./local-a9nen65z.js");
2118
+ localStoreModulePromise = import("./local-0a9gja58.js");
2068
2119
  return localStoreModulePromise;
2069
2120
  }
2070
2121
 
@@ -2078,6 +2129,15 @@ class LocalConfigStore {
2078
2129
  async listConfigs(filter) {
2079
2130
  return (await localStoreModule()).listConfigs(filter, this.db);
2080
2131
  }
2132
+ async listConfigsPage(filter = {}, options = {}) {
2133
+ return (await localStoreModule()).listConfigsPage(filter, options, this.db);
2134
+ }
2135
+ async listConfigIdentitiesPage(filter = {}, options = {}) {
2136
+ return (await localStoreModule()).listConfigIdentitiesPage(filter, options, this.db);
2137
+ }
2138
+ async listConfigSummariesPage(filter = {}, options = {}) {
2139
+ return (await localStoreModule()).listConfigSummariesPage(filter, options, this.db);
2140
+ }
2081
2141
  async getConfig(idOrSlug) {
2082
2142
  return (await localStoreModule()).getConfig(idOrSlug, this.db);
2083
2143
  }
@@ -2112,15 +2172,7 @@ class LocalConfigStore {
2112
2172
  return (await localStoreModule()).pruneSnapshots(configId, keep, this.db);
2113
2173
  }
2114
2174
  async listProfiles() {
2115
- const profiles = [];
2116
- let cursor = 0;
2117
- while (true) {
2118
- const page = await this.listProfilesPage({ limit: 100, cursor });
2119
- profiles.push(...page.items);
2120
- if (page.complete)
2121
- return profiles;
2122
- cursor = page.next_cursor;
2123
- }
2175
+ return aggregateBoundedCollection("profile list", (cursor) => this.listProfilesPage({ limit: 100, cursor }), (profile) => profile.id);
2124
2176
  }
2125
2177
  async listProfilesPage(options = {}) {
2126
2178
  return (await localStoreModule()).listProfilesPage(options, this.db);
@@ -2129,15 +2181,7 @@ class LocalConfigStore {
2129
2181
  return (await localStoreModule()).getProfile(idOrSlug, this.db);
2130
2182
  }
2131
2183
  async getProfileConfigs(idOrSlug) {
2132
- const configs = [];
2133
- let cursor = 0;
2134
- while (true) {
2135
- const page = await this.getProfileConfigsPage(idOrSlug, { limit: 100, cursor });
2136
- configs.push(...page.items);
2137
- if (page.complete)
2138
- return configs;
2139
- cursor = page.next_cursor;
2140
- }
2184
+ return aggregateBoundedCollection("profile membership", (cursor) => this.getProfileConfigsPage(idOrSlug, { limit: 100, cursor }), (config) => config.id);
2141
2185
  }
2142
2186
  async getProfileConfigsPage(idOrSlug, options = {}) {
2143
2187
  return (await localStoreModule()).getProfileConfigsPage(idOrSlug, options, this.db);
@@ -2197,6 +2241,43 @@ class LocalConfigStore {
2197
2241
  (await localStoreModule()).resetLocalDatabase();
2198
2242
  }
2199
2243
  }
2244
+ function toConfigIdentity(value) {
2245
+ const record = value && typeof value === "object" ? value : {};
2246
+ return {
2247
+ id: String(record.id ?? ""),
2248
+ name: String(record.name ?? ""),
2249
+ slug: String(record.slug ?? ""),
2250
+ kind: String(record.kind ?? ""),
2251
+ category: String(record.category ?? ""),
2252
+ agent: String(record.agent ?? ""),
2253
+ format: String(record.format ?? ""),
2254
+ is_template: Boolean(record.is_template),
2255
+ version: Number(record.version ?? 0),
2256
+ created_at: String(record.created_at ?? ""),
2257
+ updated_at: String(record.updated_at ?? ""),
2258
+ synced_at: record.synced_at == null ? null : String(record.synced_at)
2259
+ };
2260
+ }
2261
+ function toConfigSummary(value) {
2262
+ const record = value && typeof value === "object" ? value : {};
2263
+ const outputs = Array.isArray(record.outputs) ? record.outputs : [];
2264
+ return {
2265
+ id: String(record.id ?? ""),
2266
+ slug: String(record.slug ?? ""),
2267
+ name: String(record.name ?? ""),
2268
+ category: String(record.category ?? ""),
2269
+ agent: String(record.agent ?? ""),
2270
+ kind: String(record.kind ?? ""),
2271
+ format: String(record.format ?? ""),
2272
+ target_path: record.target_path == null ? null : String(record.target_path),
2273
+ output_count: Number(record.output_count ?? outputs.length),
2274
+ version: Number(record.version ?? 0),
2275
+ is_template: Boolean(record.is_template),
2276
+ ...record.updated_at !== undefined ? { updated_at: String(record.updated_at) } : {},
2277
+ ...record.description !== undefined ? { description: record.description == null ? null : String(record.description) } : {},
2278
+ ...Array.isArray(record.tags) ? { tags: record.tags.map(String) } : {}
2279
+ };
2280
+ }
2200
2281
 
2201
2282
  class CloudConfigStore {
2202
2283
  mode = "api";
@@ -2218,7 +2299,8 @@ class CloudConfigStore {
2218
2299
  throw err;
2219
2300
  }
2220
2301
  }
2221
- async listConfigs(filter = {}) {
2302
+ async listConfigsPage(filter = {}, options = {}) {
2303
+ const normalized = normalizeBoundedReadOptions(options);
2222
2304
  const params = new URLSearchParams;
2223
2305
  if (filter.category)
2224
2306
  params.set("category", filter.category);
@@ -2228,16 +2310,75 @@ class CloudConfigStore {
2228
2310
  params.set("kind", filter.kind);
2229
2311
  if (filter.search)
2230
2312
  params.set("search", filter.search);
2231
- const qs = params.toString();
2232
- const { data } = await this.request("GET", `/configs${qs ? `?${qs}` : ""}`);
2233
- let configs = data?.configs ?? [];
2313
+ for (const tag of filter.tags ?? [])
2314
+ params.append("tag", tag);
2315
+ params.set("limit", String(normalized.limit));
2316
+ params.set("cursor", String(normalized.cursor));
2317
+ const { data } = await this.request("GET", `/configs?${params.toString()}`);
2318
+ return parseBoundedOrLegacyPage(data, data?.configs, normalized, "config list");
2319
+ }
2320
+ async listConfigIdentitiesPage(filter = {}, options = {}) {
2321
+ const normalized = normalizeBoundedReadOptions(options);
2322
+ const params = new URLSearchParams;
2323
+ if (filter.category)
2324
+ params.set("category", filter.category);
2325
+ if (filter.agent)
2326
+ params.set("agent", filter.agent);
2327
+ if (filter.kind)
2328
+ params.set("kind", filter.kind);
2329
+ if (filter.search)
2330
+ params.set("search", filter.search);
2331
+ for (const tag of filter.tags ?? [])
2332
+ params.append("tag", tag);
2333
+ params.set("view", "identity");
2334
+ params.set("limit", String(normalized.limit));
2335
+ params.set("cursor", String(normalized.cursor));
2336
+ const { data } = await this.request("GET", `/configs?${params.toString()}`);
2337
+ const page = parseBoundedOrLegacyPage(data, data?.configs, normalized, "config identity list");
2338
+ if (page.items.some((item) => {
2339
+ const record = item && typeof item === "object" ? item : {};
2340
+ return ["content", "target_path", "outputs", "description", "tags"].some((field) => Object.prototype.hasOwnProperty.call(record, field));
2341
+ })) {
2342
+ throw new Error("Instructions /v1 config identity projection is unavailable; deploy the identity endpoint before using compact lists");
2343
+ }
2344
+ return projectBoundedPage(page, toConfigIdentity);
2345
+ }
2346
+ async listConfigSummariesPage(filter = {}, options = {}) {
2347
+ const normalized = normalizeBoundedReadOptions(options);
2348
+ const params = new URLSearchParams;
2349
+ if (filter.category)
2350
+ params.set("category", filter.category);
2351
+ if (filter.agent)
2352
+ params.set("agent", filter.agent);
2353
+ if (filter.kind)
2354
+ params.set("kind", filter.kind);
2355
+ if (filter.search)
2356
+ params.set("search", filter.search);
2357
+ for (const tag of filter.tags ?? [])
2358
+ params.append("tag", tag);
2359
+ params.set("view", "summary");
2360
+ params.set("limit", String(normalized.limit));
2361
+ params.set("cursor", String(normalized.cursor));
2362
+ const { data } = await this.request("GET", `/configs?${params.toString()}`);
2363
+ const page = parseBoundedOrLegacyPage(data, data?.configs, normalized, "config summary list");
2364
+ if (page.items.some((item) => {
2365
+ const record = item && typeof item === "object" ? item : {};
2366
+ return Object.prototype.hasOwnProperty.call(record, "content") || Object.prototype.hasOwnProperty.call(record, "outputs");
2367
+ })) {
2368
+ throw new Error("Instructions /v1 config summary projection is unavailable; deploy the summary endpoint before using summary lists");
2369
+ }
2370
+ return projectBoundedPage(page, toConfigSummary);
2371
+ }
2372
+ async listConfigs(filter = {}) {
2373
+ const configs = await aggregateBoundedCollection("config list", (cursor) => this.listConfigsPage(filter, { limit: 100, cursor }), (config) => config.id, { requireAscendingIdentity: true });
2374
+ let filtered = configs;
2234
2375
  if (filter.tags && filter.tags.length > 0) {
2235
- configs = configs.filter((c) => filter.tags.every((t) => c.tags.includes(t)));
2376
+ filtered = filtered.filter((config) => filter.tags.every((tag) => config.tags.includes(tag)));
2236
2377
  }
2237
2378
  if (filter.is_template !== undefined) {
2238
- configs = configs.filter((c) => c.is_template === filter.is_template);
2379
+ filtered = filtered.filter((config) => config.is_template === filter.is_template);
2239
2380
  }
2240
- return configs;
2381
+ return filtered;
2241
2382
  }
2242
2383
  async getConfig(idOrSlug) {
2243
2384
  const { status, data } = await this.request("GET", `/configs/${encodeURIComponent(idOrSlug)}`, undefined, { allow404: true });
@@ -2268,8 +2409,11 @@ class CloudConfigStore {
2268
2409
  return data ?? { total: 0 };
2269
2410
  }
2270
2411
  async listSnapshots(configId) {
2271
- const { data } = await this.request("GET", `/configs/${encodeURIComponent(configId)}/snapshots`);
2272
- return data?.snapshots ?? [];
2412
+ return aggregateBoundedCollection("snapshot list", async (cursor) => {
2413
+ const normalized = normalizeBoundedReadOptions({ limit: 100, cursor });
2414
+ const { data } = await this.request("GET", `/configs/${encodeURIComponent(configId)}/snapshots?limit=${normalized.limit}&cursor=${normalized.cursor}`);
2415
+ return parseBoundedOrLegacyPage(data, data?.snapshots, normalized, "snapshot list");
2416
+ }, (snapshot) => snapshot.id);
2273
2417
  }
2274
2418
  async getSnapshot(id) {
2275
2419
  const { status, data } = await this.request("GET", `/snapshots/${encodeURIComponent(id)}`, undefined, { allow404: true });
@@ -2292,15 +2436,7 @@ class CloudConfigStore {
2292
2436
  return data?.pruned ?? 0;
2293
2437
  }
2294
2438
  async listProfiles() {
2295
- const profiles = [];
2296
- let cursor = 0;
2297
- while (true) {
2298
- const page = await this.listProfilesPage({ limit: 100, cursor });
2299
- profiles.push(...page.items);
2300
- if (page.complete)
2301
- return profiles;
2302
- cursor = page.next_cursor;
2303
- }
2439
+ return aggregateBoundedCollection("profile list", (cursor) => this.listProfilesPage({ limit: 100, cursor }), (profile) => profile.id);
2304
2440
  }
2305
2441
  async listProfilesPage(options = {}) {
2306
2442
  const normalized = normalizeBoundedReadOptions(options);
@@ -2324,15 +2460,7 @@ class CloudConfigStore {
2324
2460
  return profile;
2325
2461
  }
2326
2462
  async getProfileConfigs(idOrSlug) {
2327
- const configs = [];
2328
- let cursor = 0;
2329
- while (true) {
2330
- const page = await this.getProfileConfigsPage(idOrSlug, { limit: 100, cursor });
2331
- configs.push(...page.items);
2332
- if (page.complete)
2333
- return configs;
2334
- cursor = page.next_cursor;
2335
- }
2463
+ return aggregateBoundedCollection("profile membership", (cursor) => this.getProfileConfigsPage(idOrSlug, { limit: 100, cursor }), (config) => config.id);
2336
2464
  }
2337
2465
  async getProfileConfigsPage(idOrSlug, options = {}) {
2338
2466
  const normalized = normalizeBoundedReadOptions(options);
@@ -2346,20 +2474,26 @@ class CloudConfigStore {
2346
2474
  return parseBoundedOrLegacyPage(data.configs, data.profile.configs, normalized, "profile membership");
2347
2475
  }
2348
2476
  async getProfileConfigBindings(idOrSlug) {
2349
- const { status, data } = await this.requestProfileRoute(idOrSlug, (profileId) => `/profiles/${encodeURIComponent(profileId)}/bindings`, (value) => Array.isArray(value?.bindings));
2350
- if (status === 404) {
2351
- const profile = await this.getProfile(idOrSlug);
2352
- const configs = await this.getProfileConfigs(idOrSlug);
2353
- return configs.map((config, sort_order) => ({
2354
- profile_id: profile.id,
2355
- config_id: config.id,
2356
- sort_order,
2357
- binding: legacyProfileConfigBinding()
2358
- }));
2359
- }
2360
- if (!data || !Array.isArray(data.bindings))
2361
- throw new ProfileNotFoundError(idOrSlug);
2362
- return data.bindings;
2477
+ let routeMissing = false;
2478
+ const bindings = await aggregateBoundedCollection("profile config bindings", async (cursor) => {
2479
+ const normalized = normalizeBoundedReadOptions({ limit: 100, cursor });
2480
+ const { status, data } = await this.requestProfileRoute(idOrSlug, (profileId) => `/profiles/${encodeURIComponent(profileId)}/bindings?limit=${normalized.limit}&cursor=${normalized.cursor}`, (value) => Boolean(value && (Array.isArray(value.bindings) || Array.isArray(value.items))));
2481
+ if (status === 404) {
2482
+ routeMissing = true;
2483
+ return { ...boundedReadPage([], 0, normalized), source_bounded: false };
2484
+ }
2485
+ return parseBoundedOrLegacyPage(data, data?.bindings, normalized, "profile config bindings");
2486
+ }, (binding) => `${binding.profile_id}\x00${binding.config_id}`);
2487
+ if (!routeMissing)
2488
+ return bindings;
2489
+ const profile = await this.getProfile(idOrSlug);
2490
+ const configs = await this.getProfileConfigs(idOrSlug);
2491
+ return configs.map((config, sort_order) => ({
2492
+ profile_id: profile.id,
2493
+ config_id: config.id,
2494
+ sort_order,
2495
+ binding: legacyProfileConfigBinding()
2496
+ }));
2363
2497
  }
2364
2498
  async requestProfileRoute(idOrSlug, pathForId, isUsable) {
2365
2499
  const attempted = new Set;
@@ -2417,14 +2551,20 @@ class CloudConfigStore {
2417
2551
  await this.request("DELETE", `/profiles/${encodeURIComponent(profileIdOrSlug)}/configs/${encodeURIComponent(configId)}`, undefined, { allow404: true });
2418
2552
  }
2419
2553
  async getProfileAssetBindings(profileIdOrSlug) {
2420
- const { status, data } = await this.requestProfileRoute(profileIdOrSlug, (profileId) => `/profiles/${encodeURIComponent(profileId)}/assets`, (value) => Array.isArray(value?.assets));
2421
- if (status === 404) {
2422
- await this.getProfile(profileIdOrSlug);
2423
- return [];
2424
- }
2425
- if (!data || !Array.isArray(data.assets))
2426
- throw new ProfileNotFoundError(profileIdOrSlug);
2427
- return data.assets;
2554
+ let routeMissing = false;
2555
+ const assets = await aggregateBoundedCollection("profile asset bindings", async (cursor) => {
2556
+ const normalized = normalizeBoundedReadOptions({ limit: 100, cursor });
2557
+ const { status, data } = await this.requestProfileRoute(profileIdOrSlug, (profileId) => `/profiles/${encodeURIComponent(profileId)}/assets?limit=${normalized.limit}&cursor=${normalized.cursor}`, (value) => Boolean(value && (Array.isArray(value.assets) || Array.isArray(value.items))));
2558
+ if (status === 404) {
2559
+ routeMissing = true;
2560
+ return { ...boundedReadPage([], 0, normalized), source_bounded: false };
2561
+ }
2562
+ return parseBoundedOrLegacyPage(data, data?.assets, normalized, "profile asset bindings");
2563
+ }, (asset) => `${asset.profile_id}\x00${asset.binding.assetKey}`);
2564
+ if (!routeMissing)
2565
+ return assets;
2566
+ await this.getProfile(profileIdOrSlug);
2567
+ return [];
2428
2568
  }
2429
2569
  async addAssetToProfile(profileIdOrSlug, sourceConfigId, binding) {
2430
2570
  const { data } = await this.request("POST", `/profiles/${encodeURIComponent(profileIdOrSlug)}/assets`, { source_config_id: sourceConfigId, binding }, { idempotent: true });
@@ -2494,8 +2634,11 @@ class CloudConfigStore {
2494
2634
  await this.request("POST", "/machines/applied", { hostname });
2495
2635
  }
2496
2636
  async listMachines() {
2497
- const { data } = await this.request("GET", "/machines");
2498
- return data?.machines ?? [];
2637
+ return aggregateBoundedCollection("machine list", async (cursor) => {
2638
+ const normalized = normalizeBoundedReadOptions({ limit: 100, cursor });
2639
+ const { data } = await this.request("GET", `/machines?limit=${normalized.limit}&cursor=${normalized.cursor}`);
2640
+ return parseBoundedOrLegacyPage(data, data?.machines, normalized, "machine list");
2641
+ }, (machine) => machine.id, { requireAscendingIdentity: true });
2499
2642
  }
2500
2643
  async sendFeedback(input) {
2501
2644
  await this.request("POST", "/feedback", {
@@ -7245,4 +7388,4 @@ function renderMachineAwareContentPreview(content, variables) {
7245
7388
  return isTemplate(content) ? renderTemplatePreview(content, variables) : { content, unresolved: [] };
7246
7389
  }
7247
7390
 
7248
- export { pagedPayload, summarizeConfig, summarizeProfile, summarizeApplyResult, normalizeBoundedReadOptions, boundedReadPage, SESSION_RENDER_SCHEMA, SESSION_RENDER_MANIFEST_RELATIVE_PATH, isRetiredOrUnsupportedConfigAgent, retiredOrUnsupportedAgentReason, applyTransform, normalizeProfileAssetBinding, isCursorGlobalAuthorityPath, stampCursorGlobalAuthorityMarker, getReportedDbPath, getRawStoreRoot, ANTIGRAVITY_RULE_FILE_CHAR_LIMIT, SESSION_RENDERER_OWNER_ID, SESSION_RENDER_OWNED_CONFIG_TARGETS, SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS, legacyProfileConfigBinding, normalizeProfileConfigBinding, hasInstructionsEnvAuthorityIntent, normalizeOsFamily, detectMachineContext, machineContextToVariables, resolveProfileVariables, templateizeMachineContent, renderMachineAwareContentPreview, resolveConfigStore };
7391
+ export { pagedPayload, summarizeProfile, summarizeApplyResult, normalizeBoundedReadOptions, boundedReadPage, SESSION_RENDER_SCHEMA, SESSION_RENDER_MANIFEST_RELATIVE_PATH, isRetiredOrUnsupportedConfigAgent, retiredOrUnsupportedAgentReason, applyTransform, normalizeProfileAssetBinding, isCursorGlobalAuthorityPath, stampCursorGlobalAuthorityMarker, getReportedDbPath, getRawStoreRoot, ANTIGRAVITY_RULE_FILE_CHAR_LIMIT, SESSION_RENDERER_OWNER_ID, SESSION_RENDER_OWNED_CONFIG_TARGETS, SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS, legacyProfileConfigBinding, normalizeProfileConfigBinding, hasInstructionsEnvAuthorityIntent, normalizeOsFamily, detectMachineContext, machineContextToVariables, resolveProfileVariables, templateizeMachineContent, renderMachineAwareContentPreview, resolveConfigStore };