@colony2/c2j 0.0.50 → 0.0.52

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/README.md CHANGED
@@ -14,6 +14,28 @@ Use it when you want to:
14
14
 
15
15
  Examples below assume you are running from the repo root.
16
16
 
17
+ For execution requirements, actual allocation inputs, compatibility filtering,
18
+ job status, progress, handoffs, and child-job lineage, see the
19
+ [execution tracking user guide](GUIDE-Execution-Tracking.md).
20
+
21
+ Recipes can declare an `execution` block for CPU, memory, scratch, platform,
22
+ and image. Node-level `execution_needs` adds templatable, scoped overrides that
23
+ inherit within a job and restore the parent on exit. Completed tasks replay
24
+ without requesting their historical environments. Pass actual executor facts using individual `--execution-*` flags
25
+ or `C2J_EXECUTION_*` variables. If requirements change or the environment is
26
+ insufficient, execution yields the same job and reports `environment_required`.
27
+ A provisioner can then resume it in a compatible environment; c2j does not
28
+ provision resources itself. Listings show submission/latest-yield requirements
29
+ only for waiting jobs; in-flight needs are not presented as current snapshots.
30
+
31
+ ## JobDB upgrade compatibility
32
+
33
+ The typed-route JobDB update requires fresh format-3 databases/artifact storage
34
+ and matching server/worker versions; existing jobs are not migrated. Job and
35
+ task types now travel separately, preserving existing task names. Read
36
+ [the upgrade notes](JOBDB_UPGRADE_NOTES.md) before deploying it. No existing data
37
+ is automatically reset.
38
+
17
39
  ## Command Summary
18
40
 
19
41
  ```bash
@@ -142,15 +164,34 @@ This is mainly useful when you want to target another cell by short name and nee
142
164
 
143
165
  ### `c2j init`
144
166
 
145
- Writes a commented `.c2j/config.yaml` template.
167
+ Writes a commented `.c2j/config.yaml` template and installs bundled c2j skills for Codex-compatible agents.
146
168
 
147
169
  ```bash
148
170
  c2j init
149
171
  c2j init --stdout
150
172
  c2j init --force
