@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.
Files changed (79) hide show
  1. package/LICENSE +231 -6
  2. package/README.md +15 -1
  3. package/SOURCE.md +9 -0
  4. package/cli/main.js +232 -200
  5. package/dist/api.d.ts +56 -2
  6. package/dist/api.js +130 -79
  7. package/dist/boundary.js +1 -0
  8. package/dist/browser-common.d.ts +11 -0
  9. package/dist/browser-common.js +34 -18
  10. package/dist/browser-dom-capabilities.d.ts +7 -2
  11. package/dist/browser-dom-capabilities.js +123 -102
  12. package/dist/browser-dom-dependencies.js +1 -0
  13. package/dist/browser-dom-editing.d.ts +20 -2
  14. package/dist/browser-dom-editing.js +148 -116
  15. package/dist/browser-dom-navigation.d.ts +11 -2
  16. package/dist/browser-dom-navigation.js +66 -50
  17. package/dist/browser-dom-submission.d.ts +6 -1
  18. package/dist/browser-dom-submission.js +37 -18
  19. package/dist/browser-dom.d.ts +13 -1
  20. package/dist/browser-dom.js +113 -66
  21. package/dist/browser-native.d.ts +14 -1
  22. package/dist/browser-native.js +161 -128
  23. package/dist/creator-native.d.ts +3 -0
  24. package/dist/creator-native.js +113 -0
  25. package/dist/diagnostics.d.ts +11 -0
  26. package/dist/diagnostics.js +10 -0
  27. package/dist/growth-batch.d.ts +6 -3
  28. package/dist/growth-batch.js +157 -135
  29. package/dist/growth-dependencies.js +1 -0
  30. package/dist/growth-execution.d.ts +8 -2
  31. package/dist/growth-execution.js +68 -37
  32. package/dist/growth-parallel.d.ts +6 -2
  33. package/dist/growth-parallel.js +78 -47
  34. package/dist/growth-plan.d.ts +8 -3
  35. package/dist/growth-plan.js +45 -23
  36. package/dist/growth-policy.d.ts +11 -2
  37. package/dist/growth-policy.js +80 -53
  38. package/dist/growth-publishing.d.ts +7 -2
  39. package/dist/growth-publishing.js +72 -46
  40. package/dist/growth-reading.d.ts +6 -2
  41. package/dist/growth-reading.js +47 -25
  42. package/dist/growth-recovery.d.ts +7 -4
  43. package/dist/growth-recovery.js +177 -148
  44. package/dist/growth-state.d.ts +10 -2
  45. package/dist/growth-state.js +67 -30
  46. package/dist/growth-tasks.d.ts +19 -16
  47. package/dist/growth-tasks.js +153 -90
  48. package/dist/growth.d.ts +25 -1
  49. package/dist/growth.js +207 -124
  50. package/dist/platform-capabilities.d.ts +6 -2
  51. package/dist/platform-capabilities.js +18 -8
  52. package/dist/platform-collections.d.ts +10 -3
  53. package/dist/platform-collections.js +62 -35
  54. package/dist/platform-dependencies.js +1 -0
  55. package/dist/platform-diagnostics.d.ts +7 -2
  56. package/dist/platform-diagnostics.js +31 -19
  57. package/dist/platform-observation.d.ts +10 -2
  58. package/dist/platform-observation.js +44 -23
  59. package/dist/platform-targets.d.ts +10 -3
  60. package/dist/platform-targets.js +39 -14
  61. package/dist/platform-writing.d.ts +10 -2
  62. package/dist/platform-writing.js +63 -32
  63. package/dist/platform.d.ts +22 -1
  64. package/dist/platform.js +120 -52
  65. package/dist/readers-ax-host.d.ts +11 -1
  66. package/dist/readers-ax-host.js +108 -99
  67. package/dist/readers-ax-program.js +5 -0
  68. package/dist/readers-ax-tree.js +1 -0
  69. package/dist/readers-cli.js +79 -0
  70. package/dist/readers.js +9 -0
  71. package/dist/round-state.js +18 -1
  72. package/dist/store.d.ts +22 -4
  73. package/dist/store.js +173 -151
  74. package/dist/types.js +1 -0
  75. package/generated/observation.mjs +1 -1
  76. package/package.json +23 -9
  77. package/skill/SKILL.md +2 -2
  78. package/skill/references/reads.md +1 -1
  79. 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
