@elaraai/e3-cloud-codex-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.
- package/.build/hooks/codex-tools.js +13 -13
- package/.build/mcp/server.js +10 -10
- package/.codex-plugin/plugin.json +3 -3
- package/README.md +4 -4
- package/hooks/hooks.json +1 -1
- package/index.json +305 -3
- package/package.json +2 -2
- package/skills/e3-cloud-cli/SKILL.md +321 -15
- package/skills/e3-cloud-files/SKILL.md +195 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.
|
|
1
|
+
// ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.
|
|
8
|
+
// ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.
|
|
46
|
+
// ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.
|
|
95
|
+
// ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.
|
|
108
|
+
// ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.
|
|
150
|
+
// ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.
|
|
202
|
+
// ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.
|
|
210
|
+
// ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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.
|
|
238
|
+
// ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.93_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"
|
package/.build/mcp/server.js
CHANGED
|
@@ -21491,7 +21491,7 @@ var StdioServerTransport = class {
|
|
|
21491
21491
|
}
|
|
21492
21492
|
};
|
|
21493
21493
|
|
|
21494
|
-
// ../../node_modules/.pnpm/@elaraai+east-plugin@1.0.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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-codex-plugin",
|
|
3
|
-
"version": "1.0.
|
|
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",
|
|
@@ -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-
|
|
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":
|
|
5
|
-
"totalFiles":
|
|
4
|
+
"totalEntries": 38,
|
|
5
|
+
"totalFiles": 2,
|
|
6
6
|
"packages": {
|
|
7
7
|
"e3-cloud-ui": 12,
|
|
8
|
-
"e3-cloud-
|
|
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.
|
|
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,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
|
|
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
|
|
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
|
|
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
|
|
85
|
-
a rollback. One staging to a workspace; no staging has
|
|
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.
|
|
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
|
|
102
|
-
target's staging releases to it. The target keeps the
|
|
103
|
-
|
|
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
|
|
116
|
-
releases an entry again, its package
|
|
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.
|