@hasna/instructions 0.4.20 → 0.4.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp/index.js CHANGED
@@ -586,6 +586,133 @@ var init_machine = __esm(() => {
586
586
  init_template();
587
587
  });
588
588
 
589
+ // src/lib/compact-output.ts
590
+ function parseLimit(value, fallback = DEFAULT_LIST_LIMIT, max = MAX_LIST_LIMIT) {
591
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
592
+ if (!Number.isFinite(parsed) || parsed <= 0)
593
+ return fallback;
594
+ return Math.min(Math.floor(parsed), max);
595
+ }
596
+ function parseCursor(value) {
597
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
598
+ if (!Number.isFinite(parsed) || parsed < 0)
599
+ return 0;
600
+ return Math.floor(parsed);
601
+ }
602
+ function paginate(items, opts = {}) {
603
+ const limit = parseLimit(opts.limit, opts.defaultLimit ?? DEFAULT_LIST_LIMIT, opts.maxLimit ?? MAX_LIST_LIMIT);
604
+ const cursor = parseCursor(opts.cursor);
605
+ const pageItems = items.slice(cursor, cursor + limit);
606
+ const nextCursor = cursor + pageItems.length < items.length ? cursor + pageItems.length : null;
607
+ return {
608
+ items: pageItems,
609
+ total: items.length,
610
+ limit,
611
+ cursor,
612
+ next_cursor: nextCursor,
613
+ has_more: nextCursor !== null
614
+ };
615
+ }
616
+ function pagedPayload(items, opts = {}) {
617
+ return {
618
+ ...paginate(items, opts),
619
+ hint: opts.hint
620
+ };
621
+ }
622
+ function truncateText(value, max = 80) {
623
+ const text = (value ?? "").replace(/\s+/g, " ").trim();
624
+ if (text.length <= max)
625
+ return text;
626
+ if (max <= 3)
627
+ return text.slice(0, max);
628
+ return `${text.slice(0, max - 3)}...`;
629
+ }
630
+ function summarizeConfig(config, opts = {}) {
631
+ const summary = {
632
+ id: config.id,
633
+ slug: config.slug,
634
+ name: config.name,
635
+ category: config.category,
636
+ agent: config.agent,
637
+ kind: config.kind,
638
+ format: config.format,
639
+ target_path: config.target_path,
640
+ output_count: config.outputs.length,
641
+ version: config.version,
642
+ is_template: config.is_template
643
+ };
644
+ if (opts.verbose) {
645
+ summary.updated_at = config.updated_at;
646
+ summary.description = config.description;
647
+ summary.tags = config.tags;
648
+ summary.outputs = config.outputs;
649
+ }
650
+ return summary;
651
+ }
652
+ function summarizeProfile(profile, opts = {}) {
653
+ const selectorCount = (profile.selectors.os?.length ?? 0) + (profile.selectors.arch?.length ?? 0) + (profile.selectors.hostnames?.length ?? 0);
654
+ const summary = {
655
+ id: profile.id,
656
+ slug: profile.slug,
657
+ name: profile.name,
658
+ description: opts.verbose ? profile.description : truncateText(profile.description, 80),
659
+ selector_count: selectorCount,
660
+ variable_count: Object.keys(profile.variables).length
661
+ };
662
+ if (opts.verbose) {
663
+ summary.created_at = profile.created_at;
664
+ summary.updated_at = profile.updated_at;
665
+ summary.selectors = profile.selectors;
666
+ summary.variables = profile.variables;
667
+ }
668
+ return summary;
669
+ }
670
+ function summarizeApplyResult(result) {
671
+ return {
672
+ config_id: result.config_id,
673
+ path: result.path,
674
+ dry_run: result.dry_run,
675
+ changed: result.changed,
676
+ primary_changed: result.primary_changed,
677
+ agent: result.agent,
678
+ transform: result.transform,
679
+ output_count: result.outputs?.length ?? 0,
680
+ outputs: result.outputs?.map(summarizeApplyResult)
681
+ };
682
+ }
683
+ var DEFAULT_LIST_LIMIT = 20, MAX_LIST_LIMIT = 100;
684
+
685
+ // src/lib/bounded-read.ts
686
+ function normalizeBoundedReadOptions(options = {}) {
687
+ return {
688
+ limit: parseLimit(options.limit, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT),
689
+ cursor: parseCursor(options.cursor)
690
+ };
691
+ }
692
+ function boundedReadPage(items, total, options = {}) {
693
+ const { limit, cursor } = normalizeBoundedReadOptions(options);
694
+ if (items.length > limit) {
695
+ throw new Error(`bounded read returned ${items.length} rows for limit ${limit}`);
696
+ }
697
+ const consumed = cursor + items.length;
698
+ const complete = consumed >= total;
699
+ if (!complete && items.length === 0) {
700
+ throw new Error(`bounded read did not advance at cursor ${cursor} of ${total}`);
701
+ }
702
+ return {
703
+ items,
704
+ total,
705
+ limit,
706
+ cursor,
707
+ next_cursor: complete ? null : consumed,
708
+ has_more: !complete,
709
+ complete,
710
+ truncated: false,
711
+ source_bounded: true
712
+ };
713
+ }
714
+ var init_bounded_read = () => {};
715
+
589
716
  // src/db/profiles.ts
