@elaraai/e3-cloud-claude-plugin 1.0.91 → 1.0.93

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.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/host.js
1
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/hook-io.js
9
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/transcript.js
37
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-guidance.js
40
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-hooks.js
82
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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
  }
@@ -108,9 +108,9 @@ var E3_CLOUD_GUIDANCE = {
108
108
  corpus: E3_CLOUD_CORPUS,
109
109
  scope: "@elaraai"
110
110
  };
111
- var E3_CLOUD_CODE = /@elaraai\/e3-cloud-(?:ui|client)\b/;
112
- var PACKAGE_PATH = /[/\\]node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|cli|client)(?:[/\\]|$)/;
113
- var PACKAGE_PATTERN = /(?:^|[/\\])node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|cli|client)(?:[/\\]|$)/;
111
+ var E3_CLOUD_CODE = /@elaraai\/e3-cloud-(?:ui|files|client)\b/;
112
+ var PACKAGE_PATH = /[/\\]node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|files|cli|client)(?:[/\\]|$)/;
113
+ var PACKAGE_PATTERN = /(?:^|[/\\])node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|files|cli|client)(?:[/\\]|$)/;
114
114
  function isE3CloudCorpusRead(tool, input) {
115
115
  const text = (key) => typeof input[key] === "string" ? input[key] : "";
116
116
  return tool === "Read" ? PACKAGE_PATH.test(text("file_path")) : PACKAGE_PATH.test(text("path")) || PACKAGE_PATTERN.test(text("pattern"));
@@ -132,7 +132,7 @@ var E3_CLOUD_CODEX_SEARCH_GATE = {
132
132
  gateText: E3_CLOUD_SEARCH_GATE.gateText,
133
133
  readText: E3_CLOUD_READ_REMINDER.readText,
134
134
  isCorpusRead: isE3CloudCorpusRead,
135
- shellSweep: /node_modules\/@elaraai\/e3-cloud-(?:ui|cli|client)\b/,
135
+ shellSweep: /node_modules\/@elaraai\/e3-cloud-(?:ui|files|cli|client)\b/,
136
136
  corpusCode: E3_CLOUD_CODE,
137
137
  markerPrefix: "e3-cloud-codex-search-",
138
138
  requireSearchEnv: "E3_CLOUD_REQUIRE_SEARCH"
@@ -1,10 +1,10 @@
1
- // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-hooks.js
1
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/host.js
7
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/hook-io.js
15
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/transcript.js
43
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-guidance.js
56
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.91_typescript@5.9.3/node_modules/@elaraai/east-plugin/dist/lib/search-hooks.js
98
+ // ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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;
@@ -155,9 +155,9 @@ var E3_CLOUD_GUIDANCE = {
155
155
  corpus: E3_CLOUD_CORPUS,
156
156
  scope: "@elaraai"
157
157
  };
158
- var E3_CLOUD_CODE = /@elaraai\/e3-cloud-(?:ui|client)\b/;
159
- var PACKAGE_PATH = /[/\\]node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|cli|client)(?:[/\\]|$)/;
160
- var PACKAGE_PATTERN = /(?:^|[/\\])node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|cli|client)(?:[/\\]|$)/;
158
+ var E3_CLOUD_CODE = /@elaraai\/e3-cloud-(?:ui|files|client)\b/;
159
+ var PACKAGE_PATH = /[/\\]node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|files|cli|client)(?:[/\\]|$)/;
160
+ var PACKAGE_PATTERN = /(?:^|[/\\])node_modules[/\\]@elaraai[/\\]e3-cloud-(?:ui|files|cli|client)(?:[/\\]|$)/;
161
161
  function isE3CloudCorpusRead(tool, input) {
162
162
  const text = (key) => typeof input[key] === "string" ? input[key] : "";
163
163
  return tool === "Read" ? PACKAGE_PATH.test(text("file_path")) : PACKAGE_PATH.test(text("path")) || PACKAGE_PATTERN.test(text("pattern"));
@@ -179,7 +179,7 @@ var E3_CLOUD_CODEX_SEARCH_GATE = {
179
179
  gateText: E3_CLOUD_SEARCH_GATE.gateText,
180
180
  readText: E3_CLOUD_READ_REMINDER.readText,
181
181
  isCorpusRead: isE3CloudCorpusRead,
182
- shellSweep: /node_modules\/@elaraai\/e3-cloud-(?:ui|cli|client)\b/,
182
+ shellSweep: /node_modules\/@elaraai\/e3-cloud-(?:ui|files|cli|client)\b/,
183
183
  corpusCode: E3_CLOUD_CODE,
184
184
  markerPrefix: "e3-cloud-codex-search-",
185
185
  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.93_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.93_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.93_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.93_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.93_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",
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.93",
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",
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # e3 cloud plugin for Claude Code
2
2
 
3
3
  A Claude Code plugin for building apps on e3 cloud with `@elaraai/e3-cloud-ui`,
4
- running deployments with the `e3-cloud` CLI, and driving them from code with
5
- `@elaraai/e3-cloud-client`. It follows East's plugin, and is installed beside
4
+ bringing files from buckets into a solution's inputs with
5
+ `@elaraai/e3-cloud-files`, running deployments with the `e3-cloud` CLI, and
6
+ driving them from code with `@elaraai/e3-cloud-client`. It follows East's plugin, and is installed beside
6
7
  it: East's plugin covers East, e3 and e3-ui, which an app's pages are written
7
8
  in.
8
9
 
@@ -40,7 +41,8 @@ elaraai`), or without asking once auto-update is on for the marketplace under
40
41
  | Skill | Package | Description |
41
42
  |-------|---------|-------------|
42
43
  | `e3-cloud-ui` | `@elaraai/e3-cloud-ui` | Apps: `Page` (a thunk body), `Page.task`, `Menu`, `App`, `PageLink`, `Nav`, the build and its refusals, the checks, the East types |
43
- | `e3-cloud-cli` | `@elaraai/e3-cloud-cli` | The CLI: sign-in, users and roles, secrets, schedules, task configs, records, feedback, apps, releases, history and rollback |
44
+ | `e3-cloud-files` | `@elaraai/e3-cloud-files` | Files: `Files.source` and its `file`, `latest`, `folder` and `listing`, `Files.manifest`, decoding what arrives in a task, the checks, the sync, the East types |
45
+ | `e3-cloud-cli` | `@elaraai/e3-cloud-cli` | The CLI: sign-in, users and roles, secrets, schedules, task configs, records, feedback, apps, files, releases, history and rollback |
44
46
  | `e3-cloud-client` | `@elaraai/e3-cloud-client` | Driving a deployment from code |
45
47
 
46
48
  ## Example search
@@ -52,13 +54,14 @@ The plugin's MCP server (`e3-cloud`) serves e3 cloud's example index:
52
54
  code.
53
55
  - **`get_e3_cloud_example(id, language)`**: one example in full.
54
56
 
55
- It holds `@elaraai/e3-cloud-ui`'s page bodies, and hand-written entries for
56
- `App`, `Page.task`, `buildApp` and `checkApp`, every CLI command, and the
57
- client's calls. The `PreToolUse(Edit|Write)` hook is the gate: the first write
58
- of code importing `@elaraai/e3-cloud-ui` or `@elaraai/e3-cloud-client` in a
57
+ It holds `@elaraai/e3-cloud-ui`'s page bodies, `@elaraai/e3-cloud-files`'
58
+ decoding functions, and hand-written entries for `App`, `Page.task`,
59
+ `buildApp` and `checkApp`, Files' declarations and its sync in tests, every CLI
60
+ command, and the client's calls. The `PreToolUse(Edit|Write)` hook is the gate: the first write
61
+ of code importing `@elaraai/e3-cloud-ui`, `@elaraai/e3-cloud-files` or `@elaraai/e3-cloud-client` in a
59
62
  session with no search on record (of e3 cloud's index or East's) is told to
60
63
  search first; `E3_CLOUD_REQUIRE_SEARCH=deny` makes it a refusal. A `Read`,
61
- `Grep` or `Glob` of `node_modules/@elaraai/e3-cloud-{ui,cli,client}` is
64
+ `Grep` or `Glob` of `node_modules/@elaraai/e3-cloud-{ui,files,cli,client}` is
62
65
  reminded that the index is the same code, cheaper.
63
66
 
64
67
  ## Version
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-claude-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.93",
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,20 +1,21 @@
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`, `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 …`)."
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 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."
4
4
  ---
5
5
 
6
6
  # e3-cloud — the e3 cloud CLI (@elaraai/e3-cloud-cli)
7
7
 
8
8
  `e3-cloud` runs the parts of an e3 cloud deployment that e3's own CLI does not:
9
9
  apps, releases, users and roles, secrets, schedules, task configs, records'
10
- history and backups, and feedback. e3's CLI (`e3`, `@elaraai/e3-cli`) still
10
+ history and backups, feedback, and files from buckets. e3's CLI (`e3`, `@elaraai/e3-cli`) still
11
11
  does what it does against any server: repositories, packages, workspaces,
12
12
  datasets, dataflow runs and copies. Both sign in with `e3 auth login` and share its
13
13
  credentials (`~/.e3/credentials.json`).
14
14
 
15
15
  A command names what it acts on by URL: a server (`https://e3.example.com`),
16
16
  a repository (`…/repos/acme`), or a workspace (`…/repos/acme/workspaces/production`),
17
- as its help says.
17
+ as its help says. `e3-cloud files` takes a local repository's path instead
18
+ as readily.
18
19
 
19
20
  ## Before writing code — search the example index
20
21
 
@@ -50,8 +51,284 @@ e3-cloud staging add https://e3.example.com/repos/acme production staging
50
51
  # A release: the plan first, then exactly what staging runs, production's data kept
51
52
  e3-cloud release https://e3.example.com/repos/acme staging production --plan
52
53
  e3-cloud release https://e3.example.com/repos/acme staging production
54
+ e3 dataflow run https://e3.example.com/repos/acme production # a release runs nothing
53
55
  ```
54
56
 
57
+ ## Lifecycle guides
58
+
59
+ Each guide is the commands in order, then what to watch. `URL` stands for a
60
+ repository's URL (`https://e3.example.com/repos/acme`); a workspace's URL is
61
+ `URL/workspaces/<name>`.
62
+
63
+ ```
64
+ What are you doing?
65
+ ├─ Setting up
66
+ │ ├─ A new solution in the cloud → 1. Start a solution
67
+ │ └─ Production, and a staging to try changes in → 2. Add a staging
68
+ ├─ Changing the solution (always in staging)
69
+ │ ├─ An app's pages or menus → 3. Develop an app
70
+ │ ├─ Tasks, inputs or functions → 4. Change the package
71
+ │ ├─ A record's type, or its values → 5. Migrate a record
72
+ │ └─ Which inputs come from files → 6. Files
73
+ ├─ Shipping to production
74
+ │ ├─ Rehearse on production's data, then release → 7. Rehearse and release
75
+ │ ├─ Bring reference data set up in staging → 7, --carry
76
+ │ ├─ Undo a release → 8. Roll back
77
+ │ └─ A fix that can't wait for staging → 9. An emergency change
78
+ ├─ Running it
79
+ │ ├─ Who may use which apps and pages → 10. People and roles
80
+ │ ├─ Runs on a timetable, credentials, task sizes → 11. Schedules, secrets, compute
81
+ │ ├─ What users report from the pages → 12. Feedback
82
+ │ └─ A record someone got wrong → 5, record rollback and backups
83
+ └─ Changing the platform, or ending things
84
+ ├─ The platform's next e3 → 13. The platform's next e3
85
+ └─ An app or a workspace no longer needed → 14. Retire
86
+ ```
87
+
88
+ ### 1. Start a solution
89
+
90
+ ```bash
91
+ e3 auth login https://e3.example.com
92
+ e3 repo create URL
93
+ e3 workspace deploy URL production --from-source src/package.ts # makes the workspace
94
+ e3-cloud user add URL ana@example.com --role owner
95
+ e3-cloud app deploy URL production --from-source src/apps/ops.tsx
96
+ e3 dataflow run URL production
97
+ ```
98
+
99
+ - A repository holds one solution; a workspace runs one version of its
100
+ package, with its own data.
101
+ - Add a staging (2) before anyone relies on production: from then on every
102
+ change goes through it.
103
+
104
+ ### 2. Add a staging
105
+
106
+ ```bash
107
+ e3-cloud staging add URL production staging --plan
108
+ e3-cloud staging add URL production staging
109
+ e3-cloud staging list URL
110
+ ```
111
+
112
+ - The staging starts as a copy of production, data included: its package,
113
+ inputs, records, apps, task settings and Files (declarations, bindings and
114
+ what each input took). `--as-is` makes a workspace there the staging as it
115
+ stands; `--replace` copies over one.
116
+ - Production is then protected: its package, apps and Files declarations
117
+ change only by a release from its staging, or a rollback. People go on
118
+ using it: inputs, records, runs and schedules.
119
+ - Only an admin stops a staging (`staging stop URL production`), which
120
+ deletes nothing and unprotects production.
121
+
122
+ ### 3. Develop an app
123
+
124
+ ```bash
125
+ e3-cloud app deploy URL staging --from-source src/apps/ops.tsx --dry-run
126
+ e3-cloud app deploy URL staging --from-source src/apps/ops.tsx
127
+ e3-cloud app list URL staging
128
+ e3-cloud app versions URL staging ops
129
+ ```
130
+
131
+ - Apps are developed in the cloud: deploy to staging (or a workspace of your
132
+ own) after each change, and open it in the console. Nothing serves an app
133
+ locally.
134
+ - An app is checked against the package it was built against. When staging
135
+ runs that package, only the app changes; otherwise the package deploys
136
+ first, keeping the inputs people set, and the app goes live as it
137
+ completes.
138
+ - Several apps deploy together or not at all: `--from-source a.tsx b.tsx`.
139
+ Writing pages and menus: the e3-cloud-ui skill.
140
+
141
+ ### 4. Change the package
142
+
143
+ ```bash
144
+ e3 workspace deploy URL staging --from-source src/package.ts --inputs keep-edited --plan
145
+ e3 workspace deploy URL staging --from-source src/package.ts --inputs keep-edited
146
+ e3 dataflow run URL staging
147
+ ```
148
+
149
+ - `--plan` says what the deploy does to each record, index and input, and
150
+ writes nothing. `--inputs keep-edited` keeps the inputs people set while
151
+ their type is unchanged; the default resets every input to the package's.
152
+ - A deploy that would break an app of the workspace is refused, naming each
153
+ app's problems: rebuild the apps against the new package and deploy them
154
+ with it (`e3-cloud app deploy … --from-source`, which deploys the package
155
+ first).
156
+ - A deploy runs nothing: the next run recomputes the tasks. Files
157
+ declarations that no longer fit the package make a sync refuse until
158
+ they're deployed again (6).
159
+
160
+ ### 5. Migrate a record
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:
165
+
166
+ ```bash
167
+ e3-cloud records backup URL staging orders --note "before orders v2"
168
+ e3 workspace deploy URL staging --from-source src/package.ts --inputs keep-edited --plan # orders: migrate (1 step)
169
+ e3 workspace deploy URL staging --from-source src/package.ts --inputs keep-edited
170
+ e3-cloud records backup URL production orders --note "before the release of orders v2"
171
+ e3-cloud release URL staging production --plan # orders migrates (1 step)
172
+ ```
173
+
174
+ - A deploy runs only the steps a workspace hasn't applied: production runs
175
+ them as the release deploys.
176
+ - A migration is one way. A rollback past it is refused, and so is a restore
177
+ of a backup from before it, so back production up and rehearse first (7).
178
+ - Fixing a record's values needs no deploy: `records history` (who changed
179
+ what, and when), `records show URL ws orders COMMIT` (its value then),
180
+ `records rollback URL ws orders COMMIT` (an audited `$rollback` commit;
181
+ `--expect-head` refuses if it moved meanwhile), `records restore URL ws
182
+ orders BACKUP_ID`.
183
+ - A deploy's `--schema fail` refuses rather than migrate; `--schema reset`
184
+ gives the record the package's initial value, its data lost.
185
+
186
+ ### 6. Files
187
+
188
+ ```bash
189
+ e3-cloud files deploy URL staging src/files.ts
190
+ e3-cloud files bind URL staging upload s3://bucket/acme/staging/
191
+ e3-cloud files sync URL staging
192
+ e3 dataflow run URL staging
193
+ ```
194
+
195
+ - The declarations (which inputs files fill) travel with releases; where
196
+ each source's files are (its binding) is each workspace's own. A staging
197
+ copied from production reads production's files until bound elsewhere.
198
+ - Before a release whose declarations read a source production hasn't bound,
199
+ bind it there: `files bind URL production invoices s3://…`. The release is
200
+ refused until then.
201
+ - A release syncs nothing: after it, `files sync URL production`, then run
202
+ production. Production refuses `files deploy` (9 for the exception).
203
+ Declaring and decoding the files: the e3-cloud-files skill.
204
+
205
+ ### 7. Rehearse and release
206
+
207
+ ```bash
208
+ e3 workspace copy URL production staging # staging becomes production as it is now
209
+ # … the change in staging (3–6), a run, a look in the console …
210
+ e3-cloud release URL staging production --plan
211
+ e3-cloud release URL staging production
212
+ e3-cloud files sync URL production # when Files changed
213
+ e3 dataflow run URL production
214
+ ```
215
+
216
+ - A release gives production exactly what staging runs: the package and app
217
+ versions by hash, the Files declarations, and the bundles staged for the
218
+ next e3. Production keeps its data (inputs people set kept, records
219
+ migrated), its bindings, schedules, task settings and people.
220
+ - The plan names the package, each app, record, input and Files input, the
221
+ apps' checks, and any admin change it undoes. A refused plan says why and
222
+ changes nothing; one that would change nothing is refused too.
223
+ - `--carry inputs/mappings` brings reference data set up in staging: an
224
+ input, or a record new in the release (a record production has is never
225
+ overwritten). `--remove-apps` removes production's apps staging lacks.
226
+ - A release that changes the package runs a deploy job, which the CLI waits
227
+ for. Only production's staging releases to it, and owners and admins
228
+ release.
229
+
230
+ ### 8. Roll back
231
+
232
+ ```bash
233
+ e3-cloud history URL production
234
+ e3-cloud rollback URL production --plan # the newest earlier state
235
+ e3-cloud rollback URL production 41 # an entry, by its id
236
+ ```
237
+
238
+ - A rollback releases an entry again: its package, apps and Files
239
+ declarations exactly. It undoes no data: inputs, records and runs stay.
240
+ - It is refused past a record migration (e3 never migrates a record back) and
241
+ for an app version the repository no longer keeps; the plan says which.
242
+ - Fix forward in staging afterwards: the next release brings staging's state
243
+ back to production.
244
+
245
+ ### 9. An emergency change
246
+
247
+ ```bash
248
+ e3-cloud app deploy URL production --from-source src/apps/ops.tsx --force
249
+ e3-cloud files deploy URL production src/files.ts --force
250
+ e3-cloud app remove URL production legacy --force
251
+ ```
252
+
253
+ - Admins only. Each warns first and lands in production's history as an
254
+ admin override.
255
+ - The next release from staging undoes it, and its plan says so: make the
256
+ same change in staging before then.
257
+ - No force deploys a package to production: release it from staging, or roll
258
+ back (8).
259
+
260
+ ### 10. People and roles
261
+
262
+ ```bash
263
+ e3-cloud role set URL Rostering --grant production/ops:roster,leave
264
+ e3-cloud user add URL sam@example.com --roles 'Rostering'
265
+ e3-cloud user add URL ana@example.com --role owner
266
+ e3-cloud user list URL
267
+ ```
268
+
269
+ - An owner manages the repository; a user reaches only the apps and pages
270
+ their business roles grant (`--grant WORKSPACE/APP`, its pages after a
271
+ colon; the built-in `All apps` grants every live app).
272
+ - Adding someone who hasn't signed in yet invites them, and the grant is
273
+ theirs at first sign-in. Admins: `user invitations`, `uninvite`,
274
+ `disable`, `enable`, `list-all`.
275
+ - Testers of staging need a role granting staging's apps; owners and admins
276
+ reach them already.
277
+
278
+ ### 11. Schedules, secrets and compute
279
+
280
+ ```bash
281
+ e3-cloud schedule set URL/workspaces/production --cron "0 2 * * *" --timezone Australia/Sydney
282
+ e3-cloud secrets set URL PARTNER_API_KEY < key.txt
283
+ e3-cloud compute set URL/workspaces/production forecast --size medium
284
+ e3-cloud timeout set URL/workspaces/production forecast --timeout 2h
285
+ ```
286
+
287
+ - A schedule runs its workspace's dataflow; each workspace has its own, and
288
+ releases and copies leave it as it is.
289
+ - Secrets are the repository's: every workspace's tasks read them by name
290
+ (`Env.get("NAME")`), and the next run reads a new value. Values are never
291
+ shown again.
292
+ - A task's compute size and timeout are its workspace's: a copy brings the
293
+ source's, a release leaves the target's.
294
+
295
+ ### 12. Feedback
296
+
297
+ ```bash
298
+ e3-cloud feedback enable URL/workspaces/production
299
+ e3-cloud feedback list URL/workspaces/production
300
+ e3-cloud feedback show URL/workspaces/production 12
301
+ e3-cloud feedback close URL/workspaces/production 12
302
+ ```
303
+
304
+ - Users send notes about any part of a page, with a screenshot and the data
305
+ behind the view. Owners and admins read and close them; each sender reads
306
+ their own (`feedback list --mine`).
307
+
308
+ ### 13. The platform's next e3
309
+
310
+ ```bash
311
+ e3-cloud app list-all https://e3.example.com # admins: which apps need a bundle
312
+ e3-cloud app deploy URL staging --from-source src/apps/ops.tsx --for-platform 1.0.93
313
+ e3-cloud release URL staging production
314
+ ```
315
+
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
+ carries staging's staged bundles, so production gets them through its
319
+ staging.
320
+
321
+ ### 14. Retire
322
+
323
+ ```bash
324
+ e3-cloud app remove URL staging legacy
325
+ e3-cloud release URL staging production --remove-apps
326
+ ```
327
+
328
+ - A workspace with apps isn't removed: remove its apps first. Neither is a
329
+ staging, or a workspace a staging protects: an admin stops the staging
330
+ first (`staging stop`). Then `e3 workspace remove URL old`.
331
+
55
332
  ## Apps
