@elaraai/e3-cloud-claude-plugin 1.0.94 → 1.1.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.
@@ -1,4 +1,4 @@
1
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/host.js
1
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/host.js
2
2
  import { existsSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  var isCodex = existsSync(fileURLToPath(new URL("../../.codex-plugin/plugin.json", import.meta.url)));
@@ -6,7 +6,7 @@ function hostText(text) {
6
6
  return isCodex ? text.replaceAll("mcp__plugin_east_east__", "").replaceAll("/east:", "$east-codex-plugin:").replaceAll("Claude Code", "Codex") : text;
7
7
  }
8
8
 
9
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/hook-io.js
9
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/hook-io.js
10
10
  async function readHookInput() {
11
11
  let input = "";
12
12
  for await (const chunk of process.stdin) {
@@ -34,10 +34,10 @@ function writeHookDecision(hookEventName, decision, reason) {
34
34
  process.stdout.write(JSON.stringify(output));
35
35
  }
36
36
 
37
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/transcript.js
37
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/transcript.js
38
38
  var SEARCH_TOOLS = ["mcp__plugin_east_east__search_east_examples", "mcp__plugin_east_east__get_east_example", "mcp__east__search_east_examples", "mcp__east__get_east_example"];
39
39
 
40
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-guidance.js
40
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-guidance.js
41
41
  var EAST_GUIDANCE = {
42
42
  searchTool: "mcp__plugin_east_east__search_east_examples",
43
43
  getTool: "mcp__plugin_east_east__get_east_example",
@@ -79,7 +79,7 @@ var GATE_TEXT = gateText(EAST_GUIDANCE);
79
79
  var READ_TEXT = readText(EAST_GUIDANCE);
80
80
  var isExampleCorpusRead = corpusReadFor(EAST_GUIDANCE.scope);
81
81
 
82
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-hooks.js
82
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-hooks.js
83
83
  function preReadReminder(event, options) {
84
84
  return options.isCorpusRead(event.tool_name ?? "", event.tool_input ?? {}) ? { decision: "context", text: options.readText } : null;
85
85
  }
@@ -1,10 +1,10 @@
1
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-hooks.js
1
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-hooks.js
2
2
  import { readFile as readFile2 } from "node:fs/promises";
3
3
  import { existsSync as existsSync2, writeFileSync } from "node:fs";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
 
7
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/host.js
7
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/host.js
8
8
  import { existsSync } from "node:fs";
9
9
  import { fileURLToPath } from "node:url";
10
10
  var isCodex = existsSync(fileURLToPath(new URL("../../.codex-plugin/plugin.json", import.meta.url)));
@@ -12,7 +12,7 @@ function hostText(text) {
12
12
  return isCodex ? text.replaceAll("mcp__plugin_east_east__", "").replaceAll("/east:", "$east-codex-plugin:").replaceAll("Claude Code", "Codex") : text;
13
13
  }
14
14
 
15
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/hook-io.js
15
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/hook-io.js
16
16
  async function readHookInput() {
17
17
  let input = "";
18
18
  for await (const chunk of process.stdin) {
@@ -40,7 +40,7 @@ function writeHookDecision(hookEventName, decision, reason) {
40
40
  process.stdout.write(JSON.stringify(output));
41
41
  }
42
42
 
43
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/transcript.js
43
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/transcript.js
44
44
  import { readFile } from "node:fs/promises";
45
45
  var SEARCH_TOOLS = ["mcp__plugin_east_east__search_east_examples", "mcp__plugin_east_east__get_east_example", "mcp__east__search_east_examples", "mcp__east__get_east_example"];
46
46
  async function searchedInTranscript(transcriptPath, tools = SEARCH_TOOLS) {
@@ -53,7 +53,7 @@ async function searchedInTranscript(transcriptPath, tools = SEARCH_TOOLS) {
53
53
  return tools.some((tool) => raw.includes(`"name":"${tool}"`) || raw.includes(`"name": "${tool}"`));
54
54
  }
55
55
 
56
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-guidance.js
56
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-guidance.js
57
57
  var EAST_GUIDANCE = {
58
58
  searchTool: "mcp__plugin_east_east__search_east_examples",
59
59
  getTool: "mcp__plugin_east_east__get_east_example",
@@ -95,7 +95,7 @@ var GATE_TEXT = gateText(EAST_GUIDANCE);
95
95
  var READ_TEXT = readText(EAST_GUIDANCE);
96
96
  var isExampleCorpusRead = corpusReadFor(EAST_GUIDANCE.scope);
97
97
 
98
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-hooks.js
98
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-hooks.js
99
99
  async function preWriteGate(event, options) {
100
100
  const cwd = event.cwd || process.cwd();
101
101
  const filePath = event.tool_input?.file_path;
@@ -3098,9 +3098,9 @@ var require_data = __commonJS({
3098
3098
  }
3099
3099
  });
3100
3100
 
3101
- // ../../node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/utils.js
3101
+ // ../../node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/utils.js
3102
3102
  var require_utils = __commonJS({
3103
- "../../node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/utils.js"(exports, module) {
3103
+ "../../node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/utils.js"(exports, module) {
3104
3104
  "use strict";
3105
3105
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
3106
3106
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
@@ -3332,6 +3332,37 @@ var require_utils = __commonJS({
3332
3332
  }
3333
3333
  return output.join("");
3334
3334
  }
3335
+ function removeDotSegmentsRelative(path) {
3336
+ if (path === "" || path[0] === "/") {
3337
+ return removeDotSegments(path);
3338
+ }
3339
+ const segments = path.split("/");
3340
+ const last2 = segments.length - 1;
3341
+ const output = [];
3342
+ let trailingDirectory = false;
3343
+ for (let i = 0; i <= last2; i++) {
3344
+ const segment = segments[i];
3345
+ if (segment === "." || segment === "..") {
3346
+ if (segment === "..") {
3347
+ if (output.length !== 0 && output[output.length - 1] !== "..") {
3348
+ output.pop();
3349
+ } else {
3350
+ output.push("..");
3351
+ }
3352
+ }
3353
+ trailingDirectory = i === last2;
3354
+ } else {
3355
+ output.push(segment);
3356
+ }
3357
+ }
3358
+ if (trailingDirectory && output[output.length - 1] !== "..") {
3359
+ output.push("");
3360
+ }
3361
+ if (output.length === 0 || output[0] === "" || output[0].includes(":")) {
3362
+ output.unshift(".");
3363
+ }
3364
+ return output.join("/");
3365
+ }
3335
3366
  var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
3336
3367
  var HOST_DELIM_RE = /[@/?#:]/g;
3337
3368
  var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
@@ -3592,6 +3623,7 @@ var require_utils = __commonJS({
3592
3623
  encodeFragment,
3593
3624
  escapePreservingEscapes,
3594
3625
  removeDotSegments,
3626
+ removeDotSegmentsRelative,
3595
3627
  isIPv4,
3596
3628
  isUUID,
3597
3629
  normalizeIPv6,
@@ -3600,9 +3632,9 @@ var require_utils = __commonJS({
3600
3632
  }
3601
3633
  });
3602
3634
 
3603
- // ../../node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/schemes.js
3635
+ // ../../node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/schemes.js
3604
3636
  var require_schemes = __commonJS({
3605
- "../../node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/schemes.js"(exports, module) {
3637
+ "../../node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/schemes.js"(exports, module) {
3606
3638
  "use strict";
3607
3639
  var { isUUID } = require_utils();
3608
3640
  var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
@@ -3811,20 +3843,22 @@ var require_schemes = __commonJS({
3811
3843
  }
3812
3844
  });
3813
3845
 
3814
- // ../../node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/index.js
3846
+ // ../../node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/index.js
3815
3847
  var require_fast_uri = __commonJS({
3816
- "../../node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/index.js"(exports, module) {
3848
+ "../../node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/index.js"(exports, module) {
3817
3849
  "use strict";
3818
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3850
+ var { normalizeIPv6, removeDotSegments, removeDotSegmentsRelative, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3819
3851
  var { SCHEMES, getSchemeHandler } = require_schemes();
3820
3852
  var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
3821
3853
  var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
3822
- function decodeValidScheme(scheme) {
3823
- const decodedScheme = unescape(String(scheme));
3824
- if (!VALID_SCHEME.test(decodedScheme)) {
3854
+ var MALFORMED_HOST_ERROR = "URI host is malformed.";
3855
+ var FORBIDDEN_DOMAIN_HOST_CHARACTER = /[\0-\x20"#/:<>?@[\\\]^`{|}\x7F]/u;
3856
+ function validateScheme(scheme) {
3857
+ const stringScheme = String(scheme);
3858
+ if (!VALID_SCHEME.test(stringScheme)) {
3825
3859
  throw new TypeError(MALFORMED_SCHEME_ERROR);
3826
3860
  }
3827
- return decodedScheme;
3861
+ return stringScheme;
3828
3862
  }
3829
3863
  function normalize(uri, options) {
3830
3864
  if (typeof uri === "string") {
@@ -3861,6 +3895,9 @@ var require_fast_uri = __commonJS({
3861
3895
  const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
3862
3896
  const resolvedHost = resolved.host;
3863
3897
  const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
3898
+ if (hasForbiddenDomainHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)) {
3899
+ throw new Error(MALFORMED_HOST_ERROR);
3900
+ }
3864
3901
  canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
3865
3902
  const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
3866
3903
  if (resolved.error && !encodedASCIIHost) {
@@ -3902,14 +3939,15 @@ var require_fast_uri = __commonJS({
3902
3939
  if (relative.path[0] === "/") {
3903
3940
  target.path = removeDotSegments(relative.path);
3904
3941
  } else {
3905
- if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
3942
+ const baseHasAuthority = base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0;
3943
+ if (baseHasAuthority && !base.path) {
3906
3944
  target.path = "/" + relative.path;
3907
3945
  } else if (!base.path) {
3908
3946
  target.path = relative.path;
3909
3947
  } else {
3910
3948
  target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
3911
3949
  }
3912
- target.path = removeDotSegments(target.path);
3950
+ target.path = base.scheme === void 0 && !baseHasAuthority && target.path[0] !== "/" ? removeDotSegmentsRelative(target.path) : removeDotSegments(target.path);
3913
3951
  }
3914
3952
  target.query = relative.query;
3915
3953
  }
@@ -3947,12 +3985,13 @@ var require_fast_uri = __commonJS({
3947
3985
  const options = Object.assign({}, opts);
3948
3986
  const uriTokens = [];
3949
3987
  if (component.scheme) {
3950
- component.scheme = decodeValidScheme(component.scheme);
3988
+ component.scheme = validateScheme(component.scheme);
3951
3989
  }
3952
3990
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
3953
3991
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
3954
3992
  const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
3955
- const pathNoScheme = !options.skipEscape && component.scheme === void 0 && !hasAuthority;
3993
+ const schemelessReference = (component.scheme === void 0 || options.reference === "suffix") && !hasAuthority;
3994
+ const pathNoScheme = !options.skipEscape && schemelessReference;
3956
3995
  if (component.path !== void 0) {
3957
3996
  if (!options.skipEscape) {
3958
3997
  component.path = serializePathEncoding(component.path, pathNoScheme);
@@ -3961,7 +4000,7 @@ var require_fast_uri = __commonJS({
3961
4000
  }
3962
4001
  }
3963
4002
  if (options.reference !== "suffix" && component.scheme) {
3964
- component.scheme = decodeValidScheme(component.scheme);
4003
+ component.scheme = validateScheme(component.scheme);
3965
4004
  uriTokens.push(component.scheme, ":");
3966
4005
  }
3967
4006
  const authority = recomposeAuthority(component);
@@ -3977,9 +4016,9 @@ var require_fast_uri = __commonJS({
3977
4016
  if (component.path !== void 0) {
3978
4017
  let s = component.path;
3979
4018
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
3980
- s = removeDotSegments(s);
4019
+ s = schemelessReference && s[0] !== "/" ? removeDotSegmentsRelative(s) : removeDotSegments(s);
3981
4020
  }
3982
- if (pathNoScheme) {
4021
+ if (schemelessReference) {
3983
4022
  s = serializePathEncoding(s, true);
3984
4023
  }
3985
4024
  if (authority === void 0 && s[0] === "/" && s[1] === "/") {
@@ -4026,16 +4065,34 @@ var require_fast_uri = __commonJS({
4026
4065
  return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
4027
4066
  }
4028
4067
  function canonicalizeHost(parsed, options, schemeHandler, isIP) {
4068
+ if (hasForbiddenDomainHost(parsed, options, schemeHandler, isIP)) {
4069
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
4070
+ return true;
4071
+ }
4029
4072
  if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
4073
+ let url;
4030
4074
  try {
4031
- parsed.host = new URL("http://" + parsed.host).hostname;
4075
+ url = new URL("http://" + parsed.host);
4032
4076
  } catch (e) {
4033
4077
  parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
4034
4078
  return true;
4035
4079
  }
4080
+ if (!isHostOnlyURL(url)) {
4081
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
4082
+ return true;
4083
+ }
4084
+ parsed.host = url.hostname;
4036
4085
  }
4037
4086
  return false;
4038
4087
  }
4088
+ function hasForbiddenDomainHost(parsed, options, schemeHandler, isIP) {
4089
+ return Boolean(
4090
+ parsed.host && isIP === false && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && FORBIDDEN_DOMAIN_HOST_CHARACTER.test(parsed.host)
4091
+ );
4092
+ }
4093
+ function isHostOnlyURL(url) {
4094
+ return url.username === "" && url.password === "" && url.port === "" && url.pathname === "/" && url.search === "" && url.hash === "" && url.hostname !== "";
4095
+ }
4039
4096
  function parseWithStatus(uri, opts) {
4040
4097
  const options = Object.assign({}, opts);
4041
4098
  const parsed = {
@@ -4090,9 +4147,8 @@ var require_fast_uri = __commonJS({
4090
4147
  parsed.query = matches[7];
4091
4148
  parsed.fragment = matches[8];
4092
4149
  if (parsed.scheme !== void 0) {
4093
- const decodedScheme = unescape(parsed.scheme);
4094
- if (VALID_SCHEME.test(decodedScheme)) {
4095
- parsed.scheme = decodedScheme.toLowerCase();
4150
+ if (VALID_SCHEME.test(parsed.scheme)) {
4151
+ parsed.scheme = parsed.scheme.toLowerCase();
4096
4152
  } else {
4097
4153
  parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
4098
4154
  malformedScheme = true;
@@ -4120,7 +4176,7 @@ var require_fast_uri = __commonJS({
4120
4176
  malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
4121
4177
  parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
4122
4178
  if (malformedIPLiteral) {
4123
- parsed.error = parsed.error || "URI host is malformed.";
4179
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
4124
4180
  malformedAuthorityOrPort = true;
4125
4181
  }
4126
4182
  } else {
@@ -4148,7 +4204,7 @@ var require_fast_uri = __commonJS({
4148
4204
  if (!isIP) {
4149
4205
  host = normalizePercentEncoding(host.toLowerCase());
4150
4206
  }
4151
- parsed.host = reescapeHostDelimiters(host, isIP);
4207
+ parsed.host = isIP || options.scheme !== "null" ? reescapeHostDelimiters(host, isIP) : host;
4152
4208
  }
4153
4209
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
4154
4210
  if (parsed.path) {
@@ -15084,7 +15140,7 @@ function object(shape, params) {
15084
15140
  return new ZodMiniObject(def);
15085
15141
  }
15086
15142
 
15087
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
15143
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
15088
15144
  function isZ4Schema(s) {
15089
15145
  const schema = s;
15090
15146
  return !!schema._zod;
@@ -15939,7 +15995,7 @@ function preprocess(fn, schema) {
15939
15995
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/external.js
15940
15996
  config(en_default2());
15941
15997
 
15942
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
15998
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
15943
15999
  var LATEST_PROTOCOL_VERSION = "2025-11-25";
15944
16000
  var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
15945
16001
  var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
@@ -17470,7 +17526,7 @@ var UrlElicitationRequiredError = class extends McpError {
17470
17526
  }
17471
17527
  };
17472
17528
 
17473
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
17529
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
17474
17530
  function isTerminal(status) {
17475
17531
  return status === "completed" || status === "failed" || status === "cancelled";
17476
17532
  }
@@ -18759,7 +18815,7 @@ var zodToJsonSchema = (schema, options) => {
18759
18815
  return combined;
18760
18816
  };
18761
18817
 
18762
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
18818
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
18763
18819
  function mapMiniTarget(t) {
18764
18820
  if (!t)
18765
18821
  return "draft-7";
@@ -18801,7 +18857,7 @@ function parseWithCompat(schema, data) {
18801
18857
  return result.data;
18802
18858
  }
18803
18859
 
18804
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
18860
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
18805
18861
  var DEFAULT_REQUEST_TIMEOUT_MSEC = 6e4;
18806
18862
  var Protocol = class {
18807
18863
  constructor(_options) {
@@ -19778,7 +19834,7 @@ function mergeCapabilities(base, additional) {
19778
19834
  return result;
19779
19835
  }
19780
19836
 
19781
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
19837
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
19782
19838
  var import_ajv = __toESM(require_ajv(), 1);
19783
19839
  var import_ajv_formats = __toESM(require_dist(), 1);
19784
19840
  function createDefaultAjvInstance() {
@@ -19846,7 +19902,7 @@ var AjvJsonSchemaValidator = class {
19846
19902
  }
19847
19903
  };
19848
19904
 
19849
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
19905
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
19850
19906
  var ExperimentalServerTasks = class {
19851
19907
  constructor(_server) {
19852
19908
  this._server = _server;
@@ -20059,7 +20115,7 @@ var ExperimentalServerTasks = class {
20059
20115
  }
20060
20116
  };
20061
20117
 
20062
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
20118
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
20063
20119
  function assertToolsCallTaskCapability(requests, method, entityName) {
20064
20120
  if (!requests) {
20065
20121
  throw new Error(`${entityName} does not support task creation (required for ${method})`);
@@ -20094,7 +20150,7 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
20094
20150
  }
20095
20151
  }
20096
20152
 
20097
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
20153
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
20098
20154
  var Server = class extends Protocol {
20099
20155
  /**
20100
20156
  * Initializes this server with the given name and version information.
@@ -20465,7 +20521,7 @@ var Server = class extends Protocol {
20465
20521
  }
20466
20522
  };
20467
20523
 
20468
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
20524
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
20469
20525
  var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
20470
20526
  function isCompletable(schema) {
20471
20527
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
@@ -20479,7 +20535,7 @@ var McpZodTypeKind;
20479
20535
  McpZodTypeKind2["Completable"] = "McpCompletable";
20480
20536
  })(McpZodTypeKind || (McpZodTypeKind = {}));
20481
20537
 
20482
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
20538
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
20483
20539
  var TOOL_NAME_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
20484
20540
  function validateToolName(name) {
20485
20541
  const warnings = [];
@@ -20537,7 +20593,7 @@ function validateAndWarnToolName(name) {
20537
20593
  return result.isValid;
20538
20594
  }
20539
20595
 
20540
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
20596
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
20541
20597
  var ExperimentalMcpServerTasks = class {
20542
20598
  constructor(_mcpServer) {
20543
20599
  this._mcpServer = _mcpServer;
@@ -20552,7 +20608,7 @@ var ExperimentalMcpServerTasks = class {
20552
20608
  }
20553
20609
  };
20554
20610
 
20555
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
20611
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
20556
20612
  function toolInputElementCount(value, max) {
20557
20613
  let count = 0;
20558
20614
  const stack = [value];
@@ -20601,6 +20657,15 @@ var McpServer = class {
20601
20657
  this._promptHandlersInitialized = false;
20602
20658
  this.server = new Server(serverInfo, options);
20603
20659
  this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
20660
+ if (options?.capabilities?.tools) {
20661
+ this.setToolRequestHandlers();
20662
+ }
20663
+ if (options?.capabilities?.resources) {
20664
+ this.setResourceRequestHandlers();
20665
+ }
20666
+ if (options?.capabilities?.prompts) {
20667
+ this.setPromptRequestHandlers();
20668
+ }
20604
20669
  }
20605
20670
  /**
20606
20671
  * Access experimental features.
@@ -21384,10 +21449,10 @@ var EMPTY_COMPLETION_RESULT = {
21384
21449
  }
21385
21450
  };
21386
21451
 
21387
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
21452
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
21388
21453
  import process from "node:process";
21389
21454
 
21390
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
21455
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
21391
21456
  var STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
21392
21457
  var ReadBuffer = class {
21393
21458
  constructor(options) {
@@ -21424,7 +21489,7 @@ function serializeMessage(message) {
21424
21489
  return JSON.stringify(message) + "\n";
21425
21490
  }
21426
21491
 
21427
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
21492
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
21428
21493
  var StdioServerTransport = class {
21429
21494
  constructor(_stdin = process.stdin, _stdout = process.stdout, options) {
21430
21495
  this._stdin = _stdin;
@@ -21491,7 +21556,7 @@ var StdioServerTransport = class {
21491
21556
  }
21492
21557
  };
21493
21558
 
21494
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search.js
21559
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search.js
21495
21560
  import { readFile } from "node:fs/promises";
21496
21561
 
21497
21562
  // ../../node_modules/.pnpm/minisearch@7.2.0/node_modules/minisearch/dist/es/index.js
@@ -23304,7 +23369,7 @@ var objectToNumericMapAsync = async (object3) => {
23304
23369
  var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
23305
23370
  var SPACE_OR_PUNCTUATION = /[\n\r\p{Z}\p{P}]+/u;
23306
23371
 
23307
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-guidance.js
23372
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-guidance.js
23308
23373
  var EAST_GUIDANCE = {
23309
23374
  searchTool: "mcp__plugin_east_east__search_east_examples",
23310
23375
  getTool: "mcp__plugin_east_east__get_east_example",
@@ -23346,7 +23411,7 @@ var GATE_TEXT = gateText(EAST_GUIDANCE);
23346
23411
  var READ_TEXT = readText(EAST_GUIDANCE);
23347
23412
  var isExampleCorpusRead = corpusReadFor(EAST_GUIDANCE.scope);
23348
23413
 
23349
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search.js
23414
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search.js
23350
23415
  var MIN_SCORE = 5;
23351
23416
  async function loadIndexes(indexPaths) {
23352
23417
  const entries = [];
@@ -23388,6 +23453,7 @@ function normalizePackage(filter, scopes = EAST_SCOPES) {
23388
23453
  const scope = scopes.find((s) => name.startsWith(`${s.toLowerCase()}/`));
23389
23454
  return scope === void 0 ? name : name.slice(scope.length + 1);
23390
23455
  }
23456
+ var OPT_IN_PACKAGES = /* @__PURE__ */ new Set(["east-values"]);
23391
23457
  function searchExamples(index, request) {
23392
23458
  const known = index.packages;
23393
23459
  const filter = request.package;
@@ -23397,7 +23463,7 @@ function searchExamples(index, request) {
23397
23463
  }
23398
23464
  const options = {
23399
23465
  limit: request.limit,
23400
- ...pkg === void 0 ? {} : { filter: (r) => r.package === pkg || r.skill === pkg }
23466
+ filter: (r) => OPT_IN_PACKAGES.has(r.package) ? r.package === pkg : pkg === void 0 || r.package === pkg || r.skill === pkg
23401
23467
  };
23402
23468
  const hits = index.search.search(request.query, options);
23403
23469
  return { entries: hits.filter((r) => r.score >= MIN_SCORE).slice(0, request.limit), known };
@@ -23471,7 +23537,7 @@ function getEntry(index, id) {
23471
23537
  return stored === void 0 ? null : stored;
23472
23538
  }
23473
23539
 
23474
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/mcp/example-tools.js
23540
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/mcp/example-tools.js
23475
23541
  var EAST_PACKAGES = ["east", "east-node-std", "east-node-io", "east-py-datascience", "east-ui", "e3-ui", "e3", "e3-ui-cli", "e3-create"];
23476
23542
  function isLoaded(index) {
23477
23543
  return typeof index.then === "function";
@@ -23492,7 +23558,7 @@ function registerExampleTools(server2, options) {
23492
23558
  language: languageArg,
23493
23559
  format: external_exports.enum(["summary", "full"]).default("summary").describe(`"summary" (default) lists the hits in one line each; "full" includes each hit's code in the requested language`),
23494
23560
  limit: external_exports.number().int().min(1).max(20).default(5).describe("Maximum number of results to return (default 5, max 20)"),
23495
- package: external_exports.string().optional().describe(`Filter results to one package by its bare name \u2014 ${packages.join(", ")} \u2014 ${scopesNamed}; an unknown name is reported with the indexed names`)
23561
+ package: external_exports.string().optional().describe(`Filter results to one package by its bare name \u2014 ${packages.join(", ")} \u2014 ${scopesNamed}; an unknown name is reported with the indexed names${packages.includes("east-values") ? '. "east-values" is east-py code working with East values directly (eager methods on python values, not an East function), and is searched only when named here' : ""}`)
23496
23562
  }, async ({ query, language, format, limit, package: packageFilter }) => {
23497
23563
  const index = await indexPromise;
23498
23564
  const { entries, unknownPackage, known } = searchExamples(index, { query, limit, package: packageFilter, scopes });
@@ -23544,7 +23610,7 @@ function registerExampleTools(server2, options) {
23544
23610
  import { fileURLToPath } from "node:url";
23545
23611
  var E3_CLOUD_INDEX_PATH = fileURLToPath(new URL("../../index.json", import.meta.url));
23546
23612
 
23547
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.94_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/transcript.js
23613
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.100_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/transcript.js
23548
23614
  var SEARCH_TOOLS = ["mcp__plugin_east_east__search_east_examples", "mcp__plugin_east_east__get_east_example", "mcp__east__search_east_examples", "mcp__east__get_east_example"];
23549
23615
 
23550
23616
  // ../e3-cloud-plugin/dist/lib/names.js
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "e3-cloud",
3
- "version": "1.0.94",
3
+ "version": "1.1.0",
4
4
  "description": "e3 cloud - apps for hosted e3 workspaces with @elaraai/e3-cloud-ui, files from buckets as inputs with @elaraai/e3-cloud-files, the e3-cloud CLI and @elaraai/e3-cloud-client: skills, example search and the search-first hooks",
5
5
  "author": {
6
6
  "name": "Elara AI",
package/README.md CHANGED
@@ -66,8 +66,9 @@ reminded that the index is the same code, cheaper.
66
66
 
67
67
  ## Version
68
68
 
69
- The @elaraai lockstep, as every e3 cloud package's: the release of e3 cloud's
70
- packages is the release of this plugin.
69
+ e3 cloud's version, as every e3 cloud package's: the release of e3 cloud's
70
+ packages is the release of this plugin, and documents the packages of its
71
+ version.
71
72
 
72
73
  ## Developing it
73
74
 
package/index.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "version": 2,
3
3
  "stats": {
4
- "totalEntries": 38,
4
+ "totalEntries": 44,
5
5
  "totalFiles": 2,
6
6
  "packages": {
7
7
  "e3-cloud-ui": 12,
8
- "e3-cloud-files": 10,
9
- "e3-cloud-cli": 12,
10
- "e3-cloud-client": 4
8
+ "e3-cloud-files": 14,
9
+ "e3-cloud-cli": 13,
10
+ "e3-cloud-client": 5
11
11
  }
12
12
  },
13
13
  "entries": [
@@ -183,7 +183,7 @@
183
183
  "package": "e3-cloud-ui",
184
184
  "file": "packages/e3-cloud-ui/test/pages.examples.tsx",
185
185
  "suite": "Functions and records",
186
- "test": "Changing a record from a page: Record.bind(orders, [approve]) names the mutations the page may run, and o.mutate.approve(id) commits one",
186
+ "test": "Changing a record from a page: Record.bind(orders, ['approve']) names the mutations the page may run, by the names the record declares them under, and o.mutate.approve(id) commits one",
187
187
  "keywords": [
188
188
  "Record.bind",
189
189
  "mutate",
@@ -203,7 +203,7 @@
203
203
  "languages": [
204
204
  "tsx"
205
205
  ],
206
- "source": "// Changing a record from a page: Record.bind(orders, [approve]) names the mutations the page may run, and o.mutate.approve(id) commits one\n// inputs: []\n/** The runtime the package's functions run on. */\nconst RUNNER: Runner = { runtime: 'east-c', platforms: ['east-c-std'] };\n\nconst Order = StructType({ line: StringType, qty: FloatType, status: StringType });\n\n/** A record of orders, changed only through its mutations. */\nconst orders = e3.record('orders', DictType(StringType, Order), new Map());\n\n/** Approves an order by its id. */\nconst approve = e3.mutation.reduce('approve', orders,\n East.function([DictType(StringType, Order), StringType], DictType(StringType, Order), (_$, all, _id) => all),\n { runner: RUNNER });\n\nEast.function([], UIComponentType, _$ => (\n <Reactive>{$ => {\n const o = $.let(Record.bind(orders, [approve]));\n const approveA17 = $.const(East.function([], NullType, $ => { $(o.mutate.approve('A-17')); }));\n return <Button onClick={approveA17}>Approve A-17</Button>;\n }}</Reactive>\n ))"
206
+ "source": "// Changing a record from a page: Record.bind(orders, ['approve']) names the mutations the page may run, by the names the record declares them under, and o.mutate.approve(id) commits one\n// inputs: []\n/** The runtime the package's functions run on. */\nconst RUNNER: Runner = { runtime: 'east-c', platforms: ['east-c-std'] };\n\nconst Order = StructType({ line: StringType, qty: FloatType, status: StringType });\n\n/** A record of orders, changed only through its mutations, declared whole with its index and its mutation. */\nconst orders = e3.record('orders', DictType(StringType, Order), {\n initial: new Map(),\n indexes: (index) => ({\n /** The orders by line. */\n by_line: index.key(East.function([StringType, Order], StringType, (_$, _id, o) => o.line)),\n }),\n mutations: (mutation) => ({\n /** Approves an order by its id. */\n approve: mutation.reduce(\n East.function([DictType(StringType, Order), StringType], DictType(StringType, Order), (_$, all, _id) => all),\n { runner: RUNNER }),\n }),\n});\n\nEast.function([], UIComponentType, _$ => (\n <Reactive>{$ => {\n const o = $.let(Record.bind(orders, ['approve']));\n const approveA17 = $.const(East.function([], NullType, $ => { $(o.mutate.approve('A-17')); }));\n return <Button onClick={approveA17}>Approve A-17</Button>;\n }}</Reactive>\n ))"
207
207
  },
208
208
  {
209
209
  "id": "e3-cloud-ui:pages.examples.tsx:pageReadsARecordThroughAnIndex",
@@ -211,11 +211,11 @@
211
211
  "package": "e3-cloud-ui",
212
212
  "file": "packages/e3-cloud-ui/test/pages.examples.tsx",
213
213
  "suite": "Functions and records",
214
- "test": "Reading a record through one of its indexes, a page at a time: Data.bindPaged(orders, { index: byLine }) given the index's declaration, its rows the index key and the order's id",
214
+ "test": "Reading a record through one of its indexes, a page at a time: Data.bindPaged(orders, { index: 'by_line' }) given the name the record declares the index under, its rows the index key and the order's id",
215
215
  "keywords": [
216
216
  "Data.bindPaged",
217
217
  "index",
218
- "recordIndex",
218
+ "indexes",
219
219
  "record",
220
220
  "paged",
221
221
  "Table",
@@ -232,7 +232,7 @@
232
232
  "languages": [
233
233
  "tsx"
234
234
  ],
235
- "source": "// Reading a record through one of its indexes, a page at a time: Data.bindPaged(orders, { index: byLine }) given the index's declaration, its rows the index key and the order's id\n// inputs: []\nconst Order = StructType({ line: StringType, qty: FloatType, status: StringType });\n\n/** A record of orders, changed only through its mutations. */\nconst orders = e3.record('orders', DictType(StringType, Order), new Map());\n\n/** The orders by line. */\nconst byLine = e3.recordIndex('by_line', orders,\n { key: East.function([StringType, Order], StringType, (_$, _id, o) => o.line) });\n\nEast.function([], UIComponentType, _$ => (\n <Reactive>{$ => {\n const mine = $.let(Data.bindPaged(orders, { index: byLine }));\n return <Table data={mine} columns={['ik', 'key']} />;\n }}</Reactive>\n ))"
235
+ "source": "// Reading a record through one of its indexes, a page at a time: Data.bindPaged(orders, { index: 'by_line' }) given the name the record declares the index under, its rows the index key and the order's id\n// inputs: []\n/** The runtime the package's functions run on. */\nconst RUNNER: Runner = { runtime: 'east-c', platforms: ['east-c-std'] };\n\nconst Order = StructType({ line: StringType, qty: FloatType, status: StringType });\n\n/** A record of orders, changed only through its mutations, declared whole with its index and its mutation. */\nconst orders = e3.record('orders', DictType(StringType, Order), {\n initial: new Map(),\n indexes: (index) => ({\n /** The orders by line. */\n by_line: index.key(East.function([StringType, Order], StringType, (_$, _id, o) => o.line)),\n }),\n mutations: (mutation) => ({\n /** Approves an order by its id. */\n approve: mutation.reduce(\n East.function([DictType(StringType, Order), StringType], DictType(StringType, Order), (_$, all, _id) => all),\n { runner: RUNNER }),\n }),\n});\n\nEast.function([], UIComponentType, _$ => (\n <Reactive>{$ => {\n const mine = $.let(Data.bindPaged(orders, { index: 'by_line' }));\n return <Table data={mine} columns={['ik', 'key']} />;\n }}</Reactive>\n ))"
236
236
  },
237
237
  {
238
238
  "id": "e3-cloud-files:decode.examples.ts:fileAsJson",
@@ -314,6 +314,34 @@
314
314
  ],
315
315
  "source": "// A text file input's lines, trimmed, blank ones left out\n// inputs: [utf8('first\\n\\n second \\nthird\\n')] returns: ['first', 'second', 'third']\nEast.function([BlobType], ArrayType(StringType), (_$, file) =>\n file.decodeUtf8().split('\\n').map((_$, line) => line.trim()).filter((_$, line) => line.notEquals('')))"
316
316
  },
317
+ {
318
+ "id": "e3-cloud-files:decode.examples.ts:typedFileAsItsValue",
319
+ "skill": "e3-cloud-files",
320
+ "package": "e3-cloud-files",
321
+ "file": "packages/e3-cloud-files/test/decode.examples.ts",
322
+ "suite": "A typed file: no decoding",
323
+ "test": "A typed input needs no decoding: what upload.file('prices', { key: 'prices.beast2', type: ArrayType(Price) }) fills is the beast2 file's value itself, an Array<Price> the task takes as any dataset, here counted",
324
+ "keywords": [
325
+ "Files",
326
+ "typed",
327
+ "type",
328
+ "beast2",
329
+ "file",
330
+ "latest",
331
+ "no decoding",
332
+ "ArrayType",
333
+ "size",
334
+ "task"
335
+ ],
336
+ "imports": [
337
+ "import { East, ArrayType, BlobType, DateTimeType, FloatType, IntegerType, StringType, StructType, example } from '@elaraai/east';",
338
+ "import { FilesFolderType, FilesListingType } from '@elaraai/e3-cloud-files';"
339
+ ],
340
+ "languages": [
341
+ "typescript"
342
+ ],
343
+ "source": "// A typed input needs no decoding: what upload.file('prices', { key: 'prices.beast2', type: ArrayType(Price) }) fills is the beast2 file's value itself, an Array<Price> the task takes as any dataset, here counted\n// inputs: [[{ sku: 'A1', price: 1.5 }, { sku: 'B2', price: 2.25 }, { sku: 'C3', price: 9 }]] returns: 3n\nconst Price = StructType({ sku: StringType, price: FloatType });\n\nEast.function([ArrayType(Price)], IntegerType, (_$, prices) => prices.size())"
344
+ },
317
345
  {
318
346
  "id": "e3-cloud-files:decode.examples.ts:folderAsOneArray",
319
347
  "skill": "e3-cloud-files",
@@ -592,6 +620,109 @@
592
620
  "typescript"
593
621
  ]
594
622
  },
623
+ {
624
+ "id": "e3-cloud-files:static:typed-input",
625
+ "skill": "e3-cloud-files",
626
+ "package": "e3-cloud-files",
627
+ "file": "static",
628
+ "suite": "Files",
629
+ "test": "A beast2 file as an input of its type with no task in between: file or latest with a type, a collection of any size, its header checked before any other byte, a file of another type refused naming both types, the same bytes under a new ETag changing nothing",
630
+ "keywords": [
631
+ "Files",
632
+ "type",
633
+ "beast2",
634
+ "typed",
635
+ "typed input",
636
+ "ArrayType",
637
+ "upload.file",
638
+ "upload.latest",
639
+ "encodeBeast2For",
640
+ "Beast2ElementWriter",
641
+ "collection",
642
+ "type_mismatch",
643
+ "not_beast2",
644
+ "e3-cloud-files"
645
+ ],
646
+ "imports": [
647
+ "import { East, ArrayType, FloatType, IntegerType, StringType, StructType } from '@elaraai/east';",
648
+ "import e3, { type Runner } from '@elaraai/e3';",
649
+ "import { Files } from '@elaraai/e3-cloud-files';"
650
+ ],
651
+ "source": "// src/package.ts\nconst Price = StructType({ sku: StringType, price: FloatType });\nexport const upload = Files.source('upload');\n// A beast2 file of Array<Price> (encodeBeast2For(ArrayType(Price)), a Beast2ElementWriter for a collection of any size, east-py, e3 convert):\n// an input of that type, taken in as the value it holds, its header checked before any other byte is read; a file of another type is\n// refused naming both types (type_mismatch), no beast2 at all as not_beast2, and the input keeps its value. The same bytes again under\n// a new ETag give the same value: nothing downstream runs\nexport const prices = upload.file('prices', { key: 'prices.beast2', type: ArrayType(Price) });\nexport const stock = upload.latest('stock', { prefix: 'stock/', include: '*.beast2', type: ArrayType(Price) }); // the greatest key, typed\nconst runner: Runner = { runtime: 'east-c', platforms: ['east-c-std'] };\n// Used as any dataset: no decoding, the value is the file's\nexport const summary = e3.task('price_summary', [prices, stock], East.function([ArrayType(Price), ArrayType(Price)], IntegerType, (_$, rows, latest) => rows.size().add(latest.size())), { runner });\nexport default e3.package('typed', '1.0.0', summary);\n\n// src/files.ts\nexport default Files.manifest([upload]);",
652
+ "languages": [
653
+ "typescript"
654
+ ]
655
+ },
656
+ {
657
+ "id": "e3-cloud-files:static:record-seed",
658
+ "skill": "e3-cloud-files",
659
+ "package": "e3-cloud-files",
660
+ "file": "static",
661
+ "suite": "Files",
662
+ "test": "A record seeded from a file: upload.record(name, type, spec, parts) is e3.record(name, type, parts) with no initial value, declared whole with its indexes, mutations and migrations, pending until its file arrives (its first state, $init), seeded again by a changed file while nothing has mutated it since ($seed) or on --reset, kept once mutated; the tasks over it wait meanwhile, and its history names the file",
663
+ "keywords": [
664
+ "Files",
665
+ "record",
666
+ "seed",
667
+ "seeded",
668
+ "upload.record",
669
+ "pending",
670
+ "$init",
671
+ "$seed",
672
+ "--reset",
673
+ "genesis",
674
+ "no initial value",
675
+ "e3.record",
676
+ "declared whole",
677
+ "mutations",
678
+ "kept",
679
+ "waiting",
680
+ "e3-cloud-files"
681
+ ],
682
+ "imports": [
683
+ "import { East, DictType, IntegerType, StringType, StructType } from '@elaraai/east';",
684
+ "import e3, { type Runner } from '@elaraai/e3';",
685
+ "import { Files } from '@elaraai/e3-cloud-files';"
686
+ ],
687
+ "source": "// src/package.ts\nconst Customer = StructType({ name: StringType, tier: IntegerType });\nconst CustomerBook = DictType(StringType, Customer);\nexport const upload = Files.source('upload');\n// e3.record('customers', CustomerBook, parts) with no initial value: pending until customers.beast2 arrives, which is its first state ($init:\n// nothing runs on an empty state first, and the tasks over it wait as on an unset input, the run completing without them); a changed\n// file while nothing has mutated it since is its state again ($seed); once mutated it is kept, until `files sync --reset customers`\n// (or `files push --reset customers`) takes the file as its state over whatever was done. The workspace page shows it Pending, its page\n// what seeds it, `files status` prints pending; its history reads \"Initialised from customers.beast2 (2 rows)\" and \"Seeded again from …\"\n// Declared whole, as any record is: its indexes, mutations and migrations are the fourth argument, as e3.record takes them (an initial\n// value there is refused). Its mutations are any record's: after one, the file no longer seeds it until a reset\nexport const customers = upload.record('customers', CustomerBook, { key: 'customers.beast2' }, {\n mutations: (mutation) => ({\n set_tier: mutation.reduce(East.function([CustomerBook, StringType, IntegerType], CustomerBook, ($, state, key, tier) => {\n const next = $.let(state.copy());\n $(next.insertOrUpdate(key, { name: key, tier }, (_$, existing, _given) => ({ name: existing.name, tier })));\n return next;\n })),\n }),\n});\nconst runner: Runner = { runtime: 'east-c', platforms: ['east-c-std'] };\nexport const customerCount = e3.task('customer_count', [customers], East.function([CustomerBook], IntegerType, (_$, book) => book.size()), { runner });\n// The record carries its mutation: the package takes the task over it\nexport default e3.package('typed', '1.0.0', customerCount);\n\n// src/files.ts\nexport default Files.manifest([upload]);\n\n// A deploy that cannot keep the record (its type changed) under --schema reset unassigns it: pending again, seeded by the next\n// version's file once the new declarations are deployed",
688
+ "languages": [
689
+ "typescript"
690
+ ]
691
+ },
692
+ {
693
+ "id": "e3-cloud-files:static:mutation-feed",
694
+ "skill": "e3-cloud-files",
695
+ "package": "e3-cloud-files",
696
+ "file": "static",
697
+ "suite": "Files",
698
+ "test": "A record's mutation fed by files: upload.mutation(def, spec), def one the record declares (priceBook.mutations.load_prices), applies each new file the spec selects, in key order, once by its bytes' hash, as the mutation's one argument; a refused file is tried again only once it changes, a feed of a pending record waits, and each commit names its file",
699
+ "keywords": [
700
+ "Files",
701
+ "mutation",
702
+ "feed",
703
+ "fed",
704
+ "upload.mutation",
705
+ "mutation.reduce",
706
+ "mutations",
707
+ "one argument",
708
+ "idempotent",
709
+ "once per file",
710
+ "key order",
711
+ "refused",
712
+ "waiting",
713
+ "deltas",
714
+ "e3-cloud-files"
715
+ ],
716
+ "imports": [
717
+ "import { East, ArrayType, DictType, FloatType, StringType, StructType } from '@elaraai/east';",
718
+ "import e3 from '@elaraai/e3';",
719
+ "import { Files } from '@elaraai/e3-cloud-files';"
720
+ ],
721
+ "source": "// src/package.ts\nconst Price = StructType({ sku: StringType, price: FloatType });\nconst PriceBook = DictType(StringType, FloatType);\nexport const upload = Files.source('upload');\n// A record the app's users edit, declared whole with a mutation of one argument that loads a delivery into it: the record's own\n// reduce (or edit; a patch cannot be fed), its rules deciding what a delivery does. A delivery holding a price below zero is refused\n// whole: the mutation fails, the book is kept, and the file is tried again only once it changes, the files after it waiting\nexport const priceBook = e3.record('price_book', PriceBook, {\n initial: new Map(),\n mutations: (mutation) => ({\n load_prices: mutation.reduce(East.function([PriceBook, ArrayType(Price)], PriceBook, ($, state, rows) => {\n const next = $.let(state.copy()); // the book as it is (a mutation's state is immutable), each delivered price over its sku's\n $.for(rows, ($, row) => {\n $.if(row.price.lessThan(0), ($) => $.error(East.str`a price below zero: ${row.sku}`));\n $(next.insertOrUpdate(row.sku, row.price, (_$, _existing, delivered) => delivered));\n });\n return next;\n })),\n }),\n});\n// The record carries its mutation: the package takes the record\nexport default e3.package('typed', '1.0.0', priceBook);\n\n// src/files.ts: each new delta file under deltas/ calls load_prices once with its value, by the file's hash (a sync run again\n// applies nothing twice; the same bytes under another name are applied no more); a feed of a record with no state yet waits for\n// it; a sync seeds records before it feeds mutations. The commit reads in the record's history as any mutation's, through the\n// files channel, naming the file it was fed\nupload.mutation(priceBook.mutations.load_prices, { prefix: 'deltas/', include: '*.beast2' });\nexport default Files.manifest([upload]);",
722
+ "languages": [
723
+ "typescript"
724
+ ]
725
+ },
595
726
  {
596
727
  "id": "e3-cloud-files:static:test-sync",
597
728
  "skill": "e3-cloud-files",
@@ -875,7 +1006,10 @@
875
1006
  "restore",
876
1007
  "backup-pull",
877
1008
  "commit",
878
- "audit"
1009
+ "audit",
1010
+ "pending",
1011
+ "$seed",
1012
+ "seeded from a file"
879
1013
  ],
880
1014
  "imports": [],
881
1015
  "source": "e3-cloud records list https://e3.example.com/repos/acme production\ne3-cloud records history https://e3.example.com/repos/acme production orders --limit 50\ne3-cloud records show https://e3.example.com/repos/acme production orders <commit> -o orders.beast2\ne3-cloud records backup https://e3.example.com/repos/acme production orders --note \"before the import\"\ne3-cloud records rollback https://e3.example.com/repos/acme production orders <commit> # a new commit holding that state",
@@ -925,12 +1059,41 @@
925
1059
  "--profile",
926
1060
  "--latest",
927
1061
  "--dry-run",
928
- "--input",
929
1062
  "e3 dataflow run",
1063
+ "e3-cloud",
1064
+ "--target",
1065
+ "--reset"
1066
+ ],
1067
+ "imports": [],
1068
+ "source": "e3 workspace deploy ./repo dev --from-source src/package.ts\ne3-cloud files deploy ./repo dev src/files.ts --dry-run # checked against the package dev runs; changes nothing\ne3-cloud files deploy ./repo dev src/files.ts\ne3-cloud files bind ./repo dev upload s3://partner-drop/orders/ --profile partner --latest 30 # locally: your own profile, each folder's 30 newest\ne3-cloud files sync ./repo dev --dry-run # what a sync would take\ne3-cloud files sync ./repo dev # reads only files whose ETag or size changed\ne3-cloud files status ./repo dev # each mapping, its type, what it holds (pending for an unseeded record), bindings, the last sync\ne3 dataflow run ./repo dev # a sync doesn't run the dataflow\n# <repo> may be a repository URL instead (https://e3.example.com/repos/acme): the same commands, run by the platform",
1069
+ "languages": [
1070
+ "typescript"
1071
+ ]
1072
+ },
1073
+ {
1074
+ "id": "e3-cloud-cli:static:files-push",
1075
+ "skill": "e3-cloud-cli",
1076
+ "package": "e3-cloud-cli",
1077
+ "file": "static",
1078
+ "suite": "e3-cloud files",
1079
+ "test": "Pushing files from disk into a source and taking them in: each file matched to the mapping that takes it (by its name, or --as), checked against its type and limit, put with your e3 login alone where the source is the deployment's own bucket, then synced; unchanged once it is there; --dry-run plans and puts nothing",
1080
+ "keywords": [
1081
+ "files",
1082
+ "push",
1083
+ "upload",
1084
+ "put",
1085
+ "--as",
1086
+ "--dry-run",
1087
+ "--profile",
1088
+ "--reset",
1089
+ "--target",
1090
+ "beast2",
1091
+ "typed",
1092
+ "unchanged",
930
1093
  "e3-cloud"
931
1094
  ],
932
1095
  "imports": [],
933
- "source": "e3 workspace deploy ./repo dev --from-source src/package.ts\ne3-cloud files deploy ./repo dev src/files.ts --dry-run # checked against the package dev runs; changes nothing\ne3-cloud files deploy ./repo dev src/files.ts\ne3-cloud files bind ./repo dev upload s3://partner-drop/orders/ --profile partner --latest 30 # locally: your own profile, each folder's 30 newest\ne3-cloud files sync ./repo dev --dry-run # what a sync would take\ne3-cloud files sync ./repo dev # reads only files whose ETag or size changed\ne3-cloud files status ./repo dev # declarations, bindings, the last sync\ne3 dataflow run ./repo dev # a sync doesn't run the dataflow\n# <repo> may be a repository URL instead (https://e3.example.com/repos/acme): the same commands, run by the platform",
1096
+ "source": "# A file by its name (a file mapping's key, or the newest under a latest prefix), a directory to a folder mapping; a row per file\ne3-cloud files push https://e3.example.com/repos/acme dev ./data/prices.beast2 ./data/deltas/2026-10-19.beast2\n# prices.beast2 → prices Array<Struct{sku, price}> 38 MiB put taken in 412,310 rows\n# 2026-10-19.beast2 → mutation:price_book.load_prices load_prices 1 MiB put committed 9f2c1a0b7e3d\n# Next: e3 dataflow run https://e3.example.com/repos/acme dev\ne3-cloud files push ./repo dev ./data/orders/ --as orders # a directory's files into the orders folder, each at its path\ne3-cloud files push ./repo dev ./data/prices.beast2 --dry-run # the plan alone: put, unchanged or refused, and why\ne3-cloud files push ./repo dev ./data/x.beast2 --profile partner # a bucket the platform does not write: your own credentials\ne3-cloud files sync ./repo dev --target prices --target mutation:price_book.load_prices # only these targets\ne3-cloud files sync ./repo dev --reset price_book # a seeded record's file taken as its state again, mutations since or not",
934
1097
  "languages": [
935
1098
  "typescript"
936
1099
  ]
@@ -1046,6 +1209,45 @@
1046
1209
  "languages": [
1047
1210
  "typescript"
1048
1211
  ]
1212
+ },
1213
+ {
1214
+ "skill": "e3-cloud-client",
1215
+ "package": "e3-cloud-client",
1216
+ "file": "static",
1217
+ "id": "e3-cloud-client:static:files",
1218
+ "suite": "@elaraai/e3-cloud-client files",
1219
+ "test": "Files from code: deploy a workspace's declarations, bind a source, sync named targets (a seeded record reset) and wait for the sync's report, read what each mapping holds, and plan a push of files from disk",
1220
+ "keywords": [
1221
+ "getFiles",
1222
+ "deployFiles",
1223
+ "bindFilesSource",
1224
+ "startFilesSync",
1225
+ "filesSyncWait",
1226
+ "planFilesPush",
1227
+ "completeFilesPush",
1228
+ "FilesTarget",
1229
+ "targets",
1230
+ "reset",
1231
+ "pending",
1232
+ "seeded",
1233
+ "record",
1234
+ "mutation",
1235
+ "push",
1236
+ "unchanged",
1237
+ "refused",
1238
+ "retaken",
1239
+ "workspace_protected"
1240
+ ],
1241
+ "imports": [
1242
+ "import { none, some, variant } from '@elaraai/east';",
1243
+ "import { bindFilesSource, completeFilesPush, deployFiles, filesSyncWait, getFiles, planFilesPush, startFilesSync } from '@elaraai/e3-cloud-client';",
1244
+ "import { filesPushRefusalText, filesTargetText } from '@elaraai/e3-cloud-types';",
1245
+ "import manifest from './files.js'; // export default Files.manifest([upload])"
1246
+ ],
1247
+ "source": "// The declarations (checked against the package the workspace runs), then where the source's files are\nconst deployed = await deployFiles(url, 'acme', 'staging', manifest, options);\nif (deployed.type === 'refused') throw new Error(deployed.value.problems.map((p) => p.type).join(', '));\nawait bindFilesSource(url, 'acme', 'staging', { source: 'upload', url: 's3://partner-drop/acme/', region: none, profile: none, latest: none }, options);\n\n// A sync of two targets: the seeded record's file taken as its state again (reset), and the fed mutation's new files\nconst start = await startFilesSync(url, 'acme', 'staging', {\n targets: [variant('record', 'customers'), variant('mutation', { record: 'price_book', mutation: 'load_prices' })],\n reset: ['customers'],\n}, options);\nconst id = start.value; // started, or running: the sync under way, waited on the same way\nconst job = await filesSyncWait(url, 'acme', 'staging', id, options, { deadlineMs: 10 * 60_000 });\nfor (const m of job.report.type === 'some' ? job.report.value.mappings : []) {\n console.log(filesTargetText(m.target), m.updated ? `written (commit ${m.commit.type === 'some' ? m.commit.value.slice(0, 12) : 'none'})` : 'as it was', m.refused.length === 0 ? '' : `refused ${m.refused[0]!.key}`);\n}\n\n// What each mapping holds now; a record not yet seeded is pending\nconst files = await getFiles(url, 'acme', 'staging', options);\nconsole.log(files.status.mappings.map((m) => `${filesTargetText(m.target)}: ${m.pending ? 'pending' : m.holds.type === 'some' ? m.holds.value.key : 'nothing yet'}`).join(' · '));\n\n// A push from disk: the plan says per file where it goes and how to upload it (a single PUT here); then sync what was put\nconst plan = await planFilesPush(url, 'acme', 'staging', { files: [{ name: 'customers.beast2', directory: none, size: 4096n, sha256, type: some(customerBookType) }], as: none }, options);\nfor (const f of plan.files) {\n if (f.outcome.type === 'refused') console.warn(f.name, filesPushRefusalText(f.outcome.value));\n else if (f.outcome.type === 'put' && f.outcome.value.upload.type === 'single') {\n await fetch(f.outcome.value.upload.value.url, { method: 'PUT', body: bytes, headers: Object.fromEntries(f.outcome.value.upload.value.headers) });\n } else if (f.outcome.type === 'put' && f.outcome.value.upload.type === 'multipart') {\n const parts = []; // each part PUT to its url, its ETag kept\n await completeFilesPush(url, 'acme', 'staging', { key: f.outcome.value.key, uploadId: f.outcome.value.upload.value.uploadId, parts }, options);\n }\n}",
1248
+ "languages": [
1249
+ "typescript"
1250
+ ]
1049
1251
  }
1050
1252
  ]
1051
1253
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elaraai/e3-cloud-claude-plugin",
3
- "version": "1.0.94",
3
+ "version": "1.1.0",
4
4
  "description": "e3 cloud - apps for hosted e3 workspaces with @elaraai/e3-cloud-ui, files from buckets as inputs with @elaraai/e3-cloud-files, the e3-cloud CLI and @elaraai/e3-cloud-client: skills, example search and the search-first hooks",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -159,9 +159,9 @@ e3 dataflow run URL staging
159
159
 
160
160
  ### 5. Migrate a record
161
161
 
162
- A record's new type comes with a migration in the package: e3's
163
- `e3.migration.value`, `rows` or `rekey`, each step naming the one before it
164
- with `after` (the e3 skill). Then, in staging:
162
+ A record's new type comes with a migration in its own declaration: a step
163
+ added at the end of its `migrations` (`migration.value`, `rows` or `rekey`,
164
+ each named, the list's order the chain; the e3 skill). Then, in staging:
165
165
 
166
166
  ```bash
167
167
  e3-cloud records backup URL staging orders --note "before orders v2"
@@ -189,6 +189,8 @@ e3-cloud release URL staging production --plan
189
189
  e3-cloud files deploy URL staging src/files.ts
190
190
  e3-cloud files bind URL staging upload s3://bucket/acme/staging/
191
191
  e3-cloud files sync URL staging
192
+ e3-cloud files push URL staging ./data/prices.beast2 ./data/customers.beast2 # from disk: matched by name (or --as), put, synced
193
+ e3-cloud files sync URL staging --target record:customers --reset customers # named targets; a seeded record's file as its state again
192
194
  e3 dataflow run URL staging
193
195
  ```
194
196
 
@@ -313,8 +315,9 @@ e3-cloud app deploy URL staging --from-source src/apps/ops.tsx --for-platform 1.
313
315
  e3-cloud release URL staging production
314
316
  ```
315
317
 
316
- - Build with the next release's packages and stage the bundle beside the
317
- live app: it goes live when the platform moves to that e3. A release
318
+ - Build with the e3 cloud release built with the next e3 (its packages pin
319
+ it) and stage the bundle beside the live app: it goes live when the
320
+ platform moves to that e3. A release
318
321
  carries staging's staged bundles, so production gets them through its
319
322
  staging.
320
323
 
@@ -409,7 +412,7 @@ a repository URL:
409
412
  changes them, and a source may be bound before a release brings the
410
413
  declarations that read it.
411
414
  - `files sync <repo> <ws>` reads only files whose ETag or size changed and
412
- writes only inputs whose files changed; `--input`, `--dry-run`. It doesn't
415
+ writes only inputs whose files changed; `--target`, `--reset`, `--dry-run`. It doesn't
413
416
  run the dataflow: `e3 dataflow run` does, and the sync prints the command.
414
417
  - `files status <repo> <ws>`: the declarations, the bindings, the last sync.
415
418
 
@@ -427,5 +430,6 @@ a repository URL:
427
430
  hasn't bound: bind it in the target first.
428
431
  - `secrets set` reads the value from stdin unless `--value` is given; the
429
432
  next run reads the new value.
430
- - The version is the @elaraai lockstep: use the CLI of the release your
431
- deployment runs.
433
+ - The version is e3 cloud's own, not e3's: use the CLI of the e3 cloud
434
+ release your deployment runs, beside e3's CLI of the e3 that release is
435
+ built with (its dependencies pin it).
@@ -67,10 +67,44 @@ try {
67
67
  and its `undoes` lists forced changes it undoes. `rollbackWorkspace` takes
68
68
  an entry of `workspaceHistory`.
69
69
 
70
+ ## Files
71
+
72
+ - `getFiles(url, repo, ws, options)` answers a workspace's Files: its
73
+ declarations (`manifest`), where each source is bound (`bindings`), the
74
+ last sync, a sync under way, and `status` — per mapping, what it holds and
75
+ whether its record is `pending` (seeded from a file, its file not yet
76
+ arrived).
77
+ - `deployFiles(url, repo, ws, manifest, options `{ dryRun?, force? }`)` deploys declarations
78
+ (`Files.manifest(...)` from `@elaraai/e3-cloud-files`): `deployed` with
79
+ the targets the next sync takes again whole (`retaken`), `checked` for a
80
+ dry run, or `refused` with every problem. A workspace with a staging
81
+ refuses it (`workspace_protected`) unless an admin forces it.
82
+ - `bindFilesSource(url, repo, ws, binding, options)` says where a source's
83
+ files are (`s3://bucket/prefix/`): `bound` with what the next sync takes
84
+ again, or `refused` (a bucket the deployment does not let the repository
85
+ bind). Bindings are each workspace's own; a release never changes them.
86
+ - `startFilesSync(url, repo, ws, { dryRun?, targets?, reset? }, options)`
87
+ answers `started` with the sync's id, or `running` with the one under way;
88
+ `filesSyncWait(url, repo, ws, id, options, { deadlineMs?, refresh?, signal? })`
89
+ polls it to its end and answers the job, its `report` per target (what was
90
+ taken, the commit a seed or feed made, what was refused and why, what
91
+ waits). `targets` are `variant('input', name)`, `variant('record', name)`
92
+ or `variant('mutation', { record, mutation })`; `reset` names seeded
93
+ records whose file is taken as their state again.
94
+ - `planFilesPush(url, repo, ws, { files, as }, options)` plans a push of
95
+ files from disk (each offered with its name, size, SHA-256 and beast2
96
+ header type): per file `put` with its key and how to upload it — `single`
97
+ (one PUT with the plan's headers), `multipart` (a URL per part, then
98
+ `completeFilesPush(url, repo, ws, { key, uploadId, parts }, options)`) or
99
+ `external` (your own credentials) — `unchanged`, or `refused` with why
100
+ (`filesPushRefusalText`). Then sync the targets put. The CLI's
101
+ `files push` does all of this.
102
+
70
103
  ## Traps
71
104
 
72
105
  - The URL is the deployment's origin, not `…/api`: the client adds the path.
73
106
  - A refusal of a release or an app deploy is an answer (`refused`), not a
74
107
  throw; a request the server cannot take at all throws `ApiError`.
75
- - The version is the @elaraai lockstep: use the client of the release the
76
- deployment runs.
108
+ - The version is e3 cloud's own, shared by every e3 cloud package and
109
+ independent of e3's: use the client of the e3 cloud release the deployment
110
+ runs, beside the east and e3 it pins.
@@ -1,16 +1,18 @@
1
1
  ---
2
2
  name: e3-cloud-files
3
- description: "Files for e3 cloud — a solution's inputs that come from files in a bucket (s3://bucket/prefix/), declared in its code and synced when asked, the same on a local repository and a deployed workspace. Use when: (1) Declaring which inputs a bucket's files fill with Files.source(name) and its file, latest, folder and listing, then Files.manifest([...]) as a module's default export, (2) Decoding what arrives in a task: a Blob (file, latest), a Dict of path to Blob (folder) or a listing of sizes, times and ETags, as East's JSON, CSV or text, (3) Deploying the declarations to a workspace and binding each source to s3://bucket/prefix/ with e3-cloud files deploy and bind, (4) Syncing what changed with e3-cloud files sync, locally with your own cloud profile (--profile, --latest), then running the dataflow with e3 dataflow run, (5) Reading a refusal: declarations that don't fit the package (FilesDeclarationError, a deploy's problems), an unbound source, a file over its limit, a protected workspace (workspace_protected), (6) Moving declarations to production through staging: a release carries them with the package and apps, a rollback restores them, a copy brings bindings and what each input took, and production's bindings stay its own, (7) Reading Files' East types (FilesManifestType, FilesBindingType, FilesSyncReportType — @elaraai/e3-cloud-types)."
3
+ description: "Files for e3 cloud — a solution's inputs that come from files in a bucket (s3://bucket/prefix/), declared in its code and synced when asked, the same on a local repository and a deployed workspace. Use when: (1) Declaring which inputs a bucket's files fill with Files.source(name) and its file, latest, folder and listing, then Files.manifest([...]) as a module's default export, (2) Decoding what arrives in a task: a Blob (file, latest), a Dict of path to Blob (folder) or a listing of sizes, times and ETags, as East's JSON, CSV or text, (3) Deploying the declarations to a workspace and binding each source to s3://bucket/prefix/ with e3-cloud files deploy and bind, (4) Syncing what changed with e3-cloud files sync, locally with your own cloud profile (--profile, --latest), then running the dataflow with e3 dataflow run, (5) Reading a refusal: declarations that don't fit the package (FilesDeclarationError, a deploy's problems), an unbound source, a file over its limit, a protected workspace (workspace_protected), (6) Moving declarations to production through staging: a release carries them with the package and apps, a rollback restores them, a copy brings bindings and what each input took, and production's bindings stay its own, (7) Reading Files' East types (FilesManifestType, FilesBindingType, FilesSyncReportType — @elaraai/e3-cloud-types), (8) Taking a beast2 file in as an input of its type with no task in between: file or latest with a type, a collection of any size, (9) Feeding a record's mutation from files with source.mutation(def, spec), def one its record declares (priceBook.mutations.load_prices), each new file applied once, (10) Pushing files from disk into a source and taking them in with e3-cloud files push, matched by name or --as, dry-run, unchanged, (11) Seeding a record from a file with source.record(name, type, spec, parts): e3.record with no initial value, declared whole with its indexes, mutations and migrations, pending until its file, its tasks waiting meanwhile, (12) Syncing or resetting particular targets with --target and --reset."
4
4
  ---
5
5
 
6
6
  # e3-cloud-files — files as e3 inputs
7
7
 
8
- A solution says in its code which of its inputs come from files in a bucket. Each
9
- workspace says where those files are, a bucket and a prefix, and
10
- `e3-cloud files sync` brings in what changed: on a local repository with your
11
- own cloud credentials, in the cloud with the platform's read-only role. A file
12
- arrives as it is, a `Blob`, and a task decodes it. Nothing polls: a sync runs
13
- when someone asks.
8
+ A solution says in its code which of its inputs come from files in a bucket,
9
+ and which records are fed from them. Each workspace says where those files
10
+ are, a bucket and a prefix, and `e3-cloud files sync` brings in what changed:
11
+ on a local repository with your own cloud credentials, in the cloud with the
12
+ platform's read-only role. A file arrives as it is, a `Blob`, and a task
13
+ decodes it; or, named with a type, a beast2 file arrives as the value it
14
+ holds, with no task in between. `e3-cloud files push` puts files from disk
15
+ into a source and takes them in. Nothing polls: a sync runs when someone asks.
14
16
 
15
17
  Read `README.md` for the author's guide; this file is the short form.
16
18
 
@@ -65,7 +67,11 @@ What do you need?
65
67
  ├─ Declare which inputs a bucket's files fill (src/files.ts)
66
68
  │ ├─ A place files come from → Files.source(name) — each workspace binds it to s3://bucket/prefix/
67
69
  │ ├─ One known file → source.file(input, { key }) input: Blob
70
+ │ ├─ A beast2 file as its value → source.file(input, { key, type: ArrayType(Row) }) input: Array<Row>, no task between
68
71
  │ ├─ The newest of a folder → source.latest(input, { prefix, include? }) input: Blob, the greatest key
72
+ │ ├─ The newest, typed → source.latest(input, { prefix, include?, type })
73
+ │ ├─ A record's first state → source.record(name, type, { key } | { prefix, include? }, parts?) e3.record(name, type, parts): pending until its file
74
+ │ ├─ Feed a record's mutation → source.mutation(record.mutations.<name>, { key } | { prefix, include? }) every new file, in key order, once
69
75
  │ ├─ Every file of a folder → source.folder(input, { prefix, include? }) input: Dict<String, Blob> by path
70
76
  │ ├─ A folder's files, not bytes → source.listing(input, { prefix, include? }) input: Dict<String, {size, modified, etag}>
71
77
  │ ├─ Subfolders' files too → include: '**/*.csv' ('*.csv' is the top level only)
@@ -81,8 +87,9 @@ What do you need?
81
87
  │ ├─ Deploy the declarations → e3-cloud files deploy <repo> <ws> src/files.ts [--dry-run]
82
88
  │ ├─ Say where a source's files are → e3-cloud files bind <repo> <ws> <source> s3://bucket/prefix/ [--region r]
83
89
  │ │ └─ Locally only → --profile <aws profile>, --latest <n> (each folder's n greatest keys)
84
- │ ├─ Bring in what changed → e3-cloud files sync <repo> <ws> [--input name…] [--dry-run]
85
- │ ├─ What is declared, bound, synced → e3-cloud files status <repo> <ws>
90
+ │ ├─ Bring in what changed → e3-cloud files sync <repo> <ws> [--target t…] [--reset record…] [--dry-run]
91
+ │ ├─ Put files from disk → e3-cloud files push <repo> <ws> <file|dir>… [--as target] [--dry-run] [--profile p]
92
+ │ ├─ What is declared, bound, held → e3-cloud files status <repo> <ws> (each mapping, its type, what it holds, pending)
86
93
  │ └─ Run the dataflow on it → e3 dataflow run <repo> <ws> (a sync doesn't run it)
87
94
  ├─ Production: a workspace with a staging (a deployed repository's URL)
88
95
  │ ├─ Change its declarations → files deploy to the staging, then e3-cloud release <url> <staging> <production>
@@ -94,8 +101,8 @@ What do you need?
94
101
  │ └─ Rehearse on production's files → e3-cloud staging add … (a copy: declarations, bindings, what each input took)
95
102
  ├─ Keep synced inputs across a package deploy → e3 workspace deploy … --inputs keep-edited
96
103
  └─ Drive it from code
97
- ├─ The checks → @elaraai/e3-cloud-files/check: checkFiles, checkBindings, readPackageInputs
98
- ├─ The sync over your own ports → @elaraai/e3-cloud-files/sync: syncFiles (FilesReader, FilesWriter, FilesLedger)
104
+ ├─ The checks → @elaraai/e3-cloud-files/check: checkFiles, checkBindings, readPackageSurface
105
+ ├─ The sync over your own ports → @elaraai/e3-cloud-files/sync: syncFiles (FilesReader, FilesWriter, FilesLedger), planPush
99
106
  ├─ Writing an e3 store → @elaraai/e3-cloud-files/storage: storageWriter(storage, repo, ws)
100
107
  └─ Reading a bucket → @elaraai/e3-cloud-files/s3: s3FilesReader(client, { bucket, prefix })
101
108
  ```
@@ -112,12 +119,34 @@ export const rates = upload.file('rates', { key: 'rates.json' });
112
119
  export const stock = upload.latest('stock', { prefix: 'stock/', include: '*.json' }); // input: Blob, the greatest key
113
120
  export const orders = upload.folder('orders', { prefix: 'orders/', include: '*.json' }); // input: Dict<String, Blob>
114
121
  export const orderFiles = upload.listing('order_files', { prefix: 'orders/' }); // input: each file's size, modified, etag
122
+ export const prices = upload.file('prices', { key: 'prices.beast2', type: ArrayType(Price) }); // input: Array<Price>: the beast2 file's value, any size
123
+ export const customers = upload.record('customers', CustomerBook, { key: 'customers.beast2' }); // a record, e3.record('customers', CustomerBook): pending until its file
124
+ upload.mutation(priceBook.mutations.load_prices, { prefix: 'deltas/', include: '*.beast2' }); // each new delta file fed to the record's mutation, in key order
115
125
 
116
126
  export default Files.manifest([upload]); // what e3-cloud files deploy deploys
117
127
  ```
118
128
 
119
129
  - Each shape is an ordinary `e3.input`, unassigned until a sync sets it: use
120
- it in tasks as any input, and give the tasks to `e3.package` as usual.
130
+ it in tasks as any input, and give the tasks to `e3.package` as usual. A
131
+ `file` or `latest` with a `type` is an input of that type: the file must be
132
+ beast2 of it (`encodeBeast2For(T)`, east-py, `e3 convert`), a collection of
133
+ any size, checked by its header before any other byte is read.
134
+ - `source.record(name, type, spec, parts?)` is `e3.record(name, type, parts)`
135
+ with no initial value. It is declared whole: its indexes, mutations and
136
+ migrations are `parts`, as `e3.record` takes them
137
+ (`{ mutations: (mutation) => ({ set_tier: mutation.reduce(fn) }) }`), and an
138
+ initial value is refused. It is pending until its file arrives, which is
139
+ its first state (`$init`; nothing runs on an empty state first, and the
140
+ tasks over it wait as on an unset input, the run completing without them).
141
+ A changed file while nothing has mutated it since is its state again
142
+ (`$seed`); mutated since, it is kept until `files sync --reset <record>`.
143
+ - `source.mutation(def, spec)` feeds a record's mutation: `def` is one the
144
+ record declares, `record.mutations.<name>`, a `reduce` or an `edit` taking
145
+ exactly one argument. Every new file the spec selects (one key, or each
146
+ under the prefix its glob matches) is applied in key order, once by its
147
+ bytes' hash, so the same bytes under another name are applied no more; a
148
+ file the mutation refuses holds the ones after it until it changes; a busy
149
+ record leaves the file to the next sync.
121
150
  - Keys and prefixes are relative to where a workspace binds the source; a
122
151
  prefix is `''` or ends with `/`.
123
152
  - `include` is a glob over the path under the prefix: `*` within one folder,
@@ -145,11 +174,26 @@ file's path under the prefix, in path order.
145
174
  ```
146
175
  e3-cloud files deploy <repo> <workspace> src/files.ts [--dry-run]
147
176
  e3-cloud files bind <repo> <workspace> upload s3://bucket/prefix/ [--region r] [--profile p] [--latest n]
148
- e3-cloud files sync <repo> <workspace> [--input name...] [--dry-run]
177
+ e3-cloud files sync <repo> <workspace> [--target name|record:r|mutation:r.m ...] [--reset record...] [--dry-run]
178
+ e3-cloud files push <repo> <workspace> <file|dir>... [--as target] [--reset record...] [--dry-run] [--profile p]
149
179
  e3-cloud files status <repo> <workspace>
150
180
  e3 dataflow run <repo> <workspace>
151
181
  ```
152
182
 
183
+ `files push` matches each file to the mapping that takes it (a `file` by its
184
+ name, a `latest` under its prefix, a directory to the `folder`; `--as` names
185
+ one), checks its type and size, puts it where the source is bound (the
186
+ platform signs the upload into the deployment's own bucket, bound to the
187
+ file's SHA-256; your own credentials elsewhere), then syncs what was put. A
188
+ file the source holds already is `unchanged`; refused: `no_mapping`,
189
+ `ambiguous` (name the target with `--as`), `not_included` (the mapping's
190
+ glob leaves it out), `not_latest` (a greater key would shadow it), a typed
191
+ mapping's file of another type or no beast2, one over the limit.
192
+
193
+ `--target` names an input, `record:<name>` or `mutation:<record>.<name>`
194
+ (split at its last dot); `--reset <record>` takes a seeded record's file as
195
+ its state again, whatever mutated it since.
196
+
153
197
  `<repo>` is a local repository's path or a repository URL. Deploy the package
154
198
  first (`e3 workspace deploy`), then the declarations, which are checked
155
199
  against it. A sync reads only files whose ETag or size changed, writes only
@@ -161,9 +205,28 @@ keeps only each folder's n greatest keys.
161
205
  ## Traps
162
206
 
163
207
  - An input a mapping fills must be writable and of exactly its shape's type:
164
- `Blob`, `Dict<String, Blob>` (`FilesFolderType`) or the listing's
165
- (`FilesListingType`). Declare it through the shape; never write it with
166
- `e3.input` yourself.
208
+ `Blob`, the `type` it names, `Dict<String, Blob>` (`FilesFolderType`) or
209
+ the listing's (`FilesListingType`). Declare it through the shape; never
210
+ write it with `e3.input` yourself. A fed mutation takes exactly one
211
+ argument, of the file's type; a `patch` mutation cannot be fed.
212
+ - A typed file of another type, or no beast2, is refused by its header
213
+ (`type_mismatch` with both types, `not_beast2`): the input keeps its value.
214
+ A typed value that is no collection is at most 1 GiB; a collection's
215
+ segments at most 64 MiB (`segment_too_large`: write it again).
216
+ - A seeded record (`upload.record(name, type, { key })`) is pending until
217
+ its file, which is its first state (`$init`); a changed file seeds it
218
+ again while unmutated (`$seed`), `--reset` does whatever was done since;
219
+ the tasks over it wait meanwhile. The workspace page shows it Pending, its
220
+ page says what seeds it, `files status` prints `pending`; its seeds read
221
+ "Initialised from customers.beast2 (12,004 rows)" / "Seeded again from …"
222
+ in its history (the record page, `e3-cloud records history`), a feed names
223
+ its file. A deploy that cannot keep it (its type changed) under
224
+ `--schema reset` unassigns it: pending again, seeded by the next version's
225
+ file.
226
+ - A record is declared whole: a fed mutation is one its record declares
227
+ (`priceBook.mutations.load_prices`), and a seeded record's indexes,
228
+ mutations and migrations are `upload.record`'s `parts`; an initial value
229
+ there is refused, its file being its first state.
167
230
  - A file over 1 GiB less 64 KiB (`file`, `latest`), or over 64 MiB less
168
231
  64 KiB in a folder, is refused and named in the report, never truncated.
169
232
  So is a file the sync can't read (archived, under a key it may not use,
@@ -181,7 +244,9 @@ keeps only each folder's n greatest keys.
181
244
  - Bindings never travel with a release; a copy carries them. A staging
182
245
  started as a copy of production reads production's files until you bind it
183
246
  elsewhere.
184
- - The version is the @elaraai lockstep: use the same version as east and e3.
247
+ - The version is e3 cloud's own, not e3's: install the release built with the
248
+ e3 the deployment runs, its peers (east, e3) at exactly the versions it
249
+ names, and the `e3-cloud` CLI of the same release.
185
250
 
186
251
  ## Related skills
187
252
 
@@ -64,7 +64,10 @@ const overview = Page('overview', { title: 'Overview', icon: 'gauge-high' },
64
64
  - The function's **one argument is the page's params** (or none). Never write
65
65
  the type separately: TypeScript takes it from the function.
66
66
  - Bind data exactly as a `ui()` body does: `Data.bind`, `Data.bindPaged`,
67
- `Func.bind`, `Record.bind`. Pass the defs, never names.
67
+ `Func.bind`, `Record.bind`. Pass the defs, never a dataset's or a
68
+ function's name; a record's mutations and indexes are named as the record
69
+ declares them: `Record.bind(orders, ['approve'])`,
70
+ `Data.bindPaged(orders, { index: 'by_line' })`.
68
71
  - `meta.parent` makes the crumbs and lights the menu item.
69
72
  - Bind event handlers once: `const go = $.const(East.function([], NullType, $ => { $(Nav.go(line, key)); }))`.
70
73
 
@@ -103,6 +106,12 @@ on the workspace fits.
103
106
  - A body written without the thunk can only link to pages defined above it.
104
107
  - A page that writes a task's output or a record through `Data.bind` is
105
108
  refused: write inputs; change records through their mutations.
109
+ - A record is declared whole: its indexes, mutations and migrations in its
110
+ own `e3.record(name, type, { initial, indexes, mutations, migrations })`.
111
+ `e3.package` takes the record, never one of its parts.
106
112
  - `Page.task` needs a task in the `ui` role (`ui()`), and its output shows only
107
113
  after the workspace's next dataflow run.
108
- - The version is the @elaraai lockstep: use the same version as east and e3.
114
+ - The version is e3 cloud's own, not e3's: install the release built with the
115
+ e3 the deployment runs, its peers (east, east-ui, e3, e3-ui) at exactly the
116
+ versions it names. An app built with another e3 than the platform's is
117
+ refused, naming both.