@nextclaw/x-harness 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +231 -6
- package/README.md +15 -1
- package/SOURCE.md +9 -0
- package/cli/main.js +232 -200
- package/dist/api.d.ts +56 -2
- package/dist/api.js +130 -79
- package/dist/boundary.js +1 -0
- package/dist/browser-common.d.ts +11 -0
- package/dist/browser-common.js +34 -18
- package/dist/browser-dom-capabilities.d.ts +7 -2
- package/dist/browser-dom-capabilities.js +123 -102
- package/dist/browser-dom-dependencies.js +1 -0
- package/dist/browser-dom-editing.d.ts +20 -2
- package/dist/browser-dom-editing.js +148 -116
- package/dist/browser-dom-navigation.d.ts +11 -2
- package/dist/browser-dom-navigation.js +66 -50
- package/dist/browser-dom-submission.d.ts +6 -1
- package/dist/browser-dom-submission.js +37 -18
- package/dist/browser-dom.d.ts +13 -1
- package/dist/browser-dom.js +113 -66
- package/dist/browser-native.d.ts +14 -1
- package/dist/browser-native.js +161 -128
- package/dist/creator-native.d.ts +3 -0
- package/dist/creator-native.js +113 -0
- package/dist/diagnostics.d.ts +11 -0
- package/dist/diagnostics.js +10 -0
- package/dist/growth-batch.d.ts +6 -3
- package/dist/growth-batch.js +157 -135
- package/dist/growth-dependencies.js +1 -0
- package/dist/growth-execution.d.ts +8 -2
- package/dist/growth-execution.js +68 -37
- package/dist/growth-parallel.d.ts +6 -2
- package/dist/growth-parallel.js +78 -47
- package/dist/growth-plan.d.ts +8 -3
- package/dist/growth-plan.js +45 -23
- package/dist/growth-policy.d.ts +11 -2
- package/dist/growth-policy.js +80 -53
- package/dist/growth-publishing.d.ts +7 -2
- package/dist/growth-publishing.js +72 -46
- package/dist/growth-reading.d.ts +6 -2
- package/dist/growth-reading.js +47 -25
- package/dist/growth-recovery.d.ts +7 -4
- package/dist/growth-recovery.js +177 -148
- package/dist/growth-state.d.ts +10 -2
- package/dist/growth-state.js +67 -30
- package/dist/growth-tasks.d.ts +19 -16
- package/dist/growth-tasks.js +153 -90
- package/dist/growth.d.ts +25 -1
- package/dist/growth.js +207 -124
- package/dist/platform-capabilities.d.ts +6 -2
- package/dist/platform-capabilities.js +18 -8
- package/dist/platform-collections.d.ts +10 -3
- package/dist/platform-collections.js +62 -35
- package/dist/platform-dependencies.js +1 -0
- package/dist/platform-diagnostics.d.ts +7 -2
- package/dist/platform-diagnostics.js +31 -19
- package/dist/platform-observation.d.ts +10 -2
- package/dist/platform-observation.js +44 -23
- package/dist/platform-targets.d.ts +10 -3
- package/dist/platform-targets.js +39 -14
- package/dist/platform-writing.d.ts +10 -2
- package/dist/platform-writing.js +63 -32
- package/dist/platform.d.ts +22 -1
- package/dist/platform.js +120 -52
- package/dist/readers-ax-host.d.ts +11 -1
- package/dist/readers-ax-host.js +108 -99
- package/dist/readers-ax-program.js +5 -0
- package/dist/readers-ax-tree.js +1 -0
- package/dist/readers-cli.js +79 -0
- package/dist/readers.js +9 -0
- package/dist/round-state.js +18 -1
- package/dist/store.d.ts +22 -4
- package/dist/store.js +173 -151
- package/dist/types.js +1 -0
- package/generated/observation.mjs +1 -1
- package/package.json +23 -9
- package/skill/SKILL.md +2 -2
- package/skill/references/reads.md +1 -1
- package/skill/references/setup.md +1 -1
package/cli/main.js
CHANGED
|
@@ -4,216 +4,248 @@ import { join, resolve, dirname, relative } from "node:path";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
-
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
-
const release = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
9
|
-
const args = process.argv.slice(2);
|
|
10
|
-
const command = args.shift() ?? "help";
|
|
11
|
-
const flags = new Map();
|
|
12
|
-
for (let i = 0; i < args.length; i += 2) {
|
|
13
|
-
const key = args[i], value = args[i + 1];
|
|
14
|
-
if (!["--target", "--home", "--directory", "--account"].includes(key) ||
|
|
15
|
-
!value ||
|
|
16
|
-
value.startsWith("--") ||
|
|
17
|
-
flags.has(key))
|
|
18
|
-
throw Error(`Invalid argument: ${key}`);
|
|
19
|
-
flags.set(key, value);
|
|
20
|
-
}
|
|
21
|
-
const customHome = flags.get("--home");
|
|
22
|
-
const home = resolve(customHome ?? join(homedir(), ".x-harness"));
|
|
23
7
|
const readJSON = (path) => JSON.parse(readFileSync(path, "utf8"));
|
|
24
8
|
const output = (value) => process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
function stageRuntime(stage, runtime) {
|
|
48
|
-
const stagedRuntime = join(stage, "runtime");
|
|
49
|
-
const suffix = join("versions", release.version, "node_modules", ...release.name.split("/"));
|
|
50
|
-
const installedRoot = join(stagedRuntime, suffix);
|
|
51
|
-
cpSync(packageRoot, installedRoot, { recursive: true });
|
|
52
|
-
if (readJSON(join(installedRoot, "package.json")).version !== release.version)
|
|
53
|
-
throw Error("Installed version mismatch");
|
|
54
|
-
const finalRoot = join(runtime, suffix);
|
|
55
|
-
const stableRoot = join(stagedRuntime, "node_modules", ...release.name.split("/"));
|
|
56
|
-
const stableCLI = join(runtime, "node_modules", ...release.name.split("/"), "cli", "main.js");
|
|
57
|
-
mkdirSync(join(stableRoot, "cli"), { recursive: true });
|
|
58
|
-
writeFileSync(join(stableRoot, "package.json"), JSON.stringify({ type: "module" }));
|
|
59
|
-
const entry = relative(dirname(stableCLI), join(finalRoot, "cli", "main.js"))
|
|
60
|
-
.split("\\")
|
|
61
|
-
.join("/");
|
|
62
|
-
writeFileSync(join(stableRoot, "cli", "main.js"), `#!/usr/bin/env node\nawait import(new URL(${JSON.stringify(entry)}, import.meta.url));\n`);
|
|
63
|
-
return { stagedRuntime, installedRoot, finalRoot, stableCLI };
|
|
64
|
-
}
|
|
65
|
-
function setup() {
|
|
66
|
-
const target = flags.get("--target") ?? "codex";
|
|
67
|
-
const stateFile = join(home, "installation.json");
|
|
68
|
-
if (!existsSync(stateFile) &&
|
|
69
|
-
readdirSync(home).some((name) => name !== ".installation.lock"))
|
|
70
|
-
throw Error(`Unmanaged installation home: ${home}`);
|
|
71
|
-
const previous = existsSync(stateFile)
|
|
72
|
-
? readJSON(stateFile)
|
|
73
|
-
: { managedBy: release.name, targets: [] };
|
|
74
|
-
if (![release.name, "@peiiii/x-harness"].includes(previous.managedBy))
|
|
75
|
-
throw Error("Unmanaged installation state");
|
|
76
|
-
const targets = [...new Set([...previous.targets, target])];
|
|
77
|
-
const runtime = join(home, "runtime");
|
|
78
|
-
guard(runtime, ".x-harness-managed.json");
|
|
79
|
-
for (const t of targets)
|
|
80
|
-
guard(skillPath(t), ".x-harness-managed.json");
|
|
81
|
-
mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
82
|
-
const stage = mkdtempSync(join(home, ".setup-"));
|
|
83
|
-
const backup = join(home, "backups", `${Date.now()}-${randomUUID()}`);
|
|
84
|
-
const changes = [];
|
|
85
|
-
let completed = false;
|
|
86
|
-
try {
|
|
87
|
-
// Persist npx's already fetched runtime once; versioned URLs invalidate the whole module graph.
|
|
88
|
-
const { stagedRuntime, installedRoot, finalRoot, stableCLI } = stageRuntime(stage, runtime);
|
|
89
|
-
const metadata = { managedBy: release.name, version: release.version };
|
|
90
|
-
writeFileSync(join(stagedRuntime, ".x-harness-managed.json"), JSON.stringify(metadata));
|
|
91
|
-
const installs = [{ src: stagedRuntime, dest: runtime, key: "runtime" }];
|
|
92
|
-
for (const t of targets) {
|
|
93
|
-
const stagedSkill = join(stage, `skill-${t}`);
|
|
94
|
-
cpSync(join(installedRoot, "skill"), stagedSkill, { recursive: true });
|
|
95
|
-
const content = readFileSync(join(stagedSkill, "SKILL.md"), "utf8")
|
|
96
|
-
.replaceAll("{{MODULE_URL}}", pathToFileURL(join(finalRoot, "dist", "index.js")).href)
|
|
97
|
-
.replaceAll("{{CLI_PATH}}", stableCLI);
|
|
98
|
-
writeFileSync(join(stagedSkill, "SKILL.md"), content);
|
|
99
|
-
writeFileSync(join(stagedSkill, ".x-harness-managed.json"), JSON.stringify(metadata));
|
|
100
|
-
installs.push({
|
|
101
|
-
src: stagedSkill,
|
|
102
|
-
dest: skillPath(t),
|
|
103
|
-
key: `skill-${t}`,
|
|
104
|
-
});
|
|
9
|
+
class XHarnessCLI {
|
|
10
|
+
#packageRoot;
|
|
11
|
+
#release;
|
|
12
|
+
#args;
|
|
13
|
+
#command;
|
|
14
|
+
#flags;
|
|
15
|
+
#customHome;
|
|
16
|
+
#home;
|
|
17
|
+
constructor(argv = process.argv.slice(2)) {
|
|
18
|
+
this.#packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
19
|
+
this.#release = JSON.parse(readFileSync(join(this.#packageRoot, "package.json"), "utf8"));
|
|
20
|
+
this.#args = argv.slice();
|
|
21
|
+
this.#command = this.#args.shift() ?? "help";
|
|
22
|
+
this.#flags = new Map();
|
|
23
|
+
for (let i = 0; i < this.#args.length; i += 2) {
|
|
24
|
+
const key = this.#args[i], value = this.#args[i + 1];
|
|
25
|
+
if (!["--target", "--home", "--directory", "--account"].includes(key) ||
|
|
26
|
+
!value ||
|
|
27
|
+
value.startsWith("--") ||
|
|
28
|
+
this.#flags.has(key))
|
|
29
|
+
throw Error(`Invalid argument: ${key}`);
|
|
30
|
+
this.#flags.set(key, value);
|
|
105
31
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const saved = join(backup, item.key), hadOld = existsSync(item.dest);
|
|
109
|
-
mkdirSync(dirname(item.dest), { recursive: true });
|
|
110
|
-
if (hadOld) {
|
|
111
|
-
mkdirSync(backup, { recursive: true });
|
|
112
|
-
renameSync(item.dest, saved);
|
|
113
|
-
}
|
|
114
|
-
changes.push({ dest: item.dest, backup: saved, hadOld });
|
|
115
|
-
renameSync(item.src, item.dest);
|
|
116
|
-
}
|
|
117
|
-
const state = { ...metadata, targets, updatedAt: new Date().toISOString() };
|
|
118
|
-
const nextState = join(stage, "installation.json");
|
|
119
|
-
writeFileSync(nextState, JSON.stringify(state, null, 2));
|
|
120
|
-
renameSync(nextState, stateFile);
|
|
121
|
-
completed = true;
|
|
122
|
-
output({
|
|
123
|
-
status: "installed",
|
|
124
|
-
...state,
|
|
125
|
-
moduleURL: pathToFileURL(join(finalRoot, "dist", "index.js")).href,
|
|
126
|
-
cliPath: stableCLI,
|
|
127
|
-
skillPaths: targets.map(skillPath),
|
|
128
|
-
backup: changes.some((c) => c.hadOld) ? backup : null,
|
|
129
|
-
next: "Start a fresh module binding using the installed x-harness Skill.",
|
|
130
|
-
});
|
|
32
|
+
this.#customHome = this.#flags.get("--home");
|
|
33
|
+
this.#home = resolve(this.#customHome ?? join(homedir(), ".x-harness"));
|
|
131
34
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
rmSync(change.dest, { recursive: true, force: true });
|
|
136
|
-
if (change.hadOld)
|
|
137
|
-
renameSync(change.backup, change.dest);
|
|
138
|
-
}
|
|
139
|
-
rmSync(stage, { recursive: true, force: true });
|
|
35
|
+
marker(path, file) {
|
|
36
|
+
return (existsSync(join(path, file)) &&
|
|
37
|
+
[this.#release.name, "@peiiii/x-harness"].includes(readJSON(join(path, file)).managedBy));
|
|
140
38
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (!/^[a-zA-Z0-9_]{1,15}$/.test(account))
|
|
146
|
-
throw Error("Provide your own X handle with --account");
|
|
147
|
-
const configPath = join(directory, "x-harness.json");
|
|
148
|
-
if (existsSync(configPath)) {
|
|
149
|
-
const config = readJSON(configPath);
|
|
150
|
-
if (config.accountHandle !== account)
|
|
151
|
-
throw Error("Existing workspace belongs to a different account");
|
|
152
|
-
output({ status: "existing", configPath, ...config });
|
|
153
|
-
return;
|
|
39
|
+
guard(path, file) {
|
|
40
|
+
if (existsSync(path) &&
|
|
41
|
+
(lstatSync(path).isSymbolicLink() || !this.marker(path, file)))
|
|
42
|
+
throw Error(`Refusing to overwrite unmanaged directory: ${path}`);
|
|
154
43
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
44
|
+
skillPath(target) {
|
|
45
|
+
if (!["codex", "claude"].includes(target))
|
|
46
|
+
throw Error("Target must be codex or claude");
|
|
47
|
+
return this.#customHome
|
|
48
|
+
? join(this.#home, "skills", target, "x-harness")
|
|
49
|
+
: join(homedir(), `.${target}`, "skills", "x-harness");
|
|
50
|
+
}
|
|
51
|
+
location() {
|
|
52
|
+
return {
|
|
53
|
+
version: this.#release.version,
|
|
54
|
+
moduleURL: pathToFileURL(join(this.#packageRoot, "dist", "index.js"))
|
|
55
|
+
.href,
|
|
56
|
+
cliPath: fileURLToPath(import.meta.url),
|
|
57
|
+
home: this.#home,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
stageRuntime(stage, runtime) {
|
|
61
|
+
const stagedRuntime = join(stage, "runtime");
|
|
62
|
+
const suffix = join("versions", this.#release.version, "node_modules", ...this.#release.name.split("/"));
|
|
63
|
+
const installedRoot = join(stagedRuntime, suffix);
|
|
64
|
+
cpSync(this.#packageRoot, installedRoot, { recursive: true });
|
|
65
|
+
if (readJSON(join(installedRoot, "package.json")).version !==
|
|
66
|
+
this.#release.version)
|
|
67
|
+
throw Error("Installed version mismatch");
|
|
68
|
+
const finalRoot = join(runtime, suffix);
|
|
69
|
+
const stableRoot = join(stagedRuntime, "node_modules", ...this.#release.name.split("/"));
|
|
70
|
+
const stableCLI = join(runtime, "node_modules", ...this.#release.name.split("/"), "cli", "main.js");
|
|
71
|
+
mkdirSync(join(stableRoot, "cli"), { recursive: true });
|
|
72
|
+
writeFileSync(join(stableRoot, "package.json"), JSON.stringify({ type: "module" }));
|
|
73
|
+
const entry = relative(dirname(stableCLI), join(finalRoot, "cli", "main.js"))
|
|
74
|
+
.split("\\")
|
|
75
|
+
.join("/");
|
|
76
|
+
writeFileSync(join(stableRoot, "cli", "main.js"), `#!/usr/bin/env node\nawait import(new URL(${JSON.stringify(entry)}, import.meta.url));\n`);
|
|
77
|
+
return { stagedRuntime, installedRoot, finalRoot, stableCLI };
|
|
78
|
+
}
|
|
79
|
+
setup() {
|
|
80
|
+
const target = this.#flags.get("--target") ?? "codex";
|
|
81
|
+
const stateFile = join(this.#home, "installation.json");
|
|
82
|
+
if (!existsSync(stateFile) &&
|
|
83
|
+
readdirSync(this.#home).some((name) => name !== ".installation.lock"))
|
|
84
|
+
throw Error(`Unmanaged installation home: ${this.#home}`);
|
|
85
|
+
const previous = existsSync(stateFile)
|
|
86
|
+
? readJSON(stateFile)
|
|
87
|
+
: { managedBy: this.#release.name, targets: [] };
|
|
88
|
+
if (![this.#release.name, "@peiiii/x-harness"].includes(previous.managedBy))
|
|
89
|
+
throw Error("Unmanaged installation state");
|
|
90
|
+
const targets = [
|
|
91
|
+
...new Set([...previous.targets, target]),
|
|
92
|
+
];
|
|
93
|
+
const runtime = join(this.#home, "runtime");
|
|
94
|
+
this.guard(runtime, ".x-harness-managed.json");
|
|
95
|
+
for (const t of targets)
|
|
96
|
+
this.guard(this.skillPath(t), ".x-harness-managed.json");
|
|
97
|
+
mkdirSync(this.#home, { recursive: true, mode: 0o700 });
|
|
98
|
+
const stage = mkdtempSync(join(this.#home, ".setup-"));
|
|
99
|
+
const backup = join(this.#home, "backups", `${Date.now()}-${randomUUID()}`);
|
|
100
|
+
const changes = [];
|
|
101
|
+
let completed = false;
|
|
190
102
|
try {
|
|
191
|
-
|
|
103
|
+
// Persist npx's already fetched runtime once; versioned URLs invalidate the whole module graph.
|
|
104
|
+
const { stagedRuntime, installedRoot, finalRoot, stableCLI } = this.stageRuntime(stage, runtime);
|
|
105
|
+
const metadata = {
|
|
106
|
+
managedBy: this.#release.name,
|
|
107
|
+
version: this.#release.version,
|
|
108
|
+
};
|
|
109
|
+
writeFileSync(join(stagedRuntime, ".x-harness-managed.json"), JSON.stringify(metadata));
|
|
110
|
+
const installs = [{ src: stagedRuntime, dest: runtime, key: "runtime" }];
|
|
111
|
+
for (const t of targets) {
|
|
112
|
+
const stagedSkill = join(stage, `skill-${t}`);
|
|
113
|
+
cpSync(join(installedRoot, "skill"), stagedSkill, { recursive: true });
|
|
114
|
+
const content = readFileSync(join(stagedSkill, "SKILL.md"), "utf8")
|
|
115
|
+
.replaceAll("{{MODULE_URL}}", pathToFileURL(join(finalRoot, "dist", "index.js")).href)
|
|
116
|
+
.replaceAll("{{CLI_PATH}}", stableCLI);
|
|
117
|
+
writeFileSync(join(stagedSkill, "SKILL.md"), content);
|
|
118
|
+
writeFileSync(join(stagedSkill, ".x-harness-managed.json"), JSON.stringify(metadata));
|
|
119
|
+
installs.push({
|
|
120
|
+
src: stagedSkill,
|
|
121
|
+
dest: this.skillPath(t),
|
|
122
|
+
key: `skill-${t}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// Install/download succeeds before replacing either managed artifact.
|
|
126
|
+
for (const item of installs) {
|
|
127
|
+
const saved = join(backup, item.key), hadOld = existsSync(item.dest);
|
|
128
|
+
mkdirSync(dirname(item.dest), { recursive: true });
|
|
129
|
+
if (hadOld) {
|
|
130
|
+
mkdirSync(backup, { recursive: true });
|
|
131
|
+
renameSync(item.dest, saved);
|
|
132
|
+
}
|
|
133
|
+
changes.push({ dest: item.dest, backup: saved, hadOld });
|
|
134
|
+
renameSync(item.src, item.dest);
|
|
135
|
+
}
|
|
136
|
+
const state = {
|
|
137
|
+
...metadata,
|
|
138
|
+
targets,
|
|
139
|
+
updatedAt: new Date().toISOString(),
|
|
140
|
+
};
|
|
141
|
+
const nextState = join(stage, "installation.json");
|
|
142
|
+
writeFileSync(nextState, JSON.stringify(state, null, 2));
|
|
143
|
+
renameSync(nextState, stateFile);
|
|
144
|
+
completed = true;
|
|
145
|
+
output({
|
|
146
|
+
status: "installed",
|
|
147
|
+
...state,
|
|
148
|
+
moduleURL: pathToFileURL(join(finalRoot, "dist", "index.js")).href,
|
|
149
|
+
cliPath: stableCLI,
|
|
150
|
+
skillPaths: targets.map(this.skillPath.bind(this)),
|
|
151
|
+
backup: changes.some((c) => c.hadOld) ? backup : null,
|
|
152
|
+
next: "Start a fresh module binding using the installed x-harness Skill.",
|
|
153
|
+
});
|
|
192
154
|
}
|
|
193
155
|
finally {
|
|
194
|
-
|
|
195
|
-
|
|
156
|
+
if (!completed)
|
|
157
|
+
for (const change of changes.reverse()) {
|
|
158
|
+
rmSync(change.dest, { recursive: true, force: true });
|
|
159
|
+
if (change.hadOld)
|
|
160
|
+
renameSync(change.backup, change.dest);
|
|
161
|
+
}
|
|
162
|
+
rmSync(stage, { recursive: true, force: true });
|
|
196
163
|
}
|
|
197
164
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
165
|
+
init() {
|
|
166
|
+
const directory = resolve(this.#flags.get("--directory") ?? "./x-workspace");
|
|
167
|
+
const account = (this.#flags.get("--account") ?? "").replace(/^@/, "");
|
|
168
|
+
if (!/^[a-zA-Z0-9_]{1,15}$/.test(account))
|
|
169
|
+
throw Error("Provide your own X handle with --account");
|
|
170
|
+
const configPath = join(directory, "x-harness.json");
|
|
171
|
+
if (existsSync(configPath)) {
|
|
172
|
+
const config = readJSON(configPath);
|
|
173
|
+
if (config.accountHandle !== account)
|
|
174
|
+
throw Error("Existing workspace belongs to a different account");
|
|
175
|
+
output({ status: "existing", configPath, ...config });
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
179
|
+
const ledgerPath = join(directory, "round.md"), proofDirectory = join(directory, "proofs");
|
|
180
|
+
if (existsSync(ledgerPath))
|
|
181
|
+
throw Error("Existing ledger is unmanaged; use its configuration instead of overwriting");
|
|
182
|
+
const taskId = `x-${randomUUID()}`;
|
|
183
|
+
const state = {
|
|
184
|
+
batchId: taskId,
|
|
185
|
+
status: "RUNNING",
|
|
186
|
+
updatedAt: new Date().toISOString(),
|
|
187
|
+
accountHandle: account,
|
|
188
|
+
items: ["follow", "original", "reply"].map((kind) => ({
|
|
189
|
+
kind,
|
|
190
|
+
planned: 0,
|
|
191
|
+
verified: 0,
|
|
192
|
+
unknown: 0,
|
|
193
|
+
actionable: true,
|
|
194
|
+
})),
|
|
195
|
+
execution: { owner: "harness", exclusive: true },
|
|
196
|
+
attempts: [],
|
|
197
|
+
actions: [],
|
|
198
|
+
};
|
|
199
|
+
mkdirSync(proofDirectory);
|
|
200
|
+
writeFileSync(ledgerPath, `# @${account} · Your X workspace\n\n\`\`\`x-round-state\n${JSON.stringify(state, null, 2)}\n\`\`\`\n`, { flag: "wx", mode: 0o600 });
|
|
201
|
+
const config = {
|
|
202
|
+
accountHandle: account,
|
|
203
|
+
taskId,
|
|
204
|
+
ledgerPath,
|
|
205
|
+
proofDirectory,
|
|
206
|
+
};
|
|
207
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), {
|
|
208
|
+
flag: "wx",
|
|
209
|
+
mode: 0o600,
|
|
209
210
|
});
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
211
|
+
output({ status: "initialized", configPath, ...config });
|
|
212
|
+
}
|
|
213
|
+
run() {
|
|
214
|
+
try {
|
|
215
|
+
if (["setup", "update"].includes(this.#command)) {
|
|
216
|
+
mkdirSync(this.#home, { recursive: true, mode: 0o700 });
|
|
217
|
+
const lockPath = join(this.#home, ".installation.lock");
|
|
218
|
+
const lock = openSync(lockPath, "wx", 0o600);
|
|
219
|
+
try {
|
|
220
|
+
this.setup();
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
closeSync(lock);
|
|
224
|
+
unlinkSync(lockPath);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else if (this.#command === "init")
|
|
228
|
+
this.init();
|
|
229
|
+
else if (this.#command === "location")
|
|
230
|
+
output(this.location());
|
|
231
|
+
else if (this.#command === "help")
|
|
232
|
+
output({
|
|
233
|
+
setup: "npx @nextclaw/x-harness@latest setup --target codex",
|
|
234
|
+
update: "npx @nextclaw/x-harness@latest update --target codex",
|
|
235
|
+
init: "node <cliPath> init --directory ./x-workspace --account YOUR_HANDLE",
|
|
236
|
+
targets: ["codex", "claude"],
|
|
237
|
+
requires: "Node 22+; compatible CUA browser host and your own X login",
|
|
238
|
+
});
|
|
239
|
+
else
|
|
240
|
+
throw Error(`Unknown command: ${this.#command}`);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
process.stderr.write(JSON.stringify({
|
|
244
|
+
status: "failed",
|
|
245
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
246
|
+
}) + "\n");
|
|
247
|
+
process.exitCode = 1;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
219
250
|
}
|
|
251
|
+
new XHarnessCLI().run();
|
package/dist/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { XGrowthHarness } from "./types.js";
|
|
1
|
+
import type { ActionResult, ReadMode, ReadResult, RoundResult, XGrowthHarness, BatchResult } from "./types.js";
|
|
2
2
|
import type { BoundaryRecord } from "./boundary.js";
|
|
3
3
|
/** Internal dependencies; only the normal X factory is exported from the root. */
|
|
4
4
|
export interface BusinessDependencies {
|
|
@@ -7,4 +7,58 @@ export interface BusinessDependencies {
|
|
|
7
7
|
clock?: () => number;
|
|
8
8
|
executor?: BoundaryRecord;
|
|
9
9
|
}
|
|
10
|
-
export declare
|
|
10
|
+
export declare class BusinessHarness implements XGrowthHarness {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(dependencies: BusinessDependencies);
|
|
13
|
+
private invoke;
|
|
14
|
+
private read;
|
|
15
|
+
private compactRound;
|
|
16
|
+
private single;
|
|
17
|
+
readCreator(args: import("./types.js").CreatorOptions): Promise<ReadResult>;
|
|
18
|
+
readBookmarks(args: import("./types.js").BookmarkOptions): Promise<ReadResult>;
|
|
19
|
+
searchSources(args: import("./types.js").SearchOptions): Promise<ReadResult>;
|
|
20
|
+
readPost({ target, mode, }: {
|
|
21
|
+
target: import("./types.js").PostTarget;
|
|
22
|
+
mode?: ReadMode;
|
|
23
|
+
}): Promise<ReadResult>;
|
|
24
|
+
readPerson({ target, mode, }: {
|
|
25
|
+
target: import("./types.js").PersonTarget;
|
|
26
|
+
mode?: ReadMode;
|
|
27
|
+
}): Promise<ReadResult>;
|
|
28
|
+
readFeedback(args: import("./types.js").FeedbackOptions): Promise<ReadResult>;
|
|
29
|
+
inspectAccount(args?: {
|
|
30
|
+
mode?: ReadMode;
|
|
31
|
+
} | undefined): Promise<ReadResult>;
|
|
32
|
+
inspectRound(args: {
|
|
33
|
+
taskId: string;
|
|
34
|
+
}): Promise<RoundResult>;
|
|
35
|
+
runGrowthRound(args: import("./types.js").RoundOptions): Promise<RoundResult>;
|
|
36
|
+
publishPost(args: import("./types.js").PublishOptions): Promise<ActionResult>;
|
|
37
|
+
replyToPost(args: import("./types.js").ReplyOptions): Promise<ActionResult>;
|
|
38
|
+
followOne(args: {
|
|
39
|
+
taskId: string;
|
|
40
|
+
handle: string;
|
|
41
|
+
}): Promise<ActionResult>;
|
|
42
|
+
verifySubmission(args: {
|
|
43
|
+
taskId: string;
|
|
44
|
+
actionId: string;
|
|
45
|
+
}): Promise<ActionResult>;
|
|
46
|
+
verifyPublishedPost(args: {
|
|
47
|
+
taskId: string;
|
|
48
|
+
draftPath: string;
|
|
49
|
+
publicURL: string;
|
|
50
|
+
evidenceRef: import("./types.js").EvidenceRef;
|
|
51
|
+
sourceLink?: import("./types.js").SourceLinkEvidence;
|
|
52
|
+
}): Promise<ActionResult>;
|
|
53
|
+
followBatch(args: import("./types.js").FollowOptions): Promise<BatchResult>;
|
|
54
|
+
verifyFollow(args: {
|
|
55
|
+
handle: string;
|
|
56
|
+
url?: string;
|
|
57
|
+
evidenceRef?: import("./types.js").EvidenceRef;
|
|
58
|
+
mode?: "receipt" | "decision";
|
|
59
|
+
}): Promise<ReadResult & {
|
|
60
|
+
handle: string;
|
|
61
|
+
verified: boolean;
|
|
62
|
+
}>;
|
|
63
|
+
}
|
|
64
|
+
export declare function createBusinessHarness(...args: ConstructorParameters<typeof BusinessHarness>): XGrowthHarness;
|