@elaraai/e3-cloud-codex-plugin 1.0.91 → 1.0.92

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,11 +1,11 @@
1
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/codex-hook.js
1
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/codex-hook.js
2
2
  import { readFile as readFile2 } from "node:fs/promises";
3
3
  import { existsSync, writeFileSync } from "node:fs";
4
4
  import { createHash } from "node:crypto";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join, resolve as resolve3 } from "node:path";
7
7
 
8
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/codex-tools.js
8
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/codex-tools.js
9
9
  import { resolve } from "node:path";
10
10
  function patchFiles(command, cwd) {
11
11
  const files = [];
@@ -43,7 +43,7 @@ function shellReadPaths(command, cwd) {
43
43
  return [...paths];
44
44
  }
45
45
 
46
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/bash-writes.js
46
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/bash-writes.js
47
47
  import { resolve as resolve2 } from "node:path";
48
48
  function isRealPath(token) {
49
49
  if (token === "" || token.startsWith("&"))
@@ -92,7 +92,7 @@ function writtenPaths(command, cwd) {
92
92
  return [...found];
93
93
  }
94
94
 
95
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/transcript.js
95
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/transcript.js
96
96
  import { readFile } from "node:fs/promises";
97
97
  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"];
98
98
  async function searchedInTranscript(transcriptPath, tools = SEARCH_TOOLS) {
@@ -105,7 +105,7 @@ async function searchedInTranscript(transcriptPath, tools = SEARCH_TOOLS) {
105
105
  return tools.some((tool) => raw.includes(`"name":"${tool}"`) || raw.includes(`"name": "${tool}"`));
106
106
  }
107
107
 
108
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-guidance.js
108
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-guidance.js
109
109
  var EAST_GUIDANCE = {
110
110
  searchTool: "mcp__plugin_east_east__search_east_examples",
111
111
  getTool: "mcp__plugin_east_east__get_east_example",
@@ -147,7 +147,7 @@ var GATE_TEXT = gateText(EAST_GUIDANCE);
147
147
  var READ_TEXT = readText(EAST_GUIDANCE);
148
148
  var isExampleCorpusRead = corpusReadFor(EAST_GUIDANCE.scope);
149
149
 
150
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/codex-hook.js
150
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/codex-hook.js
151
151
  function codexToolCall(event) {
152
152
  const cwd = event.cwd || process.cwd();
153
153
  const input = event.tool_input ?? {};
@@ -199,7 +199,7 @@ async function codexSearchGate(event, options, call = codexToolCall(event)) {
199
199
  return context.length > 0 ? { decision: "context", text: context.join("\n\n") } : null;
200
200
  }
201
201
 
202
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/host.js
202
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/host.js
203
203
  import { existsSync as existsSync2 } from "node:fs";
204
204
  import { fileURLToPath } from "node:url";
205
205
  var isCodex = existsSync2(fileURLToPath(new URL("../../.codex-plugin/plugin.json", import.meta.url)));
@@ -207,7 +207,7 @@ function hostText(text) {
207
207
  return isCodex ? text.replaceAll("mcp__plugin_east_east__", "").replaceAll("/east:", "$east-codex-plugin:").replaceAll("Claude Code", "Codex") : text;
208
208
  }
209
209
 
210
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/hook-io.js
210
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/hook-io.js
211
211
  async function readHookInput() {
212
212
  let input = "";
213
213
  for await (const chunk of process.stdin) {
@@ -235,7 +235,7 @@ function writeHookDecision(hookEventName, decision, reason) {
235
235
  process.stdout.write(JSON.stringify(output));
236
236
  }
237
237
 
238
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-hooks.js
238
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-hooks.js
239
239
  function writeSearchHookReply(hookEventName, reply) {
240
240
  if (reply.decision === "deny")
241
241
  writeHookDecision(hookEventName, "deny", reply.text);
@@ -256,9 +256,9 @@ var E3_CLOUD_GUIDANCE = {
256
256
  corpus: E3_CLOUD_CORPUS,
257
257
  scope: "@elaraai"
258
258
  };
259
- var E3_CLOUD_CODE = /@elaraai\/e3-cloud-(?:ui|client)\b/;
260
- var PACKAGE_PATH = /[/\\]node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|cli|client)(?:[/\\]|$)/;
261
- var PACKAGE_PATTERN = /(?:^|[/\\])node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|cli|client)(?:[/\\]|$)/;
259
+ var E3_CLOUD_CODE = /@elaraai\/e3-cloud-(?:ui|files|client)\b/;
260
+ var PACKAGE_PATH = /[/\\]node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|files|cli|client)(?:[/\\]|$)/;
261
+ var PACKAGE_PATTERN = /(?:^|[/\\])node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|files|cli|client)(?:[/\\]|$)/;
262
262
  function isE3CloudCorpusRead(tool, input) {
263
263
  const text = (key) => typeof input[key] === "string" ? input[key] : "";
264
264
  return tool === "Read" ? PACKAGE_PATH.test(text("file_path")) : PACKAGE_PATH.test(text("path")) || PACKAGE_PATTERN.test(text("pattern"));
@@ -280,7 +280,7 @@ var E3_CLOUD_CODEX_SEARCH_GATE = {
280
280
  gateText: E3_CLOUD_SEARCH_GATE.gateText,
281
281
  readText: E3_CLOUD_READ_REMINDER.readText,
282
282
  isCorpusRead: isE3CloudCorpusRead,
283
- shellSweep: /node_modules\/@elaraai\/e3-cloud-(?:ui|cli|client)\b/,
283
+ shellSweep: /node_modules\/@elaraai\/e3-cloud-(?:ui|files|cli|client)\b/,
284
284
  corpusCode: E3_CLOUD_CODE,
285
285
  markerPrefix: "e3-cloud-codex-search-",
286
286
  requireSearchEnv: "E3_CLOUD_REQUIRE_SEARCH"
@@ -21491,7 +21491,7 @@ var StdioServerTransport = class {
21491
21491
  }
21492
21492
  };
21493
21493
 
21494
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search.js
21494
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search.js
21495
21495
  import { readFile } from "node:fs/promises";
21496
21496
 
21497
21497
  // ../../node_modules/.pnpm/minisearch@7.2.0/node_modules/minisearch/dist/es/index.js
@@ -23304,7 +23304,7 @@ var objectToNumericMapAsync = async (object3) => {
23304
23304
  var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
23305
23305
  var SPACE_OR_PUNCTUATION = /[\n\r\p{Z}\p{P}]+/u;
23306
23306
 
23307
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-guidance.js
23307
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-guidance.js
23308
23308
  var EAST_GUIDANCE = {
23309
23309
  searchTool: "mcp__plugin_east_east__search_east_examples",
23310
23310
  getTool: "mcp__plugin_east_east__get_east_example",
@@ -23346,7 +23346,7 @@ var GATE_TEXT = gateText(EAST_GUIDANCE);
23346
23346
  var READ_TEXT = readText(EAST_GUIDANCE);
23347
23347
  var isExampleCorpusRead = corpusReadFor(EAST_GUIDANCE.scope);
23348
23348
 
23349
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search.js
23349
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search.js
23350
23350
  var MIN_SCORE = 5;
23351
23351
  async function loadIndexes(indexPaths) {
23352
23352
  const entries = [];
@@ -23471,7 +23471,7 @@ function getEntry(index, id) {
23471
23471
  return stored === void 0 ? null : stored;
23472
23472
  }
23473
23473
 
23474
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/mcp/example-tools.js
23474
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/mcp/example-tools.js
23475
23475
  var EAST_PACKAGES = ["east", "east-node-std", "east-node-io", "east-py-datascience", "east-ui", "e3-ui", "e3", "e3-ui-cli", "e3-create"];
23476
23476
  function isLoaded(index) {
23477
23477
  return typeof index.then === "function";
@@ -23544,7 +23544,7 @@ function registerExampleTools(server2, options) {
23544
23544
  import { fileURLToPath } from "node:url";
23545
23545
  var E3_CLOUD_INDEX_PATH = fileURLToPath(new URL("../../index.json", import.meta.url));
23546
23546
 
23547
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/transcript.js
23547
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.92_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/transcript.js
23548
23548
  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
23549
 
23550
23550
  // ../e3-cloud-plugin/dist/lib/names.js
@@ -23552,7 +23552,7 @@ var E3_CLOUD_PLUGIN = "e3-cloud";
23552
23552
  var E3_CLOUD_SERVER = "e3-cloud";
23553
23553
  var E3_CLOUD_SEARCH_TOOL = "search_e3_cloud_examples";
23554
23554
  var E3_CLOUD_GET_TOOL = "get_e3_cloud_example";
23555
- var E3_CLOUD_PACKAGES = ["e3-cloud-ui", "e3-cloud-cli", "e3-cloud-client"];
23555
+ var E3_CLOUD_PACKAGES = ["e3-cloud-ui", "e3-cloud-files", "e3-cloud-cli", "e3-cloud-client"];
23556
23556
  var E3_CLOUD_CORPUS = "e3 cloud";
23557
23557
  var CLAUDE_TOOL_PREFIX = `mcp__plugin_${E3_CLOUD_PLUGIN}_${E3_CLOUD_SERVER}__`;
23558
23558
  var E3_CLOUD_GUIDANCE = {
@@ -23561,9 +23561,9 @@ var E3_CLOUD_GUIDANCE = {
23561
23561
  corpus: E3_CLOUD_CORPUS,
23562
23562
  scope: "@elaraai"
23563
23563
  };
23564
- var E3_CLOUD_CODE = /@elaraai\/e3-cloud-(?:ui|client)\b/;
23565
- var PACKAGE_PATH = /[/\\]node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|cli|client)(?:[/\\]|$)/;
23566
- var PACKAGE_PATTERN = /(?:^|[/\\])node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|cli|client)(?:[/\\]|$)/;
23564
+ var E3_CLOUD_CODE = /@elaraai\/e3-cloud-(?:ui|files|client)\b/;
23565
+ var PACKAGE_PATH = /[/\\]node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|files|cli|client)(?:[/\\]|$)/;
23566
+ var PACKAGE_PATTERN = /(?:^|[/\\])node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|files|cli|client)(?:[/\\]|$)/;
23567
23567
  function isE3CloudCorpusRead(tool, input) {
23568
23568
  const text = (key) => typeof input[key] === "string" ? input[key] : "";
23569
23569
  return tool === "Read" ? PACKAGE_PATH.test(text("file_path")) : PACKAGE_PATH.test(text("path")) || PACKAGE_PATTERN.test(text("pattern"));
@@ -23585,7 +23585,7 @@ var E3_CLOUD_CODEX_SEARCH_GATE = {
23585
23585
  gateText: E3_CLOUD_SEARCH_GATE.gateText,
23586
23586
  readText: E3_CLOUD_READ_REMINDER.readText,
23587
23587
  isCorpusRead: isE3CloudCorpusRead,
23588
- shellSweep: /node_modules\/@elaraai\/e3-cloud-(?:ui|cli|client)\b/,
23588
+ shellSweep: /node_modules\/@elaraai\/e3-cloud-(?:ui|files|cli|client)\b/,
23589
23589
  corpusCode: E3_CLOUD_CODE,
23590
23590
  markerPrefix: "e3-cloud-codex-search-",
23591
23591
  requireSearchEnv: "E3_CLOUD_REQUIRE_SEARCH"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "e3-cloud-codex-plugin",
3
- "version": "1.0.91",
4
- "description": "e3 cloud - apps for hosted e3 workspaces with @elaraai/e3-cloud-ui, the e3-cloud CLI and @elaraai/e3-cloud-client: skills, example search and the search-first hooks",
3
+ "version": "1.0.92",
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",
7
7
  "email": "support@elara.ai",
@@ -22,7 +22,7 @@
22
22
  "interface": {
23
23
  "displayName": "e3 cloud",
24
24
  "shortDescription": "e3 cloud skills, example search and hooks.",
25
- "longDescription": "e3 cloud - apps for hosted e3 workspaces with @elaraai/e3-cloud-ui, the e3-cloud CLI and @elaraai/e3-cloud-client: skills, example search and the search-first hooks",
25
+ "longDescription": "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",
26
26
  "developerName": "Elara AI",
27
27
  "category": "Productivity",
28
28
  "capabilities": [
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # e3 cloud plugin for Codex
2
2
 
3
- e3 cloud for Codex: the `e3-cloud-ui`, `e3-cloud-cli` and `e3-cloud-client`
4
- skills, the example search and the search-first hook, as the Claude Code
3
+ e3 cloud for Codex: the `e3-cloud-ui`, `e3-cloud-files`, `e3-cloud-cli` and
4
+ `e3-cloud-client` skills, the example search and the search-first hook, as the Claude Code
5
5
  plugin has them. Install it beside East's Codex plugin, which covers East, e3
6
6
  and e3-ui.
7
7
 
@@ -38,8 +38,8 @@ branch (`codex plugin marketplace add elaraai/plugins --ref beta`, named
38
38
  Codex runs one hook for every tool (east-plugin's `codexSearchGate` with e3
39
39
  cloud's names): after a search of e3 cloud's index or East's it records the
40
40
  search; before a call it reminds a read of
41
- `node_modules/@elaraai/e3-cloud-{ui,cli,client}`, and tells the first write of
42
- code importing `@elaraai/e3-cloud-ui` or `@elaraai/e3-cloud-client` with no
41
+ `node_modules/@elaraai/e3-cloud-{ui,files,cli,client}`, and tells the first write of
42
+ code importing `@elaraai/e3-cloud-ui`, `@elaraai/e3-cloud-files` or `@elaraai/e3-cloud-client` with no
43
43
  search recorded to search first (`E3_CLOUD_REQUIRE_SEARCH=deny`: a refusal).
44
44
 
45
45
  ## Developing it
package/hooks/hooks.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "e3 cloud plugin hooks: the search-before-coding gate for code using @elaraai/e3-cloud-ui or @elaraai/e3-cloud-client, and a reminder on reading their installed packages",
2
+ "description": "e3 cloud plugin hooks: the search-before-coding gate for code using @elaraai/e3-cloud-ui, @elaraai/e3-cloud-files or @elaraai/e3-cloud-client, and a reminder on reading their installed packages",
3
3
  "hooks": {
4
4
  "PreToolUse": [
5
5
  {
package/index.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "version": 2,
3
3
  "stats": {
4
- "totalEntries": 27,
5
- "totalFiles": 1,
4
+ "totalEntries": 38,
5
+ "totalFiles": 2,
6
6
  "packages": {
7
7
  "e3-cloud-ui": 12,
8
- "e3-cloud-cli": 11,
8
+ "e3-cloud-files": 10,
9
+ "e3-cloud-cli": 12,
9
10
  "e3-cloud-client": 4
10
11
  }
11
12
  },
@@ -233,6 +234,215 @@
233
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
236
  },
237
+ {
238
+ "id": "e3-cloud-files:decode.examples.ts:fileAsJson",
239
+ "skill": "e3-cloud-files",
240
+ "package": "e3-cloud-files",
241
+ "file": "packages/e3-cloud-files/test/decode.examples.ts",
242
+ "suite": "A file's bytes: file and latest",
243
+ "test": "A file input's bytes decoded as East's JSON, in the task that reads it: what upload.file('rates', { key: 'rates.json' }) or upload.latest(…) fills, a Blob",
244
+ "keywords": [
245
+ "Files",
246
+ "file",
247
+ "latest",
248
+ "Blob",
249
+ "decodeUtf8",
250
+ "parseJson",
251
+ "JSON",
252
+ "decode",
253
+ "task"
254
+ ],
255
+ "imports": [
256
+ "import { East, ArrayType, BlobType, DateTimeType, FloatType, IntegerType, StringType, StructType, example } from '@elaraai/east';",
257
+ "import { FilesFolderType, FilesListingType } from '@elaraai/e3-cloud-files';"
258
+ ],
259
+ "languages": [
260
+ "typescript"
261
+ ],
262
+ "source": "// A file input's bytes decoded as East's JSON, in the task that reads it: what upload.file('rates', { key: 'rates.json' }) or upload.latest(…) fills, a Blob\n// inputs: [utf8('[{\"currency\":\"AUD\",\"rate\":1.0},{\"currency\":\"USD\",\"rate\":0.66}]')] returns: [{ currency: 'AUD', rate: 1.0 }, { currency: 'USD', rate: 0.66 }]\nconst Rate = StructType({ currency: StringType, rate: FloatType });\n\nEast.function([BlobType], ArrayType(Rate), (_$, file) => file.decodeUtf8().parseJson(ArrayType(Rate)))"
263
+ },
264
+ {
265
+ "id": "e3-cloud-files:decode.examples.ts:fileAsCsv",
266
+ "skill": "e3-cloud-files",
267
+ "package": "e3-cloud-files",
268
+ "file": "packages/e3-cloud-files/test/decode.examples.ts",
269
+ "suite": "A file's bytes: file and latest",
270
+ "test": "A file input's bytes decoded as CSV with a header row, each row a struct whose fields name the columns",
271
+ "keywords": [
272
+ "Files",
273
+ "file",
274
+ "latest",
275
+ "Blob",
276
+ "decodeCsv",
277
+ "CSV",
278
+ "header",
279
+ "delimiter",
280
+ "columns"
281
+ ],
282
+ "imports": [
283
+ "import { East, ArrayType, BlobType, DateTimeType, FloatType, IntegerType, StringType, StructType, example } from '@elaraai/east';",
284
+ "import { FilesFolderType, FilesListingType } from '@elaraai/e3-cloud-files';"
285
+ ],
286
+ "languages": [
287
+ "typescript"
288
+ ],
289
+ "source": "// A file input's bytes decoded as CSV with a header row, each row a struct whose fields name the columns\n// inputs: [utf8('tank,litres\\nT1,1200.5\\nT2,880\\n')] returns: [{ tank: 'T1', litres: 1200.5 }, { tank: 'T2', litres: 880 }]\nconst Reading = StructType({ tank: StringType, litres: FloatType });\n\nEast.function([BlobType], ArrayType(Reading), (_$, file) => file.decodeCsv(Reading, { delimiter: ',' }))"
290
+ },
291
+ {
292
+ "id": "e3-cloud-files:decode.examples.ts:fileAsLines",
293
+ "skill": "e3-cloud-files",
294
+ "package": "e3-cloud-files",
295
+ "file": "packages/e3-cloud-files/test/decode.examples.ts",
296
+ "suite": "A file's bytes: file and latest",
297
+ "test": "A text file input's lines, trimmed, blank ones left out",
298
+ "keywords": [
299
+ "Files",
300
+ "file",
301
+ "Blob",
302
+ "decodeUtf8",
303
+ "split",
304
+ "lines",
305
+ "text",
306
+ "trim"
307
+ ],
308
+ "imports": [
309
+ "import { East, ArrayType, BlobType, DateTimeType, FloatType, IntegerType, StringType, StructType, example } from '@elaraai/east';",
310
+ "import { FilesFolderType, FilesListingType } from '@elaraai/e3-cloud-files';"
311
+ ],
312
+ "languages": [
313
+ "typescript"
314
+ ],
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
+ },
317
+ {
318
+ "id": "e3-cloud-files:decode.examples.ts:folderAsOneArray",
319
+ "skill": "e3-cloud-files",
320
+ "package": "e3-cloud-files",
321
+ "file": "packages/e3-cloud-files/test/decode.examples.ts",
322
+ "suite": "A folder's files",
323
+ "test": "Every file of a folder input decoded and joined into one array, in the order of their paths: what upload.folder('orders', { prefix: 'orders/', include: '*.json' }) fills, a Dict<String, Blob> keyed by each file's path under the prefix",
324
+ "keywords": [
325
+ "Files",
326
+ "folder",
327
+ "Dict",
328
+ "Blob",
329
+ "flattenToArray",
330
+ "parseJson",
331
+ "every file",
332
+ "concatenate"
333
+ ],
334
+ "imports": [
335
+ "import { East, ArrayType, BlobType, DateTimeType, FloatType, IntegerType, StringType, StructType, example } from '@elaraai/east';",
336
+ "import { FilesFolderType, FilesListingType } from '@elaraai/e3-cloud-files';"
337
+ ],
338
+ "languages": [
339
+ "typescript"
340
+ ],
341
+ "source": "// Every file of a folder input decoded and joined into one array, in the order of their paths: what upload.folder('orders', { prefix: 'orders/', include: '*.json' }) fills, a Dict<String, Blob> keyed by each file's path under the prefix\n// inputs: [new Map([\n ['2026-10-01.json', utf8('[{\"id\":\"O-1\",\"sku\":\"A1\",\"amount\":250.0}]')],\n ['2026-10-02.json', utf8('[{\"id\":\"O-2\",\"sku\":\"B2\",\"amount\":80.0},{\"id\":\"O-3\",\"sku\":\"A1\",\"amount\":120.0}]')],\n ])] returns: [{ id: 'O-1', sku: 'A1', amount: 250 }, { id: 'O-2', sku: 'B2', amount: 80 }, { id: 'O-3', sku: 'A1', amount: 120 }]\nconst Order = StructType({ id: StringType, sku: StringType, amount: FloatType });\n\nEast.function([FilesFolderType], ArrayType(Order), (_$, files) =>\n files.flattenToArray((_$, file) => file.decodeUtf8().parseJson(ArrayType(Order))))"
342
+ },
343
+ {
344
+ "id": "e3-cloud-files:decode.examples.ts:folderRowsWithTheirFile",
345
+ "skill": "e3-cloud-files",
346
+ "package": "e3-cloud-files",
347
+ "file": "packages/e3-cloud-files/test/decode.examples.ts",
348
+ "suite": "A folder's files",
349
+ "test": "Every CSV file of a folder input decoded, each row carrying the path of the file it came from",
350
+ "keywords": [
351
+ "Files",
352
+ "folder",
353
+ "path",
354
+ "key",
355
+ "flattenToArray",
356
+ "decodeCsv",
357
+ "which file",
358
+ "provenance"
359
+ ],
360
+ "imports": [
361
+ "import { East, ArrayType, BlobType, DateTimeType, FloatType, IntegerType, StringType, StructType, example } from '@elaraai/east';",
362
+ "import { FilesFolderType, FilesListingType } from '@elaraai/e3-cloud-files';"
363
+ ],
364
+ "languages": [
365
+ "typescript"
366
+ ],
367
+ "source": "// Every CSV file of a folder input decoded, each row carrying the path of the file it came from\n// inputs: [new Map([\n ['site-a.csv', utf8('tank,litres\\nT1,1200.5\\n')],\n ['site-b.csv', utf8('tank,litres\\nT9,40\\n')],\n ])] returns: [{ file: 'site-a.csv', tank: 'T1', litres: 1200.5 }, { file: 'site-b.csv', tank: 'T9', litres: 40 }]\nconst Reading = StructType({ tank: StringType, litres: FloatType });\n\nEast.function([FilesFolderType], ArrayType(StructType({ file: StringType, tank: StringType, litres: FloatType })), (_$, files) =>\n files.flattenToArray((_$, file, path) =>\n file.decodeCsv(Reading, { delimiter: ',' }).map((_$, row) => ({ file: path, tank: row.tank, litres: row.litres }))))"
368
+ },
369
+ {
370
+ "id": "e3-cloud-files:decode.examples.ts:folderUnderASubfolder",
371
+ "skill": "e3-cloud-files",
372
+ "package": "e3-cloud-files",
373
+ "file": "packages/e3-cloud-files/test/decode.examples.ts",
374
+ "suite": "A folder's files",
375
+ "test": "Only the files of a folder input under one of its subfolders. A folder declared with include '**/*.csv', or none, holds its subfolders' files too, each path naming its subfolder (2026/10/a.csv); '*.csv' matches only the top level",
376
+ "keywords": [
377
+ "Files",
378
+ "folder",
379
+ "filter",
380
+ "startsWith",
381
+ "path",
382
+ "subfolder",
383
+ "include",
384
+ "**"
385
+ ],
386
+ "imports": [
387
+ "import { East, ArrayType, BlobType, DateTimeType, FloatType, IntegerType, StringType, StructType, example } from '@elaraai/east';",
388
+ "import { FilesFolderType, FilesListingType } from '@elaraai/e3-cloud-files';"
389
+ ],
390
+ "languages": [
391
+ "typescript"
392
+ ],
393
+ "source": "// Only the files of a folder input under one of its subfolders. A folder declared with include '**/*.csv', or none, holds its subfolders' files too, each path naming its subfolder (2026/10/a.csv); '*.csv' matches only the top level\n// inputs: [new Map([['2025/12/a.csv', utf8('a')], ['2026/01/b.csv', utf8('b')], ['2026/02/c.csv', utf8('c')]]), '2026/'] returns: new Map([['2026/01/b.csv', utf8('b')], ['2026/02/c.csv', utf8('c')]])\nEast.function([FilesFolderType, StringType], FilesFolderType, (_$, files, under) =>\n files.filter((_$, _file, path) => path.startsWith(under)))"
394
+ },
395
+ {
396
+ "id": "e3-cloud-files:decode.examples.ts:listingChangedSince",
397
+ "skill": "e3-cloud-files",
398
+ "package": "e3-cloud-files",
399
+ "file": "packages/e3-cloud-files/test/decode.examples.ts",
400
+ "suite": "A listing: the files, without their bytes",
401
+ "test": "The paths of a listing input's files changed since a time, without reading their bytes: what upload.listing('order_files', { prefix: 'orders/' }) fills, each file's size, modified time and ETag by its path",
402
+ "keywords": [
403
+ "Files",
404
+ "listing",
405
+ "modified",
406
+ "since",
407
+ "DateTime",
408
+ "filter",
409
+ "keys",
410
+ "newest"
411
+ ],
412
+ "imports": [
413
+ "import { East, ArrayType, BlobType, DateTimeType, FloatType, IntegerType, StringType, StructType, example } from '@elaraai/east';",
414
+ "import { FilesFolderType, FilesListingType } from '@elaraai/e3-cloud-files';"
415
+ ],
416
+ "languages": [
417
+ "typescript"
418
+ ],
419
+ "source": "// The paths of a listing input's files changed since a time, without reading their bytes: what upload.listing('order_files', { prefix: 'orders/' }) fills, each file's size, modified time and ETag by its path\n// inputs: [\n new Map([\n ['2026-10-01.json', { size: 120n, modified: new Date(Date.UTC(2026, 9, 1)), etag: 'a1' }],\n ['2026-10-02.json', { size: 310n, modified: new Date(Date.UTC(2026, 9, 2)), etag: 'b2' }],\n ]),\n new Date(Date.UTC(2026, 9, 1, 12)),\n ] returns: ['2026-10-02.json']\nEast.function([FilesListingType, DateTimeType], ArrayType(StringType), (_$, rows, since) =>\n rows.filter((_$, row) => row.modified.greaterThan(since)).keys().toArray())"
420
+ },
421
+ {
422
+ "id": "e3-cloud-files:decode.examples.ts:listingTotals",
423
+ "skill": "e3-cloud-files",
424
+ "package": "e3-cloud-files",
425
+ "file": "packages/e3-cloud-files/test/decode.examples.ts",
426
+ "suite": "A listing: the files, without their bytes",
427
+ "test": "How many files a listing input holds, and their bytes in all",
428
+ "keywords": [
429
+ "Files",
430
+ "listing",
431
+ "size",
432
+ "sum",
433
+ "count",
434
+ "bytes",
435
+ "how many"
436
+ ],
437
+ "imports": [
438
+ "import { East, ArrayType, BlobType, DateTimeType, FloatType, IntegerType, StringType, StructType, example } from '@elaraai/east';",
439
+ "import { FilesFolderType, FilesListingType } from '@elaraai/e3-cloud-files';"
440
+ ],
441
+ "languages": [
442
+ "typescript"
443
+ ],
444
+ "source": "// How many files a listing input holds, and their bytes in all\n// inputs: [new Map([\n ['a.json', { size: 120n, modified: new Date(Date.UTC(2026, 9, 1)), etag: 'a1' }],\n ['b.json', { size: 310n, modified: new Date(Date.UTC(2026, 9, 2)), etag: 'b2' }],\n ])] returns: { files: 2n, bytes: 430n }\nEast.function([FilesListingType], StructType({ files: IntegerType, bytes: IntegerType }), (_$, rows) =>\n ({ files: rows.size(), bytes: rows.sum((_$, row) => row.size) }))"
445
+ },
236
446
  {
237
447
  "id": "e3-cloud-ui:static:app",
238
448
  "skill": "e3-cloud-ui",
@@ -347,6 +557,70 @@
347
557
  "typescript"
348
558
  ]
349
559
  },
560
+ {
561
+ "id": "e3-cloud-files:static:declare",
562
+ "skill": "e3-cloud-files",
563
+ "package": "e3-cloud-files",
564
+ "file": "static",
565
+ "suite": "Files",
566
+ "test": "Declaring which inputs a bucket's files fill: a source, one file, the newest of a folder, every file of a folder and a folder's listing, each an ordinary e3.input a task takes, and the declarations e3-cloud files deploy deploys",
567
+ "keywords": [
568
+ "Files",
569
+ "Files.source",
570
+ "file",
571
+ "latest",
572
+ "folder",
573
+ "listing",
574
+ "Files.manifest",
575
+ "include",
576
+ "glob",
577
+ "prefix",
578
+ "key",
579
+ "s3",
580
+ "bucket",
581
+ "input",
582
+ "Blob",
583
+ "e3-cloud-files"
584
+ ],
585
+ "imports": [
586
+ "import { East, ArrayType, BlobType, FloatType, StringType, StructType } from '@elaraai/east';",
587
+ "import e3, { type Runner } from '@elaraai/e3';",
588
+ "import { Files, FilesFolderType } from '@elaraai/e3-cloud-files';"
589
+ ],
590
+ "source": "// src/files.ts: which inputs a source's files fill. Each workspace binds the source to s3://bucket/prefix/;\n// keys and prefixes here are relative to it, so the code never names a bucket\nexport const upload = Files.source('upload');\nexport const rates = upload.file('rates', { key: 'rates.json' }); // Blob\nexport const stock = upload.latest('stock', { prefix: 'stock/', include: '*.json' }); // Blob: the greatest key, so name files by date\nexport const orders = upload.folder('orders', { prefix: 'orders/', include: '*.json' }); // Dict<String, Blob>, by path under the prefix\nexport const orderFiles = upload.listing('order_files', { prefix: 'orders/' }); // each file's size, modified and etag, no bytes\n// '*.json' is the prefix's own files; '**/*.json' takes its subfolders' too\nexport default Files.manifest([upload]);\n\n// src/package.ts: each is an ordinary input, unassigned until a sync sets it; a task decodes what arrives\nconst runner: Runner = { runtime: 'east-c', platforms: ['east-c-std'] };\nconst Rate = StructType({ currency: StringType, rate: FloatType });\nconst Order = StructType({ id: StringType, sku: StringType, amount: FloatType });\nexport const rateTable = e3.task('rate_table', [rates],\n East.function([BlobType], ArrayType(Rate), (_$, file) => file.decodeUtf8().parseJson(ArrayType(Rate))), { runner });\nexport const allOrders = e3.task('all_orders', [orders],\n East.function([FilesFolderType], ArrayType(Order), (_$, files) =>\n files.flattenToArray((_$, file) => file.decodeUtf8().parseJson(ArrayType(Order)))), { runner });\nexport default e3.package('orders', '1.0.0', rateTable, allOrders);",
591
+ "languages": [
592
+ "typescript"
593
+ ]
594
+ },
595
+ {
596
+ "id": "e3-cloud-files:static:test-sync",
597
+ "skill": "e3-cloud-files",
598
+ "package": "e3-cloud-files",
599
+ "file": "static",
600
+ "suite": "syncFiles",
601
+ "test": "Testing a solution's Files without a bucket: its declarations synced from a bucket in memory into a workspace in memory, then its task's function run on what came in",
602
+ "keywords": [
603
+ "syncFiles",
604
+ "MemoryBucket",
605
+ "MemoryWorkspace",
606
+ "MemoryLedger",
607
+ "test",
608
+ "sync",
609
+ "report",
610
+ "unit test",
611
+ "@elaraai/e3-cloud-files/sync"
612
+ ],
613
+ "imports": [
614
+ "import { East, none } from '@elaraai/east';",
615
+ "import { MemoryBucket, MemoryLedger, MemoryWorkspace, syncFiles } from '@elaraai/e3-cloud-files/sync';",
616
+ "import declarations from '../src/files.js';",
617
+ "import { decodeRates } from '../src/package.js';"
618
+ ],
619
+ "source": "const bucket = new MemoryBucket();\nbucket.put('drop/rates.json', '[{\"currency\":\"AUD\",\"rate\":1.0}]');\nconst workspace = new MemoryWorkspace();\nconst report = await syncFiles({\n manifest: declarations,\n bindings: [{ source: 'upload', url: 's3://any-bucket/drop/', region: none, profile: none, latest: none }],\n reader: () => bucket.reader('drop/'), // the bucket, under the binding's prefix\n writer: workspace,\n ledger: new MemoryLedger(), // what each input took: a second sync reads nothing unchanged\n inputs: ['rates'],\n});\n// report.mappings: per input, the files added, changed, removed, unchanged and refused, and whether it was written\nconst rates = East.compile(decodeRates, [])(workspace.files.get('rates')!);",
620
+ "languages": [
621
+ "typescript"
622
+ ]
623
+ },
350
624
  {
351
625
  "id": "e3-cloud-cli:static:sign-in",
352
626
  "skill": "e3-cloud-cli",
@@ -633,6 +907,34 @@
633
907
  "typescript"
634
908
  ]
635
909
  },
910
+ {
911
+ "id": "e3-cloud-cli:static:files",
912
+ "skill": "e3-cloud-cli",
913
+ "package": "e3-cloud-cli",
914
+ "file": "static",
915
+ "suite": "e3-cloud files",
916
+ "test": "Bringing a bucket's files into a workspace: deploy the declarations, bind each source to s3://bucket/prefix/, sync what changed, then run the dataflow; locally with your own cloud profile, the bytes only in the local repository",
917
+ "keywords": [
918
+ "files",
919
+ "deploy",
920
+ "bind",
921
+ "sync",
922
+ "status",
923
+ "s3",
924
+ "bucket",
925
+ "--profile",
926
+ "--latest",
927
+ "--dry-run",
928
+ "--input",
929
+ "e3 dataflow run",
930
+ "e3-cloud"
931
+ ],
932
+ "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",
934
+ "languages": [
935
+ "typescript"
936
+ ]
937
+ },
636
938
  {
637
939
  "id": "e3-cloud-client:static:basics",
638
940
  "skill": "e3-cloud-client",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elaraai/e3-cloud-codex-plugin",
3
- "version": "1.0.91",
4
- "description": "e3 cloud - apps for hosted e3 workspaces with @elaraai/e3-cloud-ui, the e3-cloud CLI and @elaraai/e3-cloud-client: skills, example search and the search-first hooks",
3
+ "version": "1.0.92",
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",
7
7
  "author": {
@@ -1,24 +1,25 @@
1
1
  ---
2
2
  name: e3-cloud-cli
3
- description: "The e3-cloud CLI — run an e3 cloud deployment from the shell. Use when: (1) Signing in (`e3 auth login` to a server, whose credentials e3-cloud shares) and checking who you are (`e3-cloud whoami`), (2) Building and deploying apps — `e3-cloud app build`, `app deploy` (UI-only, or with its package, `--dry-run`, several together, `--from-source`), `app list`, `app versions`, `app remove` (an app is developed in the cloud: deployed to a workspace of one's own and opened there), (3) Staging an app for the platform's next e3 (`app deploy --for-platform VERSION`, `app list-all`), (4) Staging and releases — `e3-cloud staging add|stop|list` (a staging protects the workspace it releases to: it then changes only by a release from it, or a rollback; `--force` on `app deploy` and `app remove` is an admin's override), `e3 workspace copy` for the rehearsal, `e3-cloud release URL FROM TO` with `--plan`, `--carry` and `--remove-apps`,… See the detailed scope below."
3
+ description: "The e3-cloud CLI — run an e3 cloud deployment from the shell. Use when: (1) Signing in (`e3 auth login` to a server, whose credentials e3-cloud shares) and checking who you are (`e3-cloud whoami`), (2) Building and deploying apps — `e3-cloud app build`, `app deploy` (UI-only, or with its package, `--dry-run`, several together, `--from-source`), `app list`, `app versions`, `app remove` (an app is developed in the cloud: deployed to a workspace of one's own and opened there), (3) Staging an app for the platform's next e3 (`app deploy --for-platform VERSION`, `app list-all`), (4) Staging and releases — `e3-cloud staging add|stop|list` (a staging protects the workspace it releases to: it then changes only by a release from it, or a rollback; `--force` on `app deploy` and `app remove` is an admin's override), `e3 workspace copy` for the rehearsal, `e3-cloud release URL FROM TO` with `--plan`, `--carry` and `--remove-apps` (a… See the detailed scope below."
4
4
  ---
5
5
 
6
6
  ## Detailed skill scope
7
7
 
8
- The e3-cloud CLI — run an e3 cloud deployment from the shell. Use when: (1) Signing in (`e3 auth login` to a server, whose credentials e3-cloud shares) and checking who you are (`e3-cloud whoami`), (2) Building and deploying apps — `e3-cloud app build`, `app deploy` (UI-only, or with its package, `--dry-run`, several together, `--from-source`), `app list`, `app versions`, `app remove` (an app is developed in the cloud: deployed to a workspace of one's own and opened there), (3) Staging an app for the platform's next e3 (`app deploy --for-platform VERSION`, `app list-all`), (4) Staging and releases — `e3-cloud staging add|stop|list` (a staging protects the workspace it releases to: it then changes only by a release from it, or a rollback; `--force` on `app deploy` and `app remove` is an admin's override), `e3 workspace copy` for the rehearsal, `e3-cloud release URL FROM TO` with `--plan`, `--carry` and `--remove-apps`, `e3-cloud history`, `e3-cloud rollback`, (5) A repository's people and business roles — `e3-cloud user list|add|remove` (`--role owner`, or a user with `--roles 'Rostering,Finance'`), `e3-cloud role list|show|set|rm` (`--grant WORKSPACE/APP[:page,page]`), and for admins `user list-all|disable|enable|invitations|uninvite`, (6) Secrets (`e3-cloud secrets set|list|rm`), schedules (`schedule set|get|list|remove`), each task's compute size and timeout (`compute`, `timeout`), people's workspace settings (`settings`), (7) Records' history, rollback and backups (`e3-cloud records …`) and feedback (`e3-cloud feedback …`).
8
+ The e3-cloud CLI — run an e3 cloud deployment from the shell. Use when: (1) Signing in (`e3 auth login` to a server, whose credentials e3-cloud shares) and checking who you are (`e3-cloud whoami`), (2) Building and deploying apps — `e3-cloud app build`, `app deploy` (UI-only, or with its package, `--dry-run`, several together, `--from-source`), `app list`, `app versions`, `app remove` (an app is developed in the cloud: deployed to a workspace of one's own and opened there), (3) Staging an app for the platform's next e3 (`app deploy --for-platform VERSION`, `app list-all`), (4) Staging and releases — `e3-cloud staging add|stop|list` (a staging protects the workspace it releases to: it then changes only by a release from it, or a rollback; `--force` on `app deploy` and `app remove` is an admin's override), `e3 workspace copy` for the rehearsal, `e3-cloud release URL FROM TO` with `--plan`, `--carry` and `--remove-apps` (a release carries the package, the apps and the Files declarations; the target keeps its data and bindings), `e3-cloud history`, `e3-cloud rollback`, (5) A repository's people and business roles — `e3-cloud user list|add|remove` (`--role owner`, or a user with `--roles 'Rostering,Finance'`), `e3-cloud role list|show|set|rm` (`--grant WORKSPACE/APP[:page,page]`), and for admins `user list-all|disable|enable|invitations|uninvite`, (6) Secrets (`e3-cloud secrets set|list|rm`), schedules (`schedule set|get|list|remove`), each task's compute size and timeout (`compute`, `timeout`), people's workspace settings (`settings`), (7) Records' history, rollback and backups (`e3-cloud records …`) and feedback (`e3-cloud feedback …`), (8) Files — a solution's inputs from files in a bucket: `e3-cloud files deploy|bind|sync|status`, given a local repository's path or a repository URL (the e3-cloud-files skill declares and decodes them), (9) Walking a solution through its life, step by step — starting it, adding a staging, developing an app's pages, changing the package's tasks and inputs, migrating a record, Files, rehearsing and releasing, rolling back, an emergency change to production, people and roles, schedules, secrets and compute, feedback, the platform's next e3, and retiring an app or a workspace.
9
9
 
10
10
  # e3-cloud — the e3 cloud CLI (@elaraai/e3-cloud-cli)
11
11
 
12
12
  `e3-cloud` runs the parts of an e3 cloud deployment that e3's own CLI does not:
13
13
  apps, releases, users and roles, secrets, schedules, task configs, records'
14
- history and backups, and feedback. e3's CLI (`e3`, `@elaraai/e3-cli`) still
14
+ history and backups, feedback, and files from buckets. e3's CLI (`e3`, `@elaraai/e3-cli`) still
15
15
  does what it does against any server: repositories, packages, workspaces,
16
16
  datasets, dataflow runs and copies. Both sign in with `e3 auth login` and share its
17
17
  credentials (`~/.e3/credentials.json`).
18
18
 
19
19
  A command names what it acts on by URL: a server (`https://e3.example.com`),
20
20
  a repository (`…/repos/acme`), or a workspace (`…/repos/acme/workspaces/production`),
21
- as its help says.
21
+ as its help says. `e3-cloud files` takes a local repository's path instead
22
+ as readily.
22
23
 
23
24
  ## Before writing code — search the example index
24
25
 
@@ -54,8 +55,284 @@ e3-cloud staging add https://e3.example.com/repos/acme production staging
54
55
  # A release: the plan first, then exactly what staging runs, production's data kept
55
56
  e3-cloud release https://e3.example.com/repos/acme staging production --plan
56
57
  e3-cloud release https://e3.example.com/repos/acme staging production
58
+ e3 dataflow run https://e3.example.com/repos/acme production # a release runs nothing
57
59
  ```
58
60
 
61
+ ## Lifecycle guides
62
+
63
+ Each guide is the commands in order, then what to watch. `URL` stands for a
64
+ repository's URL (`https://e3.example.com/repos/acme`); a workspace's URL is
65
+ `URL/workspaces/<name>`.
66
+
67
+ ```
68
+ What are you doing?
69
+ ├─ Setting up
70
+ │ ├─ A new solution in the cloud → 1. Start a solution
71
+ │ └─ Production, and a staging to try changes in → 2. Add a staging
72
+ ├─ Changing the solution (always in staging)
73
+ │ ├─ An app's pages or menus → 3. Develop an app
74
+ │ ├─ Tasks, inputs or functions → 4. Change the package
75
+ │ ├─ A record's type, or its values → 5. Migrate a record
76
+ │ └─ Which inputs come from files → 6. Files
77
+ ├─ Shipping to production
78
+ │ ├─ Rehearse on production's data, then release → 7. Rehearse and release
79
+ │ ├─ Bring reference data set up in staging → 7, --carry
80
+ │ ├─ Undo a release → 8. Roll back
81
+ │ └─ A fix that can't wait for staging → 9. An emergency change
82
+ ├─ Running it
83
+ │ ├─ Who may use which apps and pages → 10. People and roles
84
+ │ ├─ Runs on a timetable, credentials, task sizes → 11. Schedules, secrets, compute
85
+ │ ├─ What users report from the pages → 12. Feedback
86
+ │ └─ A record someone got wrong → 5, record rollback and backups
87
+ └─ Changing the platform, or ending things
88
+ ├─ The platform's next e3 → 13. The platform's next e3
89
+ └─ An app or a workspace no longer needed → 14. Retire
90
+ ```
91
+
92
+ ### 1. Start a solution
93
+
94
+ ```bash
95
+ e3 auth login https://e3.example.com
96
+ e3 repo create URL
97
+ e3 workspace deploy URL production --from-source src/package.ts # makes the workspace
98
+ e3-cloud user add URL ana@example.com --role owner
99
+ e3-cloud app deploy URL production --from-source src/apps/ops.tsx
100
+ e3 dataflow run URL production
101
+ ```
102
+
103
+ - A repository holds one solution; a workspace runs one version of its
104
+ package, with its own data.
105
+ - Add a staging (2) before anyone relies on production: from then on every
106
+ change goes through it.
107
+
108
+ ### 2. Add a staging
109
+
110
+ ```bash
111
+ e3-cloud staging add URL production staging --plan
112
+ e3-cloud staging add URL production staging
113
+ e3-cloud staging list URL
114
+ ```
115
+
116
+ - The staging starts as a copy of production, data included: its package,
117
+ inputs, records, apps, task settings and Files (declarations, bindings and
118
+ what each input took). `--as-is` makes a workspace there the staging as it
119
+ stands; `--replace` copies over one.
120
+ - Production is then protected: its package, apps and Files declarations
121
+ change only by a release from its staging, or a rollback. People go on
122
+ using it: inputs, records, runs and schedules.
123
+ - Only an admin stops a staging (`staging stop URL production`), which
124
+ deletes nothing and unprotects production.
125
+
126
+ ### 3. Develop an app
127
+
128
+ ```bash
129
+ e3-cloud app deploy URL staging --from-source src/apps/ops.tsx --dry-run
130
+ e3-cloud app deploy URL staging --from-source src/apps/ops.tsx
131
+ e3-cloud app list URL staging
132
+ e3-cloud app versions URL staging ops
133
+ ```
134
+
135
+ - Apps are developed in the cloud: deploy to staging (or a workspace of your
136
+ own) after each change, and open it in the console. Nothing serves an app
137
+ locally.
138
+ - An app is checked against the package it was built against. When staging
139
+ runs that package, only the app changes; otherwise the package deploys
140
+ first, keeping the inputs people set, and the app goes live as it
141
+ completes.
142
+ - Several apps deploy together or not at all: `--from-source a.tsx b.tsx`.
143
+ Writing pages and menus: the e3-cloud-ui skill.
144
+
145
+ ### 4. Change the package
146
+
147
+ ```bash
148
+ e3 workspace deploy URL staging --from-source src/package.ts --inputs keep-edited --plan
149
+ e3 workspace deploy URL staging --from-source src/package.ts --inputs keep-edited
150
+ e3 dataflow run URL staging
151
+ ```
152
+
153
+ - `--plan` says what the deploy does to each record, index and input, and
154
+ writes nothing. `--inputs keep-edited` keeps the inputs people set while
155
+ their type is unchanged; the default resets every input to the package's.
156
+ - A deploy that would break an app of the workspace is refused, naming each
157
+ app's problems: rebuild the apps against the new package and deploy them
158
+ with it (`e3-cloud app deploy … --from-source`, which deploys the package
159
+ first).
160
+ - A deploy runs nothing: the next run recomputes the tasks. Files
161
+ declarations that no longer fit the package make a sync refuse until
162
+ they're deployed again (6).
163
+
164
+ ### 5. Migrate a record
165
+
166
+ A record's new type comes with a migration in the package: e3's
167
+ `e3.migration.value`, `rows` or `rekey`, each step naming the one before it
168
+ with `after` (the e3 skill). Then, in staging:
169
+
170
+ ```bash
171
+ e3-cloud records backup URL staging orders --note "before orders v2"
172
+ e3 workspace deploy URL staging --from-source src/package.ts --inputs keep-edited --plan # orders: migrate (1 step)
173
+ e3 workspace deploy URL staging --from-source src/package.ts --inputs keep-edited
174
+ e3-cloud records backup URL production orders --note "before the release of orders v2"
175
+ e3-cloud release URL staging production --plan # orders migrates (1 step)
176
+ ```
177
+
178
+ - A deploy runs only the steps a workspace hasn't applied: production runs
179
+ them as the release deploys.
180
+ - A migration is one way. A rollback past it is refused, and so is a restore
181
+ of a backup from before it, so back production up and rehearse first (7).
182
+ - Fixing a record's values needs no deploy: `records history` (who changed
183
+ what, and when), `records show URL ws orders COMMIT` (its value then),
184
+ `records rollback URL ws orders COMMIT` (an audited `$rollback` commit;
185
+ `--expect-head` refuses if it moved meanwhile), `records restore URL ws
186
+ orders BACKUP_ID`.
187
+ - A deploy's `--schema fail` refuses rather than migrate; `--schema reset`
188
+ gives the record the package's initial value, its data lost.
189
+
190
+ ### 6. Files
191
+
192
+ ```bash
193
+ e3-cloud files deploy URL staging src/files.ts
194
+ e3-cloud files bind URL staging upload s3://bucket/acme/staging/
195
+ e3-cloud files sync URL staging
196
+ e3 dataflow run URL staging
197
+ ```
198
+
199
+ - The declarations (which inputs files fill) travel with releases; where
200
+ each source's files are (its binding) is each workspace's own. A staging
201
+ copied from production reads production's files until bound elsewhere.
202
+ - Before a release whose declarations read a source production hasn't bound,
203
+ bind it there: `files bind URL production invoices s3://…`. The release is
204
+ refused until then.
205
+ - A release syncs nothing: after it, `files sync URL production`, then run
206
+ production. Production refuses `files deploy` (9 for the exception).
207
+ Declaring and decoding the files: the e3-cloud-files skill.
208
+
209
+ ### 7. Rehearse and release
210
+
211
+ ```bash
212
+ e3 workspace copy URL production staging # staging becomes production as it is now
213
+ # … the change in staging (3–6), a run, a look in the console …
214
+ e3-cloud release URL staging production --plan
215
+ e3-cloud release URL staging production
216
+ e3-cloud files sync URL production # when Files changed
217
+ e3 dataflow run URL production
218
+ ```
219
+
220
+ - A release gives production exactly what staging runs: the package and app
221
+ versions by hash, the Files declarations, and the bundles staged for the
222
+ next e3. Production keeps its data (inputs people set kept, records
223
+ migrated), its bindings, schedules, task settings and people.
224
+ - The plan names the package, each app, record, input and Files input, the
225
+ apps' checks, and any admin change it undoes. A refused plan says why and
226
+ changes nothing; one that would change nothing is refused too.
227
+ - `--carry inputs/mappings` brings reference data set up in staging: an
228
+ input, or a record new in the release (a record production has is never
229
+ overwritten). `--remove-apps` removes production's apps staging lacks.
230
+ - A release that changes the package runs a deploy job, which the CLI waits
231
+ for. Only production's staging releases to it, and owners and admins
232
+ release.
233
+
234
+ ### 8. Roll back
235
+
236
+ ```bash
237
+ e3-cloud history URL production
238
+ e3-cloud rollback URL production --plan # the newest earlier state
239
+ e3-cloud rollback URL production 41 # an entry, by its id
240
+ ```
241
+
242
+ - A rollback releases an entry again: its package, apps and Files
243
+ declarations exactly. It undoes no data: inputs, records and runs stay.
244
+ - It is refused past a record migration (e3 never migrates a record back) and
245
+ for an app version the repository no longer keeps; the plan says which.
246
+ - Fix forward in staging afterwards: the next release brings staging's state
247
+ back to production.
248
+
249
+ ### 9. An emergency change
250
+
251
+ ```bash
252
+ e3-cloud app deploy URL production --from-source src/apps/ops.tsx --force
253
+ e3-cloud files deploy URL production src/files.ts --force
254
+ e3-cloud app remove URL production legacy --force
255
+ ```
256
+
257
+ - Admins only. Each warns first and lands in production's history as an
258
+ admin override.
259
+ - The next release from staging undoes it, and its plan says so: make the
260
+ same change in staging before then.
261
+ - No force deploys a package to production: release it from staging, or roll
262
+ back (8).
263
+
264
+ ### 10. People and roles
265
+
266
+ ```bash
267
+ e3-cloud role set URL Rostering --grant production/ops:roster,leave
268
+ e3-cloud user add URL sam@example.com --roles 'Rostering'
269
+ e3-cloud user add URL ana@example.com --role owner
270
+ e3-cloud user list URL
271
+ ```
272
+
273
+ - An owner manages the repository; a user reaches only the apps and pages
274
+ their business roles grant (`--grant WORKSPACE/APP`, its pages after a
275
+ colon; the built-in `All apps` grants every live app).
276
+ - Adding someone who hasn't signed in yet invites them, and the grant is
277
+ theirs at first sign-in. Admins: `user invitations`, `uninvite`,
278
+ `disable`, `enable`, `list-all`.
279
+ - Testers of staging need a role granting staging's apps; owners and admins
280
+ reach them already.
281
+
282
+ ### 11. Schedules, secrets and compute
283
+
284
+ ```bash
285
+ e3-cloud schedule set URL/workspaces/production --cron "0 2 * * *" --timezone Australia/Sydney
286
+ e3-cloud secrets set URL PARTNER_API_KEY < key.txt
287
+ e3-cloud compute set URL/workspaces/production forecast --size medium
288
+ e3-cloud timeout set URL/workspaces/production forecast --timeout 2h
289
+ ```
290
+
291
+ - A schedule runs its workspace's dataflow; each workspace has its own, and
292
+ releases and copies leave it as it is.
293
+ - Secrets are the repository's: every workspace's tasks read them by name
294
+ (`Env.get("NAME")`), and the next run reads a new value. Values are never
295
+ shown again.
296
+ - A task's compute size and timeout are its workspace's: a copy brings the
297
+ source's, a release leaves the target's.
298
+
299
+ ### 12. Feedback
300
+
301
+ ```bash
302
+ e3-cloud feedback enable URL/workspaces/production
303
+ e3-cloud feedback list URL/workspaces/production
304
+ e3-cloud feedback show URL/workspaces/production 12
305
+ e3-cloud feedback close URL/workspaces/production 12
306
+ ```
307
+
308
+ - Users send notes about any part of a page, with a screenshot and the data
309
+ behind the view. Owners and admins read and close them; each sender reads
310
+ their own (`feedback list --mine`).
311
+
312
+ ### 13. The platform's next e3
313
+
314
+ ```bash
315
+ e3-cloud app list-all https://e3.example.com # admins: which apps need a bundle
316
+ e3-cloud app deploy URL staging --from-source src/apps/ops.tsx --for-platform 1.0.93
317
+ e3-cloud release URL staging production
318
+ ```
319
+
320
+ - Build with the next release's packages and stage the bundle beside the
321
+ live app: it goes live when the platform moves to that e3. A release
322
+ carries staging's staged bundles, so production gets them through its
323
+ staging.
324
+
325
+ ### 14. Retire
326
+
327
+ ```bash
328
+ e3-cloud app remove URL staging legacy
329
+ e3-cloud release URL staging production --remove-apps
330
+ ```
331
+
332
+ - A workspace with apps isn't removed: remove its apps first. Neither is a
333
+ staging, or a workspace a staging protects: an admin stops the staging
334
+ first (`staging stop`). Then `e3 workspace remove URL old`.
335
+
59
336
  ## Apps
60
337
 
61
338
  - `app build <source>` writes the bundle (`dist/<app>.app`): the app and the
@@ -81,8 +358,9 @@ e3-cloud release https://e3.example.com/repos/acme staging production
81
358
 
82
359
  A workspace's staging is where changes are tried before they reach the
83
360
  workspace people use, which it then **protects**, as a protected branch is
84
- protected: its package and apps change only by a release from its staging, or
85
- a rollback. One staging to a workspace; no staging has one of its own.
361
+ protected: its package, apps and Files declarations change only by a release
362
+ from its staging, or a rollback. One staging to a workspace; no staging has
363
+ one of its own.
86
364
 
87
365
  - `staging add <repo-url> <live> <name>` prints what it would do, then adds it:
88
366
  a copy of the live workspace as it is now, data included. `--plan` stops at
@@ -92,16 +370,17 @@ a rollback. One staging to a workspace; no staging has one of its own.
92
370
  `staging stop <repo-url> <live>` (admins alone) unprotects it, deleting
93
371
  nothing.
94
372
  - A protected workspace refuses e3's `workspace deploy`, an app deploy or
95
- removal, a copy onto it, and its removal or its staging's. Its data goes on:
96
- inputs, records, runs and schedules.
373
+ removal, a Files deploy, a copy onto it, and its removal or its staging's.
374
+ Its data goes on: inputs, records, runs and schedules, and its Files
375
+ bindings and syncs.
97
376
 
98
377
  ## Releases
99
378
 
100
379
  A release gives the target exactly the package and app versions the source
101
- runs, by hash, with the bundles it has staged for the next e3. Only the
102
- target's staging releases to it. The target keeps the inputs people set in it,
103
- migrates its records, and runs next by hand or on its schedule. Owners and
104
- admins release.
380
+ runs, by hash, with the bundles it has staged for the next e3 and its Files
381
+ declarations. Only the target's staging releases to it. The target keeps the
382
+ inputs people set in it and where its files come from, migrates its records,
383
+ and runs next by hand or on its schedule. Owners and admins release.
105
384
 
106
385
  - `e3 workspace copy <repo-url> production staging` makes staging production
107
386
  as it is now, apps included: the rehearsal's first step before each change.
@@ -112,8 +391,31 @@ admins release.
112
391
  from the source by reference; `--remove-apps` removes the target's apps the
113
392
  source lacks.
114
393
  - `history <repo-url> <workspace>` lists each release, deploy, app deploy,
115
- copy and rollback, with its id; `rollback <repo-url> <workspace> [<id>]`
116
- releases an entry again, its package and apps exactly.
394
+ copy, rollback, Files deploy and source bound, with its id; `rollback
395
+ <repo-url> <workspace> [<id>]` releases an entry again, its package, apps
396
+ and Files declarations exactly.
397
+
398
+ ## Files
399
+
400
+ A solution declares in its code which of its inputs come from files in a
401
+ bucket (`@elaraai/e3-cloud-files`, its own skill). The CLI deploys the
402
+ declarations, binds each source and syncs, given a local repository's path or
403
+ a repository URL:
404
+
405
+ - `files deploy <repo> <ws> <source>` runs the source (its default export is
406
+ `Files.manifest(...)`) and checks the declarations against the package the
407
+ workspace runs; `--dry-run` keeps nothing. A protected workspace refuses
408
+ it: its declarations come by a release (`--force`, an admin's override).
409
+ - `files bind <repo> <ws> <source> s3://bucket/prefix/` says where a source's
410
+ files are; `--region` when the bucket's isn't yours. Locally only:
411
+ `--profile` (the profile to read with), `--latest n` (each folder's n
412
+ greatest keys). A workspace's bindings are its own: a release never
413
+ changes them, and a source may be bound before a release brings the
414
+ declarations that read it.
415
+ - `files sync <repo> <ws>` reads only files whose ETag or size changed and
416
+ writes only inputs whose files changed; `--input`, `--dry-run`. It doesn't
417
+ run the dataflow: `e3 dataflow run` does, and the sync prints the command.
418
+ - `files status <repo> <ws>`: the declarations, the bindings, the last sync.
117
419
 
118
420
  ## Traps
119
421
 
@@ -122,7 +424,11 @@ admins release.
122
424
  release (deploy the source again).
123
425
  - A rollback past a record migration is refused: e3's deploy never migrates a
124
426
  record back. A rollback stays open on a protected workspace.
125
- - Sign in with `e3 auth login <server>`: there is no `e3 login`.
427
+ - Sign in with `e3 auth login <server>`: there is no `e3 login`. A run is
428
+ `e3 dataflow run <repo> <ws>`: there is no `e3 start`.
429
+ - A release or a sync runs nothing: run the dataflow after either.
430
+ - A release is refused while its Files declarations read a source the target
431
+ hasn't bound: bind it in the target first.
126
432
  - `secrets set` reads the value from stdin unless `--value` is given; the
127
433
  next run reads the new value.
128
434
  - The version is the @elaraai lockstep: use the CLI of the release your
@@ -0,0 +1,195 @@
1
+ ---
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),… See the detailed scope below."
4
+ ---
5
+
6
+ ## Detailed skill scope
7
+
8
+ 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).
9
+
10
+ # e3-cloud-files — files as e3 inputs
11
+
12
+ A solution says in its code which of its inputs come from files in a bucket. Each
13
+ workspace says where those files are, a bucket and a prefix, and
14
+ `e3-cloud files sync` brings in what changed: on a local repository with your
15
+ own cloud credentials, in the cloud with the platform's read-only role. A file
16
+ arrives as it is, a `Blob`, and a task decodes it. Nothing polls: a sync runs
17
+ when someone asks.
18
+
19
+ Read `README.md` for the author's guide; this file is the short form.
20
+
21
+ ## Before writing code — search the example index
22
+
23
+ Every e3 cloud API has an example in the plugin's index: the decoding
24
+ functions of `@elaraai/e3-cloud-files`, and the hand-written entries for
25
+ `Files.source`, its shapes, `Files.manifest` and the `e3-cloud files`
26
+ commands. Before writing or changing Files code:
27
+
28
+ 1. Call `search_e3_cloud_examples` for the
29
+ part you are about to write. Summaries come back first: id, description
30
+ and keywords, a few hundred bytes each.
31
+ 2. Fetch the one or two that match with
32
+ `get_e3_cloud_example` and pattern your code
33
+ on them.
34
+ 3. For what a task does with the decoded data, search East's index too, with
35
+ the East plugin's `search_east_examples`.
36
+ 4. Do not read `node_modules/@elaraai/**` or `*.examples.ts` files
37
+ wholesale, and do not reason from `.d.ts` signatures: the index holds the
38
+ same code, exact and far cheaper.
39
+
40
+ Nothing is injected for you; the search is the step.
41
+
42
+ ## Quick Start
43
+
44
+ ```ts
45
+ // src/files.ts: which inputs the files fill
46
+ import { Files } from '@elaraai/e3-cloud-files';
47
+ export const upload = Files.source('upload');
48
+ export const rates = upload.file('rates', { key: 'rates.json' });
49
+ export default Files.manifest([upload]);
50
+
51
+ // src/package.ts: a task decodes the file
52
+ const decodeRates = East.function([BlobType], ArrayType(Rate), (_$, file) => file.decodeUtf8().parseJson(ArrayType(Rate)));
53
+ export const rateTable = e3.task('rate_table', [rates], decodeRates, { runner });
54
+ export default e3.package('orders', '1.0.0', rateTable);
55
+ ```
56
+
57
+ ```bash
58
+ e3 workspace deploy ./repo dev --from-source src/package.ts
59
+ e3-cloud files deploy ./repo dev src/files.ts # checked against the package dev runs
60
+ e3-cloud files bind ./repo dev upload s3://bucket/prefix/ --profile p # where upload's files are, for dev
61
+ e3-cloud files sync ./repo dev # what changed comes in
62
+ e3 dataflow run ./repo dev # the dataflow runs on it
63
+ ```
64
+
65
+ ## Decision Tree
66
+
67
+ ```
68
+ What do you need?
69
+ ├─ Declare which inputs a bucket's files fill (src/files.ts)
70
+ │ ├─ A place files come from → Files.source(name) — each workspace binds it to s3://bucket/prefix/
71
+ │ ├─ One known file → source.file(input, { key }) input: Blob
72
+ │ ├─ The newest of a folder → source.latest(input, { prefix, include? }) input: Blob, the greatest key
73
+ │ ├─ Every file of a folder → source.folder(input, { prefix, include? }) input: Dict<String, Blob> by path
74
+ │ ├─ A folder's files, not bytes → source.listing(input, { prefix, include? }) input: Dict<String, {size, modified, etag}>
75
+ │ ├─ Subfolders' files too → include: '**/*.csv' ('*.csv' is the top level only)
76
+ │ └─ What deploy deploys → export default Files.manifest([source, …])
77
+ ├─ Decode what arrives, in a task
78
+ │ ├─ East's JSON → file.decodeUtf8().parseJson(T)
79
+ │ ├─ CSV with a header → file.decodeCsv(Row, { delimiter: ',' })
80
+ │ ├─ Lines of text → file.decodeUtf8().split('\n')
81
+ │ ├─ A folder's files as one array → files.flattenToArray(($, file, path) => …)
82
+ │ ├─ Some of a folder's files → files.filter(($, file, path) => path.startsWith('2026/'))
83
+ │ └─ Choose files by name or time → a listing: rows.filter(($, row) => row.modified.greaterThan(since))
84
+ ├─ Bring the files into a workspace (the e3-cloud CLI; <repo> a local path or a repository URL)
85
+ │ ├─ Deploy the declarations → e3-cloud files deploy <repo> <ws> src/files.ts [--dry-run]
86
+ │ ├─ Say where a source's files are → e3-cloud files bind <repo> <ws> <source> s3://bucket/prefix/ [--region r]
87
+ │ │ └─ Locally only → --profile <aws profile>, --latest <n> (each folder's n greatest keys)
88
+ │ ├─ Bring in what changed → e3-cloud files sync <repo> <ws> [--input name…] [--dry-run]
89
+ │ ├─ What is declared, bound, synced → e3-cloud files status <repo> <ws>
90
+ │ └─ Run the dataflow on it → e3 dataflow run <repo> <ws> (a sync doesn't run it)
91
+ ├─ Production: a workspace with a staging (a deployed repository's URL)
92
+ │ ├─ Change its declarations → files deploy to the staging, then e3-cloud release <url> <staging> <production>
93
+ │ ├─ A source it doesn't bind yet → e3-cloud files bind <url> <production> <source> s3://… before the release
94
+ │ ├─ See what a release does → e3-cloud release … --plan (each input added, changed, unchanged, removed)
95
+ │ ├─ After a release → e3-cloud files sync <url> <production>, then e3 dataflow run
96
+ │ ├─ Undo a release → e3-cloud rollback <url> <production> (declarations go back with the package)
97
+ │ ├─ An emergency change → e3-cloud files deploy … --force (an admin's; history records it; the next release undoes it)
98
+ │ └─ Rehearse on production's files → e3-cloud staging add … (a copy: declarations, bindings, what each input took)
99
+ ├─ Keep synced inputs across a package deploy → e3 workspace deploy … --inputs keep-edited
100
+ └─ Drive it from code
101
+ ├─ The checks → @elaraai/e3-cloud-files/check: checkFiles, checkBindings, readPackageInputs
102
+ ├─ The sync over your own ports → @elaraai/e3-cloud-files/sync: syncFiles (FilesReader, FilesWriter, FilesLedger)
103
+ ├─ Writing an e3 store → @elaraai/e3-cloud-files/storage: storageWriter(storage, repo, ws)
104
+ └─ Reading a bucket → @elaraai/e3-cloud-files/s3: s3FilesReader(client, { bucket, prefix })
105
+ ```
106
+
107
+ ## Declare the files
108
+
109
+ ```ts
110
+ // src/files.ts
111
+ import { Files } from '@elaraai/e3-cloud-files';
112
+
113
+ export const upload = Files.source('upload'); // each workspace binds it to a bucket and prefix
114
+
115
+ export const rates = upload.file('rates', { key: 'rates.json' }); // input: Blob
116
+ export const stock = upload.latest('stock', { prefix: 'stock/', include: '*.json' }); // input: Blob, the greatest key
117
+ export const orders = upload.folder('orders', { prefix: 'orders/', include: '*.json' }); // input: Dict<String, Blob>
118
+ export const orderFiles = upload.listing('order_files', { prefix: 'orders/' }); // input: each file's size, modified, etag
119
+
120
+ export default Files.manifest([upload]); // what e3-cloud files deploy deploys
121
+ ```
122
+
123
+ - Each shape is an ordinary `e3.input`, unassigned until a sync sets it: use
124
+ it in tasks as any input, and give the tasks to `e3.package` as usual.
125
+ - Keys and prefixes are relative to where a workspace binds the source; a
126
+ prefix is `''` or ends with `/`.
127
+ - `include` is a glob over the path under the prefix: `*` within one folder,
128
+ `**` across folders, `?` one character, `[a-z]` one of a set (`[!_]` any
129
+ but), `{csv,json}` any of its alternatives (`*.{csv,json}`).
130
+ - `latest` takes the greatest key: name files so the newest sorts last
131
+ (`2026-10-02.json`).
132
+
133
+ ## Decode in a task
134
+
135
+ ```ts
136
+ const decodeRates = East.function([BlobType], ArrayType(Rate), (_$, file) =>
137
+ file.decodeUtf8().parseJson(ArrayType(Rate)));
138
+ const decodeOrders = East.function([FilesFolderType], ArrayType(Order), (_$, files) =>
139
+ files.flattenToArray((_$, file) => file.decodeUtf8().parseJson(ArrayType(Order))));
140
+
141
+ export const rateTable = e3.task('rate_table', [rates], decodeRates, { runner });
142
+ ```
143
+
144
+ CSV: `file.decodeCsv(Row, { delimiter: ',' })`. A folder is keyed by each
145
+ file's path under the prefix, in path order.
146
+
147
+ ## Deploy, bind and sync
148
+
149
+ ```
150
+ e3-cloud files deploy <repo> <workspace> src/files.ts [--dry-run]
151
+ e3-cloud files bind <repo> <workspace> upload s3://bucket/prefix/ [--region r] [--profile p] [--latest n]
152
+ e3-cloud files sync <repo> <workspace> [--input name...] [--dry-run]
153
+ e3-cloud files status <repo> <workspace>
154
+ e3 dataflow run <repo> <workspace>
155
+ ```
156
+
157
+ `<repo>` is a local repository's path or a repository URL. Deploy the package
158
+ first (`e3 workspace deploy`), then the declarations, which are checked
159
+ against it. A sync reads only files whose ETag or size changed, writes only
160
+ inputs whose files changed, and prints the next command when any did. It
161
+ doesn't run the dataflow: `e3 dataflow run` does. Locally the bytes land only in the
162
+ repository; `--profile` names the profile to read with and `--latest n`
163
+ keeps only each folder's n greatest keys.
164
+
165
+ ## Traps
166
+
167
+ - An input a mapping fills must be writable and of exactly its shape's type:
168
+ `Blob`, `Dict<String, Blob>` (`FilesFolderType`) or the listing's
169
+ (`FilesListingType`). Declare it through the shape; never write it with
170
+ `e3.input` yourself.
171
+ - A file over 1 GiB less 64 KiB (`file`, `latest`), or over 64 MiB less
172
+ 64 KiB in a folder, is refused and named in the report, never truncated.
173
+ So is a file the sync can't read (archived, under a key it may not use,
174
+ changed while it read it): the sync goes on, and the next tries it again.
175
+ - Deploying a new package can make the declarations no longer fit: a sync then
176
+ refuses until they're deployed again. A package deploy also resets synced
177
+ inputs unless it keeps them (`--inputs keep-edited`); the next sync takes
178
+ them again whole.
179
+ - A changed declaration, or a source bound elsewhere, has its inputs taken
180
+ again whole at the next sync.
181
+ - A workspace with a staging refuses `files deploy` (`workspace_protected`):
182
+ deploy to the staging and release. A release is refused while the
183
+ declarations read a source the production workspace hasn't bound: bind it
184
+ there first. A release syncs nothing: sync production after it.
185
+ - Bindings never travel with a release; a copy carries them. A staging
186
+ started as a copy of production reads production's files until you bind it
187
+ elsewhere.
188
+ - The version is the @elaraai lockstep: use the same version as east and e3.
189
+
190
+ ## Related skills
191
+
192
+ - **e3** — packages, tasks, inputs, the dataflow and the `e3` CLI.
193
+ - **east** — the East language a task's decoding is written in.
194
+ - **e3-cloud-cli** — the rest of the `e3-cloud` CLI: apps, staging, releases,
195
+ rollbacks, users, secrets and schedules, and the app lifecycle guides.