173
+ c2j init --no-skills
174
+ c2j init --with-op-skills
175
+ ```
176
+
177
+ The generated template can derive values from the Go module in the current repo when `base: go` is appropriate. `--stdout` prints only the config and does not install skills.
178
+
179
+ Bundled skill install controls:
180
+
181
+ ```bash
182
+ c2j init --skills-scope auto
183
+ c2j init --skills-scope project
184
+ c2j init --skills-scope user
185
+ c2j init --skills-force
151
186
  ```
152
187
 
153
- The generated template can derive values from the Go module in the current repo when `base: go` is appropriate.
188
+ `auto` currently installs to the project-local `.agents/skills` directory. `user` installs to `${CODEX_HOME:-$HOME/.codex}/skills`. Install metadata is recorded in `.c2j/skills-lock.yaml`.
189
+
190
+ You can also install the repo skills directly with the public `skills` tool:
191
+
192
+ ```bash
193
+ npx skills add colony-2/c2j
194
+ ```
154
195
 
155
196
  ## Submitting Jobs
156
197
 
@@ -489,6 +530,20 @@ Useful filters:
489
530
  - `--page-token`
490
531
  - `--all`
491
532
 
533
+ Filter by a pending task using a JSON route:
534
+
535
+ ```bash
536
+ c2j list --self --waiting-for '{"jobType":"recipe","taskType":"input:collect_user_input"}' --embed
537
+ ```
538
+
539
+ Repeat `--waiting-for` for multiple routes and `--job-type` for multiple job
540
+ types. These flags accept literal identifiers, not comma-separated lists;
541
+ `--waiting-for JOBTYPE:TASKTYPE` is no longer supported because either type may
542
+ contain colons. Identifiers are case-sensitive and are not trimmed or encoded.
543
+ List JSON exposes `next_route` (`jobType`, optional `taskType`) and `task_wait`
544
+ instead of the old `next_need` and flat `task_wait_*` fields. The human-readable
545
+ `NEXT` column also shows the route as JSON.
546
+
492
547
  ## Embedded Runtime
493
548
 
494
549
  `--embed` is shorthand for:
package/bin/cli.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const { spawnSync } = require("child_process");
8
+
9
+ const packageRoot = path.resolve(__dirname, "..");
10
+ const packageJson = require(path.join(packageRoot, "package.json"));
11
+ const packageName = packageJson.name.split("/").pop();
12
+ const binaryPath = path.join(packageRoot, "vendor", packageName);
13
+
14
+ if (!fs.existsSync(binaryPath)) {
15
+ console.error(
16
+ `${packageName} binary is missing. Reinstall the package or run npm rebuild ${packageJson.name}.`
17
+ );
18
+ process.exit(1);
19
+ }
20
+
21
+ const result = spawnSync(binaryPath, process.argv.slice(2), {
22
+ stdio: "inherit",
23
+ });
24
+
25
+ if (result.error) {
26
+ console.error(result.error.message);
27
+ process.exit(1);
28
+ }
29
+
30
+ process.exit(result.status === null ? 1 : result.status);
package/package.json CHANGED
@@ -1,85 +1,19 @@
1
1
  {
2
2
  "name": "@colony2/c2j",
3
- "type": "module",
4
- "version": "0.0.50",
3
+ "version": "0.0.52",
5
4
  "description": "c2j job system",
6
- "scripts": {
7
- "postinstall": "node install.js",
8
- "run": "node run-c2j.js"
9
- },
5
+ "license": "Apache-2.0",
6
+ "homepage": "https://github.com/colony-2/c2j",
10
7
  "repository": {
11
8
  "type": "git",
12
9
  "url": "git+https://github.com/colony-2/c2j.git"
13
10
  },
14
- "author": {
15
- "email": "noreply@colony2.com",
16
- "name": "Colony 2"
17
- },
18
- "license": "Apache-2.0",
19
11
  "bugs": {
20
12
  "url": "https://github.com/colony-2/c2j/issues"
21
13
  },
22
- "homepage": "https://github.com/colony-2/c2j",
23
- "bin": {
24
- "c2j": "run-c2j.js"
25
- },
26
- "dependencies": {
27
- "jszip": "3.10.1",
28
- "proxy-agent": "8.0.1",
29
- "tar": "7.5.15"
30
- },
31
- "archives": {
32
- "darwin-arm64": {
33
- "name": "c2j_0.0.50_Darwin_arm64.tar.gz",
34
- "url": "https://github.com/colony-2/c2j/releases/download/v0.0.50/c2j_0.0.50_Darwin_arm64.tar.gz",
35
- "bins": [
36
- "c2j"
37
- ],
38
- "format": "tar.gz",
39
- "checksum": {
40
- "algorithm": "sha256",
41
- "digest": "b91fdeb7d21a36f2600eaf3ffc1661ce014ab442aecceae26fd4254d402656a1"
42
- }
43
- },
44
- "darwin-x64": {
45
- "name": "c2j_0.0.50_Darwin_x86_64.tar.gz",
46
- "url": "https://github.com/colony-2/c2j/releases/download/v0.0.50/c2j_0.0.50_Darwin_x86_64.tar.gz",
47
- "bins": [
48
- "c2j"
49
- ],
50
- "format": "tar.gz",
51
- "checksum": {
52
- "algorithm": "sha256",
53
- "digest": "ae12ffdda3d24b6b14f55dbd329b488f69c02907bde62589ca0c234deb7080ce"
54
- }
55
- },
56
- "linux-arm64": {
57
- "name": "c2j_0.0.50_Linux_arm64.tar.gz",
58
- "url": "https://github.com/colony-2/c2j/releases/download/v0.0.50/c2j_0.0.50_Linux_arm64.tar.gz",
59
- "bins": [
60
- "c2j"
61
- ],
62
- "format": "tar.gz",
63
- "checksum": {
64
- "algorithm": "sha256",
65
- "digest": "e198f9037bd138270aa92ac8b5caa4509b9f76056273d4191909e810d406ba72"
66
- }
67
- },
68
- "linux-x64": {
69
- "name": "c2j_0.0.50_Linux_x86_64.tar.gz",
70
- "url": "https://github.com/colony-2/c2j/releases/download/v0.0.50/c2j_0.0.50_Linux_x86_64.tar.gz",
71
- "bins": [
72
- "c2j"
73
- ],
74
- "format": "tar.gz",
75
- "checksum": {
76
- "algorithm": "sha256",
77
- "digest": "c8b720b153a9b7354887bfb9dd3f7d26ff615cd29a74dd8385fecda8f79d6e0f"
78
- }
79
- }
80
- },
81
- "engines": {
82
- "node": "\u003e=14.0.0"
14
+ "author": {
15
+ "name": "Colony 2",
16
+ "email": "noreply@colony2.com"
83
17
  },
84
18
  "keywords": [
85
19
  "sandbox",
@@ -88,5 +22,29 @@
88
22
  "agent",
89
23
  "cli",
90
24
  "security"
25
+ ],
26
+ "bin": {
27
+ "c2j": "bin/cli.js"
28
+ },
29
+ "scripts": {
30
+ "postinstall": "node scripts/postinstall.js"
31
+ },
32
+ "os": [
33
+ "darwin",
34
+ "linux"
35
+ ],
36
+ "cpu": [
37
+ "x64",
38
+ "arm64"
39
+ ],
40
+ "engines": {
41
+ "node": ">=14.0.0"
42
+ },
43
+ "files": [
44
+ "bin/",
45
+ "scripts/",
46
+ "vendor/",
47
+ "README.md",
48
+ "LICENSE"
91
49
  ]
92
- }
50
+ }
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const crypto = require("crypto");
6
+ const fs = require("fs");
7
+ const os = require("os");
8
+ const path = require("path");
9
+ const { execFileSync } = require("child_process");
10
+ const { get } = require("https");
11
+
12
+ const packageRoot = path.resolve(__dirname, "..");
13
+ const packageJson = require(path.join(packageRoot, "package.json"));
14
+ const packageName = packageJson.name.split("/").pop();
15
+ const version = packageJson.version;
16
+
17
+ const platforms = {
18
+ "linux:x64": ["Linux", "x86_64"],
19
+ "linux:arm64": ["Linux", "arm64"],
20
+ "darwin:x64": ["Darwin", "x86_64"],
21
+ "darwin:arm64": ["Darwin", "arm64"],
22
+ };
23
+
24
+ const platformKey = `${process.platform}:${process.arch}`;
25
+ const target = platforms[platformKey];
26
+
27
+ if (!target) {
28
+ throw new Error(`Unsupported platform for ${packageJson.name}: ${platformKey}`);
29
+ }
30
+
31
+ const repo = repositoryPath(packageJson);
32
+ const tag = `v${version}`;
33
+ const assetName = `${packageName}_${version}_${target[0]}_${target[1]}.tar.gz`;
34
+ const baseUrl = `https://github.com/${repo}/releases/download/${tag}`;
35
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `${packageName}-`));
36
+ const archivePath = path.join(tempDir, assetName);
37
+ const checksumsPath = path.join(tempDir, "checksums.txt");
38
+ const extractDir = path.join(tempDir, "extract");
39
+ const vendorDir = path.join(packageRoot, "vendor");
40
+ const installedBinary = path.join(vendorDir, packageName);
41
+
42
+ fs.mkdirSync(extractDir);
43
+ fs.mkdirSync(vendorDir, { recursive: true });
44
+
45
+ downloadWithRetry(`${baseUrl}/${assetName}`, archivePath)
46
+ .then(() => downloadWithRetry(`${baseUrl}/checksums.txt`, checksumsPath))
47
+ .then(() => {
48
+ verifyChecksum(archivePath, checksumsPath, assetName);
49
+ execFileSync("tar", ["-xzf", archivePath, "-C", extractDir], {
50
+ stdio: "inherit",
51
+ });
52
+
53
+ const extractedBinary = path.join(extractDir, packageName);
54
+ if (!fs.existsSync(extractedBinary)) {
55
+ throw new Error(`Release archive did not contain ${packageName}`);
56
+ }
57
+
58
+ fs.copyFileSync(extractedBinary, installedBinary);
59
+ fs.chmodSync(installedBinary, 0o755);
60
+ })
61
+ .catch((error) => {
62
+ console.error(error.message || error);
63
+ process.exitCode = 1;
64
+ })
65
+ .finally(() => {
66
+ fs.rmSync(tempDir, { recursive: true, force: true });
67
+ });
68
+
69
+ function repositoryPath(pkg) {
70
+ const raw =
71
+ typeof pkg.repository === "string" ? pkg.repository : pkg.repository && pkg.repository.url;
72
+
73
+ if (!raw) {
74
+ throw new Error("package.json repository is required to locate release assets");
75
+ }
76
+
77
+ const match = raw.match(/github\.com[:/]([^/]+\/[^/.]+)(?:\.git)?/);
78
+ if (!match) {
79
+ throw new Error(`Unsupported repository URL for release assets: ${raw}`);
80
+ }
81
+
82
+ return match[1];
83
+ }
84
+
85
+ async function downloadWithRetry(url, destination) {
86
+ let lastError;
87
+
88
+ for (let attempt = 1; attempt <= 5; attempt += 1) {
89
+ try {
90
+ await download(url, destination);
91
+ return;
92
+ } catch (error) {
93
+ lastError = error;
94
+ if (attempt === 5) {
95
+ break;
96
+ }
97
+ await delay(attempt * 1000);
98
+ }
99
+ }
100
+
101
+ throw lastError;
102
+ }
103
+
104
+ function download(url, destination) {
105
+ return new Promise((resolve, reject) => {
106
+ const request = get(url, (response) => {
107
+ if (
108
+ response.statusCode >= 300 &&
109
+ response.statusCode < 400 &&
110
+ response.headers.location
111
+ ) {
112
+ response.resume();
113
+ download(response.headers.location, destination).then(resolve, reject);
114
+ return;
115
+ }
116
+
117
+ if (response.statusCode !== 200) {
118
+ response.resume();
119
+ reject(new Error(`Failed to download ${url}: HTTP ${response.statusCode}`));
120
+ return;
121
+ }
122
+
123
+ const file = fs.createWriteStream(destination, { mode: 0o600 });
124
+ response.pipe(file);
125
+ file.on("finish", () => file.close(resolve));
126
+ file.on("error", reject);
127
+ });
128
+
129
+ request.on("error", reject);
130
+ });
131
+ }
132
+
133
+ function delay(ms) {
134
+ return new Promise((resolve) => setTimeout(resolve, ms));
135
+ }
136
+
137
+ function verifyChecksum(archive, checksums, assetName) {
138
+ const expected = fs
139
+ .readFileSync(checksums, "utf8")
140
+ .split(/\r?\n/)
141
+ .map((line) => line.trim().split(/\s+/))
142
+ .find((parts) => parts[1] === assetName);
143
+
144
+ if (!expected) {
145
+ throw new Error(`No checksum found for ${assetName}`);
146
+ }
147
+
148
+ const actual = crypto.createHash("sha256").update(fs.readFileSync(archive)).digest("hex");
149
+ if (actual !== expected[0]) {
150
+ throw new Error(`Checksum mismatch for ${assetName}`);
151
+ }
152
+ }
package/install.js DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // This file was generated by GoReleaser. DO NOT EDIT.
4
-
5
- import { install } from "./lib.js";
6
- install();
package/lib.js DELETED
@@ -1,259 +0,0 @@
1
- // This file was generated by GoReleaser. DO NOT EDIT.
2
- import fs from "fs";
3
- import crypto from "crypto";
4
- import http from "http";
5
- import https from "https";
6
- import path from "path";
7
- import JSZip from "jszip";
8
- import { x as tarExtract } from "tar";
9
- import { ProxyAgent } from "proxy-agent";
10
- import { spawnSync } from "child_process";
11
- import { fileURLToPath } from "url";
12
-
13
- const { archives, name, version } = JSON.parse(
14
- fs.readFileSync(new URL("./package.json", import.meta.url), "utf8"),
15
- );
16
-
17
- const agent = new ProxyAgent();
18
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
-
20
- const getArchive = () => {
21
- let target = `${process.platform}-${process.arch}`;
22
- const archive = archives[target];
23
- if (!archive) {
24
- throw new Error(`No archive available for ${target}`);
25
- }
26
- return archive;
27
- };
28
-
29
- const binDir = path.join(__dirname, "bin");
30
-
31
- async function extractTar(tarPath, binaries, dir, wrappedIn) {
32
- try {
33
- const filesToExtract = wrappedIn
34
- ? binaries.map((bin) =>
35
- path.join(wrappedIn, bin).replace(/\\/g, "/"),
36
- )
37
- : binaries;
38
-
39
- await tarExtract({
40
- file: tarPath,
41
- cwd: dir,
42
- filter: (path) => filesToExtract.includes(path),
43
- });
44
-
45
- // If wrapped, move files from wrapped directory to bin directory
46
- if (wrappedIn) {
47
- const wrappedDir = path.join(dir, wrappedIn);
48
- for (const binary of binaries) {
49
- const srcPath = path.join(wrappedDir, binary);
50
- const destPath = path.join(dir, binary);
51
- if (fs.existsSync(srcPath)) {
52
- fs.renameSync(srcPath, destPath);
53
- }
54
- }
55
- // Clean up empty wrapped directory
56
- try {
57
- fs.rmSync(wrappedDir, { recursive: true, force: true });
58
- } catch (err) {
59
- // Ignore cleanup errors
60
- }
61
- }
62
-
63
- console.log(`Successfully extracted ${binaries} to "${dir}"`);
64
- } catch (err) {
65
- throw new Error(`Extraction failed: ${err.message}`);
66
- }
67
- }
68
-
69
- async function extractZip(zipPath, binaries, dir, wrappedIn) {
70
- try {
71
- const zipData = fs.readFileSync(zipPath);
72
- const zip = await JSZip.loadAsync(zipData);
73
-
74
- for (const binary of binaries) {
75
- const binaryPath = wrappedIn
76
- ? path.join(wrappedIn, binary).replace(/\\/g, "/")
77
- : binary;
78
-
79
- if (!zip.files[binaryPath]) {
80
- throw new Error(
81
- `Error: ${binaryPath} does not exist in ${zipPath}`,
82
- );
83
- }
84
-
85
- const content = await zip.files[binaryPath].async("nodebuffer");
86
- if (!fs.existsSync(dir)) {
87
- fs.mkdirSync(dir, { recursive: true });
88
- }
89
- const file = path.join(dir, binary);
90
- fs.writeFileSync(file, content);
91
- fs.chmodSync(file, "755");
92
- console.log(`Successfully extracted "${binary}" to "${dir}"`);
93
- }
94
- } catch (err) {
95
- throw new Error(`Extraction failed: ${err.message}`);
96
- }
97
- }
98
-
99
- const run = async (bin) => {
100
- await install();
101
- if (process.platform === "win32") {
102
- bin += ".exe";
103
- }
104
- const [, , ...args] = process.argv;
105
- let result = spawnSync(path.join(binDir, bin), args, {
106
- cwd: process.cwd(),
107
- stdio: "inherit",
108
- });
109
- if (result.error) {
110
- console.error(result.error);
111
- }
112
- return result.status;
113
- };
114
-
115
- const install = async () => {
116
- try {
117
- let archive = getArchive();
118
- if (await exists(archive)) {
119
- return;
120
- }
121
- let tmp = fs.mkdtempSync("archive-");
122
- let archivePath = path.join(tmp, archive.name);
123
- await download(archive.url, archivePath);
124
- verify(archivePath, archive.checksum);
125
-
126
- if (!fs.existsSync(binDir)) {
127
- fs.mkdirSync(binDir);
128
- }
129
- switch (archive.format) {
130
- case "binary":
131
- const bin = path.join(binDir, archive.bins[0]);
132
- fs.copyFileSync(archivePath, bin);
133
- fs.chmodSync(bin, 0o755);
134
- break;
135
- case "zip":
136
- await extractZip(
137
- archivePath,
138
- archive.bins,
139
- binDir,
140
- archive.wrappedIn,
141
- );
142
- break;
143
- case "tar":
144
- case "tar.gz":
145
- case "tgz":
146
- await extractTar(
147
- archivePath,
148
- archive.bins,
149
- binDir,
150
- archive.wrappedIn,
151
- );
152
- break;
153
- case "tar.zst":
154
- case "tzst":
155
- case "tar.xz":
156
- case "txz":
157
- default:
158
- throw new Error(`unsupported format: ${archive.format}`);
159
- }
160
- console.log(`Installed ${name} ${version} to ${binDir}`);
161
- } catch (err) {
162
- throw new Error(`Installation failed: ${err.message}`);
163
- }
164
- };
165
-
166
- const verify = (filename, checksum) => {
167
- if (checksum.algorithm == "" || checksum.digest == "") {
168
- console.warn("Warning: No checksum provided for verification");
169
- return;
170
- }
171
- let digest = crypto
172
- .createHash(checksum.algorithm)
173
- .update(fs.readFileSync(filename))
174
- .digest("hex");
175
- if (digest != checksum.digest) {
176
- throw new Error(
177
- `${filename}: ${checksum.algorithm} does not match, expected ${checksum.digest}, got ${digest}`,
178
- );
179
- }
180
- };
181
-
182
- const download = async (url, filename, maxRedirects = 10) => {
183
- try {
184
- console.log(`Downloading ${url} to ${filename}...`);
185
- const dir = path.dirname(filename);
186
- if (!fs.existsSync(dir)) {
187
- fs.mkdirSync(dir, { recursive: true });
188
- }
189
-
190
- return new Promise((resolve, reject) => {
191
- const parsedUrl = new URL(url);
192
- const mod = parsedUrl.protocol === "https:" ? https : http;
193
-
194
- const request = mod.get(url, { agent }, (response) => {
195
- if (
196
- response.statusCode >= 300 &&
197
- response.statusCode < 400 &&
198
- response.headers.location
199
- ) {
200
- if (maxRedirects <= 0) {
201
- reject(new Error("Too many redirects"));
202
- return;
203
- }
204
- download(response.headers.location, filename, maxRedirects - 1)
205
- .then(resolve)
206
- .catch(reject);
207
- return;
208
- }
209
-
210
- if (response.statusCode !== 200) {
211
- reject(
212
- new Error(
213
- `HTTP ${response.statusCode}: ${response.statusMessage}`,
214
- ),
215
- );
216
- return;
217
- }
218
-
219
- const writer = fs.createWriteStream(filename);
220
- response.pipe(writer);
221
-
222
- writer.on("finish", () => {
223
- console.log(`Download complete: ${filename}`);
224
- resolve(dir);
225
- });
226
-
227
- writer.on("error", (err) => {
228
- console.error(`Error writing file: ${err.message}`);
229
- reject(err);
230
- });
231
- });
232
-
233
- request.on("error", (err) => {
234
- reject(new Error(`Request failed: ${err.message}`));
235
- });
236
-
237
- request.setTimeout(300000, () => {
238
- request.destroy();
239
- reject(new Error("Request timed out"));
240
- });
241
- });
242
- } catch (err) {
243
- throw new Error(`Download failed: ${err.message}`);
244
- }
245
- };
246
-
247
- function exists(archive) {
248
- if (!fs.existsSync(binDir)) {
249
- return false;
250
- }
251
- return archive.bins.every((bin) => fs.existsSync(path.join(binDir, bin)));
252
- }
253
-
254
- export {
255
- install,
256
- run,
257
- getArchive,
258
- download,
259
- };
package/run-c2j.js DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // This file was generated by GoReleaser. DO NOT EDIT.
4
-
5
- import { run } from "./lib.js";
6
- run("c2j").then((code) => process.exit(code));