- const marker = (path, file) => existsSync(join(path, file)) &&
26
- [release.name, "@peiiii/x-harness"].includes(readJSON(join(path, file)).managedBy);
27
- function guard(path, file) {
28
- if (existsSync(path) &&
29
- (lstatSync(path).isSymbolicLink() || !marker(path, file)))
30
- throw Error(`Refusing to overwrite unmanaged directory: ${path}`);
31
- }
32
- function skillPath(target) {
33
- if (!["codex", "claude"].includes(target))
34
- throw Error("Target must be codex or claude");
35
- return customHome
36
- ? join(home, "skills", target, "x-harness")
37
- : join(homedir(), `.${target}`, "skills", "x-harness");
38
- }
39
- function location() {
40
- return {
41
- version: release.version,
42
- moduleURL: pathToFileURL(join(packageRoot, "dist", "index.js")).href,
43
- cliPath: fileURLToPath(import.meta.url),
44
- home,
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
- // Install/download succeeds before replacing either managed artifact.
107
- for (const item of installs) {
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
- finally {
133
- if (!completed)
134
- for (const change of changes.reverse()) {
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
- function init() {
143
- const directory = resolve(flags.get("--directory") ?? "./x-workspace");
144
- const account = (flags.get("--account") ?? "").replace(/^@/, "");
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
- mkdirSync(directory, { recursive: true, mode: 0o700 });
156
- const ledgerPath = join(directory, "round.md"), proofDirectory = join(directory, "proofs");
157
- if (existsSync(ledgerPath))
158
- throw Error("Existing ledger is unmanaged; use its configuration instead of overwriting");
159
- const taskId = `x-${randomUUID()}`;
160
- const state = {
161
- batchId: taskId,
162
- status: "RUNNING",
163
- updatedAt: new Date().toISOString(),
164
- accountHandle: account,
165
- items: ["follow", "original", "reply"].map((kind) => ({
166
- kind,
167
- planned: 0,
168
- verified: 0,
169
- unknown: 0,
170
- actionable: true,
171
- })),
172
- execution: { owner: "harness", exclusive: true },
173
- attempts: [],
174
- actions: [],
175
- };
176
- mkdirSync(proofDirectory);
177
- writeFileSync(ledgerPath, `# @${account} · Your X workspace\n\n\`\`\`x-round-state\n${JSON.stringify(state, null, 2)}\n\`\`\`\n`, { flag: "wx", mode: 0o600 });
178
- const config = { accountHandle: account, taskId, ledgerPath, proofDirectory };
179
- writeFileSync(configPath, JSON.stringify(config, null, 2), {
180
- flag: "wx",
181
- mode: 0o600,
182
- });
183
- output({ status: "initialized", configPath, ...config });
184
- }
185
- try {
186
- if (["setup", "update"].includes(command)) {
187
- mkdirSync(home, { recursive: true, mode: 0o700 });
188
- const lockPath = join(home, ".installation.lock");
189
- const lock = openSync(lockPath, "wx", 0o600);
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
- setup();
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
- closeSync(lock);
195
- unlinkSync(lockPath);
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
- else if (command === "init")
199
- init();
200
- else if (command === "location")
201
- output(location());
202
- else if (command === "help")
203
- output({
204
- setup: "npx @nextclaw/x-harness@latest setup --target codex",
205
- update: "npx @nextclaw/x-harness@latest update --target codex",
206
- init: "node <cliPath> init --directory ./x-workspace --account YOUR_HANDLE",
207
- targets: ["codex", "claude"],
208
- requires: "Node 22+; compatible CUA browser host and your own X login",
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
- else
211
- throw Error(`Unknown command: ${command}`);
212
- }
213
- catch (error) {
214
- process.stderr.write(JSON.stringify({
215
- status: "failed",
216
- reason: error instanceof Error ? error.message : String(error),
217
- }) + "\n");
218
- process.exitCode = 1;
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 function createBusinessHarness(dependencies: BusinessDependencies): XGrowthHarness;
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;