590
717
  function rowToProfile(row) {
591
718
  return {
@@ -629,9 +756,12 @@ function getProfile(idOrSlug, db) {
629
756
  throw new ProfileNotFoundError(idOrSlug);
630
757
  return rowToProfile(row);
631
758
  }
632
- function listProfiles(db) {
759
+ function listProfilesPage(options = {}, db) {
633
760
  const d = db || getDatabase();
634
- return d.query("SELECT * FROM profiles ORDER BY name").all().map(rowToProfile);
761
+ const normalized = normalizeBoundedReadOptions(options);
762
+ const total = d.query("SELECT COUNT(*) AS total FROM profiles").get()?.total ?? 0;
763
+ const rows = d.query("SELECT * FROM profiles ORDER BY name LIMIT ? OFFSET ?").all(normalized.limit, normalized.cursor).map(rowToProfile);
764
+ return boundedReadPage(rows, total, normalized);
635
765
  }
636
766
  function updateProfile(idOrSlug, input, db) {
637
767
  const d = db || getDatabase();
@@ -676,14 +806,13 @@ function removeConfigFromProfile(profileIdOrSlug, configId, db) {
676
806
  const profile = getProfile(profileIdOrSlug, d);
677
807
  d.run("DELETE FROM profile_configs WHERE profile_id = ? AND config_id = ?", [profile.id, configId]);
678
808
  }
679
- function getProfileConfigs(profileIdOrSlug, db) {
809
+ function getProfileConfigsPage(profileIdOrSlug, options = {}, db) {
680
810
  const d = db || getDatabase();
681
811
  const profile = getProfile(profileIdOrSlug, d);
682
- const rows = d.query("SELECT config_id FROM profile_configs WHERE profile_id = ? ORDER BY sort_order").all(profile.id);
683
- if (rows.length === 0)
684
- return [];
685
- const ids = rows.map((r) => r.config_id);
686
- return listConfigs(undefined, d).filter((c) => ids.includes(c.id));
812
+ const normalized = normalizeBoundedReadOptions(options);
813
+ const total = d.query("SELECT COUNT(*) AS total FROM profile_configs WHERE profile_id = ?").get(profile.id)?.total ?? 0;
814
+ const rows = d.query("SELECT config_id FROM profile_configs WHERE profile_id = ? ORDER BY sort_order LIMIT ? OFFSET ?").all(profile.id, normalized.limit, normalized.cursor);
815
+ return boundedReadPage(rows.map((row) => getConfigById(row.config_id, d)), total, normalized);
687
816
  }
688
817
  function profileHasSelectors(profile) {
689
818
  const selectors = profile.selectors ?? {};
@@ -699,20 +828,46 @@ function profileMatchesMachine(profile, machine) {
699
828
  const hostnameMatches = !selectors.hostnames?.length || selectors.hostnames.some((candidate) => candidate.trim().toLowerCase() === machine.hostname.trim().toLowerCase());
700
829
  return osMatches && archMatches && hostnameMatches;
701
830
  }
702
- function resolveProfileForMachine(machine = detectMachineContext(), db) {
703
- const profiles = listProfiles(db).filter(profileHasSelectors);
704
- const matches = profiles.filter((profile) => profileMatchesMachine(profile, machine)).map((profile) => {
705
- const selectors = profile.selectors;
706
- const score = (selectors.hostnames?.length ? 100 : 0) + (selectors.os?.length ? 10 : 0) + (selectors.arch?.length ? 10 : 0);
707
- return { profile, score };
708
- }).sort((a, b) => b.score - a.score || a.profile.name.localeCompare(b.profile.name));
709
- return matches[0]?.profile ?? null;
831
+ function resolveProfileForMachineRead(machine = detectMachineContext(), options = {}, db) {
832
+ const d = db || getDatabase();
833
+ const { limit } = normalizeBoundedReadOptions(options);
834
+ let cursor = 0;
835
+ let scanned = 0;
836
+ let total = 0;
837
+ let selected = null;
838
+ while (true) {
839
+ const page = listProfilesPage({ limit, cursor }, d);
840
+ total = page.total;
841
+ scanned += page.items.length;
842
+ for (const profile of page.items) {
843
+ if (!profileHasSelectors(profile) || !profileMatchesMachine(profile, machine))
844
+ continue;
845
+ const selectors = profile.selectors;
846
+ const score = (selectors.hostnames?.length ? 100 : 0) + (selectors.os?.length ? 10 : 0) + (selectors.arch?.length ? 10 : 0);
847
+ if (!selected || score > selected.score || score === selected.score && profile.name.localeCompare(selected.profile.name) < 0) {
848
+ selected = { profile, score };
849
+ }
850
+ }
851
+ if (page.complete)
852
+ break;
853
+ cursor = page.next_cursor;
854
+ }
855
+ return {
856
+ profile: selected?.profile ?? null,
857
+ scanned,
858
+ total,
859
+ batch_limit: limit,
860
+ source_bounded: true,
861
+ complete: true,
862
+ truncated: false
863
+ };
710
864
  }
711
865
  var init_profiles = __esm(() => {
712
866
  init_types();
713
867
  init_database();
714
868
  init_configs();
715
869
  init_machine();
870
+ init_bounded_read();
716
871
  });
717
872
 
718
873
  // src/db/snapshots.ts
@@ -790,6 +945,32 @@ var init_machines = __esm(() => {
790
945
 
791
946
  // src/data/config-store.ts
792
947
  import { randomUUID as randomUUID2 } from "crypto";
948
+ function parseBoundedPagePayload(value, label) {
949
+ const page = value;
950
+ const consumed = Number(page?.cursor) + (page?.items?.length ?? 0);
951
+ const complete = Boolean(page && Number.isSafeInteger(page.total) && consumed >= Number(page.total));
952
+ 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)) {
953
+ throw new CloudHttpError(502, `${label} returned an invalid or truncated bounded-read envelope`, value);
954
+ }
955
+ return {
956
+ ...page,
957
+ source_bounded: page.source_bounded ?? true
958
+ };
959
+ }
960
+ function parseBoundedOrLegacyPage(value, legacyItems, options, label) {
961
+ if (value && typeof value === "object") {
962
+ const candidate = value;
963
+ if ("items" in candidate || "total" in candidate || "complete" in candidate || "truncated" in candidate || "next_cursor" in candidate) {
964
+ return parseBoundedPagePayload(value, label);
965
+ }
966
+ }
967
+ if (!Array.isArray(legacyItems)) {
968
+ throw new CloudHttpError(502, `${label} returned neither a bounded envelope nor a complete legacy array`, value);
969
+ }
970
+ const normalized = normalizeBoundedReadOptions(options);
971
+ const page = boundedReadPage(legacyItems.slice(normalized.cursor, normalized.cursor + normalized.limit), legacyItems.length, normalized);
972
+ return { ...page, source_bounded: false };
973
+ }
793
974
  function resolveCloudConfig(env = process.env) {
794
975
  const apiUrl = env[API_URL_ENV]?.trim();
795
976
  const apiKey = env[API_KEY_ENV]?.trim();
@@ -844,13 +1025,35 @@ class LocalConfigStore {
844
1025
  return pruneSnapshots(configId, keep, this.db);
845
1026
  }
846
1027
  async listProfiles() {
847
- return listProfiles(this.db);
1028
+ const profiles = [];
1029
+ let cursor = 0;
1030
+ while (true) {
1031
+ const page = await this.listProfilesPage({ limit: 100, cursor });
1032
+ profiles.push(...page.items);
1033
+ if (page.complete)
1034
+ return profiles;
1035
+ cursor = page.next_cursor;
1036
+ }
1037
+ }
1038
+ async listProfilesPage(options = {}) {
1039
+ return listProfilesPage(options, this.db);
848
1040
  }
849
1041
  async getProfile(idOrSlug) {
850
1042
  return getProfile(idOrSlug, this.db);
851
1043
  }
852
1044
  async getProfileConfigs(idOrSlug) {
853
- return getProfileConfigs(idOrSlug, this.db);
1045
+ const configs = [];
1046
+ let cursor = 0;
1047
+ while (true) {
1048
+ const page = await this.getProfileConfigsPage(idOrSlug, { limit: 100, cursor });
1049
+ configs.push(...page.items);
1050
+ if (page.complete)
1051
+ return configs;
1052
+ cursor = page.next_cursor;
1053
+ }
1054
+ }
1055
+ async getProfileConfigsPage(idOrSlug, options = {}) {
1056
+ return getProfileConfigsPage(idOrSlug, options, this.db);
854
1057
  }
855
1058
  async createProfile(input) {
856
1059
  return createProfile(input, this.db);
@@ -868,7 +1071,10 @@ class LocalConfigStore {
868
1071
  removeConfigFromProfile(profileIdOrSlug, configId, this.db);
869
1072
  }
870
1073
  async resolveProfileForMachine(machine) {
871
- return machine ? resolveProfileForMachine(machine, this.db) : resolveProfileForMachine(undefined, this.db);
1074
+ return (await this.resolveProfileForMachineRead(machine)).profile;
1075
+ }
1076
+ async resolveProfileForMachineRead(machine, options = {}) {
1077
+ return machine ? resolveProfileForMachineRead(machine, options, this.db) : resolveProfileForMachineRead(undefined, options, this.db);
872
1078
  }
873
1079
  async registerMachine(hostname2, os, arch2) {
874
1080
  return registerMachine(hostname2, os, arch2, this.db);
@@ -1009,8 +1215,24 @@ class CloudConfigStore {
1009
1215
  return data?.pruned ?? 0;
1010
1216
  }
1011
1217
  async listProfiles() {
1012
- const { data } = await this.request("GET", "/profiles");
1013
- return data?.profiles ?? [];
1218
+ const profiles = [];
1219
+ let cursor = 0;
1220
+ while (true) {
1221
+ const page = await this.listProfilesPage({ limit: 100, cursor });
1222
+ profiles.push(...page.items);
1223
+ if (page.complete)
1224
+ return profiles;
1225
+ cursor = page.next_cursor;
1226
+ }
1227
+ }
1228
+ async listProfilesPage(options = {}) {
1229
+ const normalized = normalizeBoundedReadOptions(options);
1230
+ const params = new URLSearchParams;
1231
+ params.set("limit", String(normalized.limit));
1232
+ params.set("cursor", String(normalized.cursor));
1233
+ const qs = params.toString();
1234
+ const { data } = await this.request("GET", `/profiles${qs ? `?${qs}` : ""}`);
1235
+ return parseBoundedOrLegacyPage(data, data?.profiles, normalized, "profile list");
1014
1236
  }
1015
1237
  async getProfile(idOrSlug) {
1016
1238
  const { status, data } = await this.request("GET", `/profiles/${encodeURIComponent(idOrSlug)}`, undefined, { allow404: true });
@@ -1020,10 +1242,26 @@ class CloudConfigStore {
1020
1242
  return profile;
1021
1243
  }
1022
1244
  async getProfileConfigs(idOrSlug) {
1023
- const { status, data } = await this.request("GET", `/profiles/${encodeURIComponent(idOrSlug)}`, undefined, { allow404: true });
1245
+ const configs = [];
1246
+ let cursor = 0;
1247
+ while (true) {
1248
+ const page = await this.getProfileConfigsPage(idOrSlug, { limit: 100, cursor });
1249
+ configs.push(...page.items);
1250
+ if (page.complete)
1251
+ return configs;
1252
+ cursor = page.next_cursor;
1253
+ }
1254
+ }
1255
+ async getProfileConfigsPage(idOrSlug, options = {}) {
1256
+ const normalized = normalizeBoundedReadOptions(options);
1257
+ const params = new URLSearchParams;
1258
+ params.set("limit", String(normalized.limit));
1259
+ params.set("cursor", String(normalized.cursor));
1260
+ const qs = params.toString();
1261
+ const { status, data } = await this.request("GET", `/profiles/${encodeURIComponent(idOrSlug)}${qs ? `?${qs}` : ""}`, undefined, { allow404: true });
1024
1262
  if (status === 404 || !data?.profile)
1025
1263
  throw new ProfileNotFoundError(idOrSlug);
1026
- return data.profile.configs ?? [];
1264
+ return parseBoundedOrLegacyPage(data.configs, data.profile.configs, normalized, "profile membership");
1027
1265
  }
1028
1266
  async createProfile(input) {
1029
1267
  const { data } = await this.request("POST", "/profiles", input, {
@@ -1047,6 +1285,10 @@ class CloudConfigStore {
1047
1285
  await this.request("DELETE", `/profiles/${encodeURIComponent(profileIdOrSlug)}/configs/${encodeURIComponent(configId)}`, undefined, { allow404: true });
1048
1286
  }
1049
1287
  async resolveProfileForMachine(machine) {
1288
+ return (await this.resolveProfileForMachineRead(machine)).profile;
1289
+ }
1290
+ async resolveProfileForMachineRead(machine, options = {}) {
1291
+ const normalized = normalizeBoundedReadOptions(options);
1050
1292
  const params = new URLSearchParams;
1051
1293
  if (machine?.hostname)
1052
1294
  params.set("hostname", machine.hostname);
@@ -1054,11 +1296,40 @@ class CloudConfigStore {
1054
1296
  params.set("os", machine.os);
1055
1297
  if (machine?.arch)
1056
1298
  params.set("arch", machine.arch);
1299
+ params.set("limit", String(normalized.limit));
1057
1300
  const qs = params.toString();
1058
1301
  const { status, data } = await this.request("GET", `/profiles/resolve${qs ? `?${qs}` : ""}`, undefined, { allow404: true });
1059
- if (status === 404 || !data?.profile)
1060
- return null;
1061
- return data.profile;
1302
+ if (status === 404) {
1303
+ return {
1304
+ profile: null,
1305
+ scanned: null,
1306
+ total: null,
1307
+ batch_limit: null,
1308
+ source_bounded: false,
1309
+ complete: true,
1310
+ truncated: false
1311
+ };
1312
+ }
1313
+ if (data && "complete" in data) {
1314
+ if (data.complete !== true || data.truncated !== false) {
1315
+ throw new CloudHttpError(502, "profile resolve returned an incomplete or truncated read", data);
1316
+ }
1317
+ return { ...data, source_bounded: data.source_bounded ?? true };
1318
+ }
1319
+ if (data && "profile" in data) {
1320
+ return {
1321
+ profile: data.profile,
1322
+ scanned: null,
1323
+ total: null,
1324
+ batch_limit: null,
1325
+ source_bounded: false,
1326
+ complete: true,
1327
+ truncated: false
1328
+ };
1329
+ }
1330
+ {
1331
+ throw new CloudHttpError(502, "profile resolve returned an incomplete or truncated read", data);
1332
+ }
1062
1333
  }
1063
1334
  async registerMachine(hostname2, os, arch2) {
1064
1335
  const { data } = await this.request("POST", "/machines", { hostname: hostname2, os, arch: arch2 }, { idempotent: true });
@@ -1095,6 +1366,7 @@ var init_config_store = __esm(() => {
1095
1366
  init_machines();
1096
1367
  init_database();
1097
1368
  init_types();
1369
+ init_bounded_read();
1098
1370
  CloudHttpError = class CloudHttpError extends Error {
1099
1371
  status;
1100
1372
  body;
@@ -6957,7 +7229,7 @@ var init_sync_dir = __esm(() => {
6957
7229
  var require_package = __commonJS((exports, module) => {
6958
7230
  module.exports = {
6959
7231
  name: "@hasna/instructions",
6960
- version: "0.4.20",
7232
+ version: "0.4.21",
6961
7233
  description: "AI coding agent instruction & configuration manager \u2014 store, version, apply, and share all your AI coding configs. CLI + MCP + HTTP API (instructions-serve) + generated SDK + Dashboard.",
6962
7234
  type: "module",
6963
7235
  main: "dist/index.js",
@@ -7081,105 +7353,6 @@ function findReferenceConfigsByName(configs, name) {
7081
7353
  // src/mcp/server.ts
7082
7354
  init_sync_dir();
7083
7355
  init_machine();
7084
-
7085
- // src/lib/compact-output.ts
7086
- var DEFAULT_LIST_LIMIT = 20;
7087
- var MAX_LIST_LIMIT = 100;
7088
- function parseLimit(value, fallback = DEFAULT_LIST_LIMIT, max = MAX_LIST_LIMIT) {
7089
- const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
7090
- if (!Number.isFinite(parsed) || parsed <= 0)
7091
- return fallback;
7092
- return Math.min(Math.floor(parsed), max);
7093
- }
7094
- function parseCursor(value) {
7095
- const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
7096
- if (!Number.isFinite(parsed) || parsed < 0)
7097
- return 0;
7098
- return Math.floor(parsed);
7099
- }
7100
- function paginate(items, opts = {}) {
7101
- const limit = parseLimit(opts.limit, opts.defaultLimit ?? DEFAULT_LIST_LIMIT, opts.maxLimit ?? MAX_LIST_LIMIT);
7102
- const cursor = parseCursor(opts.cursor);
7103
- const pageItems = items.slice(cursor, cursor + limit);
7104
- const nextCursor = cursor + pageItems.length < items.length ? cursor + pageItems.length : null;
7105
- return {
7106
- items: pageItems,
7107
- total: items.length,
7108
- limit,
7109
- cursor,
7110
- next_cursor: nextCursor,
7111
- has_more: nextCursor !== null
7112
- };
7113
- }
7114
- function pagedPayload(items, opts = {}) {
7115
- return {
7116
- ...paginate(items, opts),
7117
- hint: opts.hint
7118
- };
7119
- }
7120
- function truncateText(value, max = 80) {
7121
- const text = (value ?? "").replace(/\s+/g, " ").trim();
7122
- if (text.length <= max)
7123
- return text;
7124
- if (max <= 3)
7125
- return text.slice(0, max);
7126
- return `${text.slice(0, max - 3)}...`;
7127
- }
7128
- function summarizeConfig(config, opts = {}) {
7129
- const summary = {
7130
- id: config.id,
7131
- slug: config.slug,
7132
- name: config.name,
7133
- category: config.category,
7134
- agent: config.agent,
7135
- kind: config.kind,
7136
- format: config.format,
7137
- target_path: config.target_path,
7138
- output_count: config.outputs.length,
7139
- version: config.version,
7140
- is_template: config.is_template
7141
- };
7142
- if (opts.verbose) {
7143
- summary.updated_at = config.updated_at;
7144
- summary.description = config.description;
7145
- summary.tags = config.tags;
7146
- summary.outputs = config.outputs;
7147
- }
7148
- return summary;
7149
- }
7150
- function summarizeProfile(profile, opts = {}) {
7151
- const selectorCount = (profile.selectors.os?.length ?? 0) + (profile.selectors.arch?.length ?? 0) + (profile.selectors.hostnames?.length ?? 0);
7152
- const summary = {
7153
- id: profile.id,
7154
- slug: profile.slug,
7155
- name: profile.name,
7156
- description: opts.verbose ? profile.description : truncateText(profile.description, 80),
7157
- selector_count: selectorCount,
7158
- variable_count: Object.keys(profile.variables).length
7159
- };
7160
- if (opts.verbose) {
7161
- summary.created_at = profile.created_at;
7162
- summary.updated_at = profile.updated_at;
7163
- summary.selectors = profile.selectors;
7164
- summary.variables = profile.variables;
7165
- }
7166
- return summary;
7167
- }
7168
- function summarizeApplyResult(result) {
7169
- return {
7170
- config_id: result.config_id,
7171
- path: result.path,
7172
- dry_run: result.dry_run,
7173
- changed: result.changed,
7174
- primary_changed: result.primary_changed,
7175
- agent: result.agent,
7176
- transform: result.transform,
7177
- output_count: result.outputs?.length ?? 0,
7178
- outputs: result.outputs?.map(summarizeApplyResult)
7179
- };
7180
- }
7181
-
7182
- // src/mcp/server.ts
7183
7356
  var TOOL_DOCS = {
7184
7357
  list_configs: "List configs. Params: category?, agent?, kind?, search?, limit?, cursor?, verbose?. Defaults to a paged compact envelope without content; use get_config for full content.",
7185
7358
  get_config: "Get a config by id or slug. Returns full config including content.",