56
333
 
57
334
  - `app build <source>` writes the bundle (`dist/<app>.app`): the app and the
@@ -77,8 +354,9 @@ e3-cloud release https://e3.example.com/repos/acme staging production
77
354
 
78
355
  A workspace's staging is where changes are tried before they reach the
79
356
  workspace people use, which it then **protects**, as a protected branch is
80
- protected: its package and apps change only by a release from its staging, or
81
- a rollback. One staging to a workspace; no staging has one of its own.
357
+ protected: its package, apps and Files declarations change only by a release
358
+ from its staging, or a rollback. One staging to a workspace; no staging has
359
+ one of its own.
82
360
 
83
361
  - `staging add <repo-url> <live> <name>` prints what it would do, then adds it:
84
362
  a copy of the live workspace as it is now, data included. `--plan` stops at
@@ -88,16 +366,17 @@ a rollback. One staging to a workspace; no staging has one of its own.
88
366
  `staging stop <repo-url> <live>` (admins alone) unprotects it, deleting
89
367
  nothing.
90
368
  - A protected workspace refuses e3's `workspace deploy`, an app deploy or
91
- removal, a copy onto it, and its removal or its staging's. Its data goes on:
92
- inputs, records, runs and schedules.
369
+ removal, a Files deploy, a copy onto it, and its removal or its staging's.
370
+ Its data goes on: inputs, records, runs and schedules, and its Files
371
+ bindings and syncs.
93
372
 
94
373
  ## Releases
95
374
 
96
375
  A release gives the target exactly the package and app versions the source
97
- runs, by hash, with the bundles it has staged for the next e3. Only the
98
- target's staging releases to it. The target keeps the inputs people set in it,
99
- migrates its records, and runs next by hand or on its schedule. Owners and
100
- admins release.
376
+ runs, by hash, with the bundles it has staged for the next e3 and its Files
377
+ declarations. Only the target's staging releases to it. The target keeps the
378
+ inputs people set in it and where its files come from, migrates its records,
379
+ and runs next by hand or on its schedule. Owners and admins release.
101
380
 
102
381
  - `e3 workspace copy <repo-url> production staging` makes staging production
103
382
  as it is now, apps included: the rehearsal's first step before each change.
@@ -108,8 +387,31 @@ admins release.
108
387
  from the source by reference; `--remove-apps` removes the target's apps the
109
388
  source lacks.
110
389
  - `history <repo-url> <workspace>` lists each release, deploy, app deploy,
111
- copy and rollback, with its id; `rollback <repo-url> <workspace> [<id>]`
112
- releases an entry again, its package and apps exactly.
390
+ copy, rollback, Files deploy and source bound, with its id; `rollback
391
+ <repo-url> <workspace> [<id>]` releases an entry again, its package, apps
392
+ and Files declarations exactly.
393
+
394
+ ## Files
395
+
396
+ A solution declares in its code which of its inputs come from files in a
397
+ bucket (`@elaraai/e3-cloud-files`, its own skill). The CLI deploys the
398
+ declarations, binds each source and syncs, given a local repository's path or
399
+ a repository URL:
400
+
401
+ - `files deploy <repo> <ws> <source>` runs the source (its default export is
402
+ `Files.manifest(...)`) and checks the declarations against the package the
403
+ workspace runs; `--dry-run` keeps nothing. A protected workspace refuses
404
+ it: its declarations come by a release (`--force`, an admin's override).
405
+ - `files bind <repo> <ws> <source> s3://bucket/prefix/` says where a source's
406
+ files are; `--region` when the bucket's isn't yours. Locally only:
407
+ `--profile` (the profile to read with), `--latest n` (each folder's n
408
+ greatest keys). A workspace's bindings are its own: a release never
409
+ changes them, and a source may be bound before a release brings the
410
+ declarations that read it.
411
+ - `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
413
+ run the dataflow: `e3 dataflow run` does, and the sync prints the command.
414
+ - `files status <repo> <ws>`: the declarations, the bindings, the last sync.
113
415
 
114
416
  ## Traps
115
417
 
@@ -118,7 +420,11 @@ admins release.
118
420
  release (deploy the source again).
119
421
  - A rollback past a record migration is refused: e3's deploy never migrates a
120
422
  record back. A rollback stays open on a protected workspace.
121
- - Sign in with `e3 auth login <server>`: there is no `e3 login`.
423
+ - Sign in with `e3 auth login <server>`: there is no `e3 login`. A run is
424
+ `e3 dataflow run <repo> <ws>`: there is no `e3 start`.
425
+ - A release or a sync runs nothing: run the dataflow after either.
426
+ - A release is refused while its Files declarations read a source the target
427
+ hasn't bound: bind it in the target first.
122
428
  - `secrets set` reads the value from stdin unless `--value` is given; the
123
429
  next run reads the new value.
124
430
  - The version is the @elaraai lockstep: use the CLI of the release your
@@ -0,0 +1,191 @@
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), 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)."
4
+ ---
5
+
6
+ # e3-cloud-files — files as e3 inputs
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.
14
+
15
+ Read `README.md` for the author's guide; this file is the short form.
16
+
17
+ ## Before writing code — search the example index
18
+
19
+ Every e3 cloud API has an example in the plugin's index: the decoding
20
+ functions of `@elaraai/e3-cloud-files`, and the hand-written entries for
21
+ `Files.source`, its shapes, `Files.manifest` and the `e3-cloud files`
22
+ commands. Before writing or changing Files code:
23
+
24
+ 1. Call `mcp__plugin_e3-cloud_e3-cloud__search_e3_cloud_examples` for the
25
+ part you are about to write. Summaries come back first: id, description
26
+ and keywords, a few hundred bytes each.
27
+ 2. Fetch the one or two that match with
28
+ `mcp__plugin_e3-cloud_e3-cloud__get_e3_cloud_example` and pattern your code
29
+ on them.
30
+ 3. For what a task does with the decoded data, search East's index too, with
31
+ the East plugin's `search_east_examples`.
32
+ 4. Do not read `node_modules/@elaraai/**` or `*.examples.ts` files
33
+ wholesale, and do not reason from `.d.ts` signatures: the index holds the
34
+ same code, exact and far cheaper.
35
+
36
+ Nothing is injected for you; the search is the step.
37
+
38
+ ## Quick Start
39
+
40
+ ```ts
41
+ // src/files.ts: which inputs the files fill
42
+ import { Files } from '@elaraai/e3-cloud-files';
43
+ export const upload = Files.source('upload');
44
+ export const rates = upload.file('rates', { key: 'rates.json' });
45
+ export default Files.manifest([upload]);
46
+
47
+ // src/package.ts: a task decodes the file
48
+ const decodeRates = East.function([BlobType], ArrayType(Rate), (_$, file) => file.decodeUtf8().parseJson(ArrayType(Rate)));
49
+ export const rateTable = e3.task('rate_table', [rates], decodeRates, { runner });
50
+ export default e3.package('orders', '1.0.0', rateTable);
51
+ ```
52
+
53
+ ```bash
54
+ e3 workspace deploy ./repo dev --from-source src/package.ts
55
+ e3-cloud files deploy ./repo dev src/files.ts # checked against the package dev runs
56
+ e3-cloud files bind ./repo dev upload s3://bucket/prefix/ --profile p # where upload's files are, for dev
57
+ e3-cloud files sync ./repo dev # what changed comes in
58
+ e3 dataflow run ./repo dev # the dataflow runs on it
59
+ ```
60
+
61
+ ## Decision Tree
62
+
63
+ ```
64
+ What do you need?
65
+ ├─ Declare which inputs a bucket's files fill (src/files.ts)
66
+ │ ├─ A place files come from → Files.source(name) — each workspace binds it to s3://bucket/prefix/
67
+ │ ├─ One known file → source.file(input, { key }) input: Blob
68
+ │ ├─ The newest of a folder → source.latest(input, { prefix, include? }) input: Blob, the greatest key
69
+ │ ├─ Every file of a folder → source.folder(input, { prefix, include? }) input: Dict<String, Blob> by path
70
+ │ ├─ A folder's files, not bytes → source.listing(input, { prefix, include? }) input: Dict<String, {size, modified, etag}>
71
+ │ ├─ Subfolders' files too → include: '**/*.csv' ('*.csv' is the top level only)
72
+ │ └─ What deploy deploys → export default Files.manifest([source, …])
73
+ ├─ Decode what arrives, in a task
74
+ │ ├─ East's JSON → file.decodeUtf8().parseJson(T)
75
+ │ ├─ CSV with a header → file.decodeCsv(Row, { delimiter: ',' })
76
+ │ ├─ Lines of text → file.decodeUtf8().split('\n')
77
+ │ ├─ A folder's files as one array → files.flattenToArray(($, file, path) => …)
78
+ │ ├─ Some of a folder's files → files.filter(($, file, path) => path.startsWith('2026/'))
79
+ │ └─ Choose files by name or time → a listing: rows.filter(($, row) => row.modified.greaterThan(since))
80
+ ├─ Bring the files into a workspace (the e3-cloud CLI; <repo> a local path or a repository URL)
81
+ │ ├─ Deploy the declarations → e3-cloud files deploy <repo> <ws> src/files.ts [--dry-run]
82
+ │ ├─ Say where a source's files are → e3-cloud files bind <repo> <ws> <source> s3://bucket/prefix/ [--region r]
83
+ │ │ └─ 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>
86
+ │ └─ Run the dataflow on it → e3 dataflow run <repo> <ws> (a sync doesn't run it)
87
+ ├─ Production: a workspace with a staging (a deployed repository's URL)
88
+ │ ├─ Change its declarations → files deploy to the staging, then e3-cloud release <url> <staging> <production>
89
+ │ ├─ A source it doesn't bind yet → e3-cloud files bind <url> <production> <source> s3://… before the release
90
+ │ ├─ See what a release does → e3-cloud release … --plan (each input added, changed, unchanged, removed)
91
+ │ ├─ After a release → e3-cloud files sync <url> <production>, then e3 dataflow run
92
+ │ ├─ Undo a release → e3-cloud rollback <url> <production> (declarations go back with the package)
93
+ │ ├─ An emergency change → e3-cloud files deploy … --force (an admin's; history records it; the next release undoes it)
94
+ │ └─ Rehearse on production's files → e3-cloud staging add … (a copy: declarations, bindings, what each input took)
95
+ ├─ Keep synced inputs across a package deploy → e3 workspace deploy … --inputs keep-edited
96
+ └─ 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)
99
+ ├─ Writing an e3 store → @elaraai/e3-cloud-files/storage: storageWriter(storage, repo, ws)
100
+ └─ Reading a bucket → @elaraai/e3-cloud-files/s3: s3FilesReader(client, { bucket, prefix })
101
+ ```
102
+
103
+ ## Declare the files
104
+
105
+ ```ts
106
+ // src/files.ts
107
+ import { Files } from '@elaraai/e3-cloud-files';
108
+
109
+ export const upload = Files.source('upload'); // each workspace binds it to a bucket and prefix
110
+
111
+ export const rates = upload.file('rates', { key: 'rates.json' }); // input: Blob
112
+ export const stock = upload.latest('stock', { prefix: 'stock/', include: '*.json' }); // input: Blob, the greatest key
113
+ export const orders = upload.folder('orders', { prefix: 'orders/', include: '*.json' }); // input: Dict<String, Blob>
114
+ export const orderFiles = upload.listing('order_files', { prefix: 'orders/' }); // input: each file's size, modified, etag
115
+
116
+ export default Files.manifest([upload]); // what e3-cloud files deploy deploys
117
+ ```
118
+
119
+ - 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.
121
+ - Keys and prefixes are relative to where a workspace binds the source; a
122
+ prefix is `''` or ends with `/`.
123
+ - `include` is a glob over the path under the prefix: `*` within one folder,
124
+ `**` across folders, `?` one character, `[a-z]` one of a set (`[!_]` any
125
+ but), `{csv,json}` any of its alternatives (`*.{csv,json}`).
126
+ - `latest` takes the greatest key: name files so the newest sorts last
127
+ (`2026-10-02.json`).
128
+
129
+ ## Decode in a task
130
+
131
+ ```ts
132
+ const decodeRates = East.function([BlobType], ArrayType(Rate), (_$, file) =>
133
+ file.decodeUtf8().parseJson(ArrayType(Rate)));
134
+ const decodeOrders = East.function([FilesFolderType], ArrayType(Order), (_$, files) =>
135
+ files.flattenToArray((_$, file) => file.decodeUtf8().parseJson(ArrayType(Order))));
136
+
137
+ export const rateTable = e3.task('rate_table', [rates], decodeRates, { runner });
138
+ ```
139
+
140
+ CSV: `file.decodeCsv(Row, { delimiter: ',' })`. A folder is keyed by each
141
+ file's path under the prefix, in path order.
142
+
143
+ ## Deploy, bind and sync
144
+
145
+ ```
146
+ e3-cloud files deploy <repo> <workspace> src/files.ts [--dry-run]
147
+ 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]
149
+ e3-cloud files status <repo> <workspace>
150
+ e3 dataflow run <repo> <workspace>
151
+ ```
152
+
153
+ `<repo>` is a local repository's path or a repository URL. Deploy the package
154
+ first (`e3 workspace deploy`), then the declarations, which are checked
155
+ against it. A sync reads only files whose ETag or size changed, writes only
156
+ inputs whose files changed, and prints the next command when any did. It
157
+ doesn't run the dataflow: `e3 dataflow run` does. Locally the bytes land only in the
158
+ repository; `--profile` names the profile to read with and `--latest n`
159
+ keeps only each folder's n greatest keys.
160
+
161
+ ## Traps
162
+
163
+ - 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.
167
+ - A file over 1 GiB less 64 KiB (`file`, `latest`), or over 64 MiB less
168
+ 64 KiB in a folder, is refused and named in the report, never truncated.
169
+ So is a file the sync can't read (archived, under a key it may not use,
170
+ changed while it read it): the sync goes on, and the next tries it again.
171
+ - Deploying a new package can make the declarations no longer fit: a sync then
172
+ refuses until they're deployed again. A package deploy also resets synced
173
+ inputs unless it keeps them (`--inputs keep-edited`); the next sync takes
174
+ them again whole.
175
+ - A changed declaration, or a source bound elsewhere, has its inputs taken
176
+ again whole at the next sync.
177
+ - A workspace with a staging refuses `files deploy` (`workspace_protected`):
178
+ deploy to the staging and release. A release is refused while the
179
+ declarations read a source the production workspace hasn't bound: bind it
180
+ there first. A release syncs nothing: sync production after it.
181
+ - Bindings never travel with a release; a copy carries them. A staging
182
+ started as a copy of production reads production's files until you bind it
183
+ elsewhere.
184
+ - The version is the @elaraai lockstep: use the same version as east and e3.
185
+
186
+ ## Related skills
187
+
188
+ - **e3** — packages, tasks, inputs, the dataflow and the `e3` CLI.
189
+ - **east** — the East language a task's decoding is written in.
190
+ - **e3-cloud-cli** — the rest of the `e3-cloud` CLI: apps, staging, releases,
191
+ rollbacks, users, secrets and schedules, and the app lifecycle guides.