@nextclaw/x-harness 0.1.0 → 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 (113) hide show
  1. package/LICENSE +231 -6
  2. package/README.md +17 -3
  3. package/SOURCE.md +9 -0
  4. package/cli/main.js +232 -188
  5. package/dist/api.d.ts +56 -2
  6. package/dist/api.js +178 -75
  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 +9 -0
  11. package/dist/browser-dom-capabilities.js +131 -0
  12. package/dist/browser-dom-dependencies.d.ts +16 -0
  13. package/dist/browser-dom-dependencies.js +1 -0
  14. package/dist/browser-dom-editing.d.ts +166 -0
  15. package/dist/browser-dom-editing.js +418 -0
  16. package/dist/browser-dom-navigation.d.ts +18 -0
  17. package/dist/browser-dom-navigation.js +181 -0
  18. package/dist/browser-dom-network.d.ts +1 -0
  19. package/dist/browser-dom-network.js +128 -0
  20. package/dist/browser-dom-submission.d.ts +7 -0
  21. package/dist/browser-dom-submission.js +101 -0
  22. package/dist/browser-dom.d.ts +14 -2
  23. package/dist/browser-dom.js +120 -824
  24. package/dist/browser-native.d.ts +14 -1
  25. package/dist/browser-native.js +180 -143
  26. package/dist/browser.js +6 -2
  27. package/dist/creator-native.d.ts +3 -0
  28. package/dist/creator-native.js +113 -0
  29. package/dist/creator.js +1 -1
  30. package/dist/diagnostics.d.ts +11 -0
  31. package/dist/diagnostics.js +10 -0
  32. package/dist/growth-batch.d.ts +8 -0
  33. package/dist/growth-batch.js +190 -0
  34. package/dist/growth-constants.d.ts +5 -0
  35. package/dist/growth-constants.js +13 -0
  36. package/dist/growth-dependencies.d.ts +33 -0
  37. package/dist/growth-dependencies.js +1 -0
  38. package/dist/growth-execution.d.ts +17 -0
  39. package/dist/growth-execution.js +346 -0
  40. package/dist/growth-parallel.d.ts +8 -0
  41. package/dist/growth-parallel.js +332 -0
  42. package/dist/growth-plan.d.ts +9 -0
  43. package/dist/growth-plan.js +229 -0
  44. package/dist/growth-policy.d.ts +18 -0
  45. package/dist/growth-policy.js +208 -0
  46. package/dist/growth-publishing.d.ts +10 -0
  47. package/dist/growth-publishing.js +266 -0
  48. package/dist/growth-reading.d.ts +8 -0
  49. package/dist/growth-reading.js +157 -0
  50. package/dist/growth-recovery.d.ts +9 -0
  51. package/dist/growth-recovery.js +187 -0
  52. package/dist/growth-state.d.ts +16 -0
  53. package/dist/growth-state.js +220 -0
  54. package/dist/growth-tasks.d.ts +20 -0
  55. package/dist/growth-tasks.js +164 -0
  56. package/dist/growth.d.ts +26 -1
  57. package/dist/growth.js +211 -1908
  58. package/dist/platform-capabilities.d.ts +8 -0
  59. package/dist/platform-capabilities.js +29 -0
  60. package/dist/platform-collections.d.ts +14 -0
  61. package/dist/platform-collections.js +250 -0
  62. package/dist/platform-dependencies.d.ts +43 -0
  63. package/dist/platform-dependencies.js +1 -0
  64. package/dist/platform-diagnostics.d.ts +14 -0
  65. package/dist/platform-diagnostics.js +63 -0
  66. package/dist/platform-observation.d.ts +16 -0
  67. package/dist/platform-observation.js +96 -0
  68. package/dist/platform-rules.d.ts +17 -0
  69. package/dist/platform-rules.js +41 -0
  70. package/dist/platform-targets.d.ts +14 -0
  71. package/dist/platform-targets.js +154 -0
  72. package/dist/platform-writing.d.ts +16 -0
  73. package/dist/platform-writing.js +202 -0
  74. package/dist/platform.d.ts +23 -2
  75. package/dist/platform.js +125 -678
  76. package/dist/readers-actions.d.ts +2 -0
  77. package/dist/readers-actions.js +66 -0
  78. package/dist/readers-ax-host.d.ts +12 -0
  79. package/dist/readers-ax-host.js +134 -0
  80. package/dist/readers-ax-parser.d.ts +1 -0
  81. package/dist/readers-ax-parser.js +69 -0
  82. package/dist/readers-ax-program.d.ts +1 -0
  83. package/dist/readers-ax-program.js +5 -0
  84. package/dist/readers-ax-tree.d.ts +19 -0
  85. package/dist/readers-ax-tree.js +111 -0
  86. package/dist/readers-ax-views.d.ts +87 -0
  87. package/dist/readers-ax-views.js +247 -0
  88. package/dist/readers-cli.d.ts +6 -0
  89. package/dist/readers-cli.js +79 -0
  90. package/dist/readers-composer.d.ts +2 -0
  91. package/dist/readers-composer.js +131 -0
  92. package/dist/readers-navigation.d.ts +2 -0
  93. package/dist/readers-navigation.js +66 -0
  94. package/dist/readers-posts.d.ts +2 -0
  95. package/dist/readers-posts.js +141 -0
  96. package/dist/readers-profile.d.ts +3 -0
  97. package/dist/readers-profile.js +157 -0
  98. package/dist/readers.d.ts +9 -20
  99. package/dist/readers.js +9 -1045
  100. package/dist/results.js +1 -1
  101. package/dist/round-state.js +20 -3
  102. package/dist/store.d.ts +22 -4
  103. package/dist/store.js +173 -151
  104. package/dist/types.d.ts +10 -0
  105. package/dist/types.js +1 -0
  106. package/generated/observation.mjs +3 -2
  107. package/package.json +23 -9
  108. package/skill/SKILL.md +17 -36
  109. package/skill/references/api.md +7 -34
  110. package/skill/references/reads.md +23 -0
  111. package/skill/references/recovery.md +25 -0
  112. package/skill/references/setup.md +23 -0
  113. package/skill/references/writes.md +51 -0
package/cli/main.js CHANGED
@@ -1,207 +1,251 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync, renameSync, rmSync, cpSync, mkdtempSync, openSync, closeSync, unlinkSync, readdirSync, } from "node:fs";
3
- import { join, resolve, dirname } from "node:path";
3
+ 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 setup() {
48
- const target = flags.get("--target") ?? "codex";
49
- const stateFile = join(home, "installation.json");
50
- if (!existsSync(stateFile) &&
51
- readdirSync(home).some((name) => name !== ".installation.lock"))
52
- throw Error(`Unmanaged installation home: ${home}`);
53
- const previous = existsSync(stateFile)
54
- ? readJSON(stateFile)
55
- : { managedBy: release.name, targets: [] };
56
- if (![release.name, "@peiiii/x-harness"].includes(previous.managedBy))
57
- throw Error("Unmanaged installation state");
58
- const targets = [...new Set([...previous.targets, target])];
59
- const runtime = join(home, "runtime");
60
- guard(runtime, ".x-harness-managed.json");
61
- for (const t of targets)
62
- guard(skillPath(t), ".x-harness-managed.json");
63
- mkdirSync(home, { recursive: true, mode: 0o700 });
64
- const stage = mkdtempSync(join(home, ".setup-"));
65
- const backup = join(home, "backups", `${Date.now()}-${randomUUID()}`);
66
- const changes = [];
67
- let completed = false;
68
- try {
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);
31
+ }
32
+ this.#customHome = this.#flags.get("--home");
33
+ this.#home = resolve(this.#customHome ?? join(homedir(), ".x-harness"));
34
+ }
35
+ marker(path, file) {
36
+ return (existsSync(join(path, file)) &&
37
+ [this.#release.name, "@peiiii/x-harness"].includes(readJSON(join(path, file)).managedBy));
38
+ }
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}`);
43
+ }
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) {
69
61
  const stagedRuntime = join(stage, "runtime");
70
- mkdirSync(stagedRuntime);
71
- const installedRoot = join(stagedRuntime, "node_modules", ...release.name.split("/"));
72
- // npx has already fetched this release. Persist its dependency-free runtime once.
73
- cpSync(packageRoot, installedRoot, { recursive: true });
74
- if (readJSON(join(installedRoot, "package.json")).version !== release.version)
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)
75
67
  throw Error("Installed version mismatch");
76
- const metadata = { managedBy: release.name, version: release.version };
77
- writeFileSync(join(stagedRuntime, ".x-harness-managed.json"), JSON.stringify(metadata));
78
- const finalRoot = join(runtime, "node_modules", ...release.name.split("/"));
79
- const installs = [{ src: stagedRuntime, dest: runtime, key: "runtime" }];
80
- for (const t of targets) {
81
- const stagedSkill = join(stage, `skill-${t}`);
82
- cpSync(join(installedRoot, "skill"), stagedSkill, { recursive: true });
83
- const content = readFileSync(join(stagedSkill, "SKILL.md"), "utf8")
84
- .replaceAll("{{MODULE_URL}}", pathToFileURL(join(finalRoot, "dist", "index.js")).href)
85
- .replaceAll("{{CLI_PATH}}", join(finalRoot, "cli", "main.js"));
86
- writeFileSync(join(stagedSkill, "SKILL.md"), content);
87
- writeFileSync(join(stagedSkill, ".x-harness-managed.json"), JSON.stringify(metadata));
88
- installs.push({
89
- src: stagedSkill,
90
- dest: skillPath(t),
91
- key: `skill-${t}`,
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;
102
+ try {
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.",
92
153
  });
93
154
  }
94
- // Install/download succeeds before replacing either managed artifact.
95
- for (const item of installs) {
96
- const saved = join(backup, item.key), hadOld = existsSync(item.dest);
97
- mkdirSync(dirname(item.dest), { recursive: true });
98
- if (hadOld) {
99
- mkdirSync(backup, { recursive: true });
100
- renameSync(item.dest, saved);
101
- }
102
- changes.push({ dest: item.dest, backup: saved, hadOld });
103
- renameSync(item.src, item.dest);
155
+ finally {
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 });
104
163
  }
105
- const state = { ...metadata, targets, updatedAt: new Date().toISOString() };
106
- const nextState = join(stage, "installation.json");
107
- writeFileSync(nextState, JSON.stringify(state, null, 2));
108
- renameSync(nextState, stateFile);
109
- completed = true;
110
- output({
111
- status: "installed",
112
- ...state,
113
- moduleURL: pathToFileURL(join(finalRoot, "dist", "index.js")).href,
114
- cliPath: join(finalRoot, "cli", "main.js"),
115
- skillPaths: targets.map(skillPath),
116
- backup: changes.some((c) => c.hadOld) ? backup : null,
117
- next: "Start a fresh module binding using the installed x-harness Skill.",
118
- });
119
164
  }
120
- finally {
121
- if (!completed)
122
- for (const change of changes.reverse()) {
123
- rmSync(change.dest, { recursive: true, force: true });
124
- if (change.hadOld)
125
- renameSync(change.backup, change.dest);
126
- }
127
- rmSync(stage, { recursive: true, force: true });
128
- }
129
- }
130
- function init() {
131
- const directory = resolve(flags.get("--directory") ?? "./x-workspace");
132
- const account = (flags.get("--account") ?? "").replace(/^@/, "");
133
- if (!/^[a-zA-Z0-9_]{1,15}$/.test(account))
134
- throw Error("Provide your own X handle with --account");
135
- const configPath = join(directory, "x-harness.json");
136
- if (existsSync(configPath)) {
137
- const config = readJSON(configPath);
138
- if (config.accountHandle !== account)
139
- throw Error("Existing workspace belongs to a different account");
140
- output({ status: "existing", configPath, ...config });
141
- return;
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,
210
+ });
211
+ output({ status: "initialized", configPath, ...config });
142
212
  }
143
- mkdirSync(directory, { recursive: true, mode: 0o700 });
144
- const ledgerPath = join(directory, "round.md"), proofDirectory = join(directory, "proofs");
145
- if (existsSync(ledgerPath))
146
- throw Error("Existing ledger is unmanaged; use its configuration instead of overwriting");
147
- const taskId = `x-${randomUUID()}`;
148
- const state = {
149
- batchId: taskId,
150
- status: "RUNNING",
151
- updatedAt: new Date().toISOString(),
152
- accountHandle: account,
153
- items: ["follow", "original", "reply"].map((kind) => ({
154
- kind,
155
- planned: 0,
156
- verified: 0,
157
- unknown: 0,
158
- actionable: true,
159
- })),
160
- execution: { owner: "harness", exclusive: true },
161
- attempts: [],
162
- actions: [],
163
- };
164
- mkdirSync(proofDirectory);
165
- writeFileSync(ledgerPath, `# @${account} · Your X workspace\n\n\`\`\`x-round-state\n${JSON.stringify(state, null, 2)}\n\`\`\`\n`, { flag: "wx", mode: 0o600 });
166
- const config = { accountHandle: account, taskId, ledgerPath, proofDirectory };
167
- writeFileSync(configPath, JSON.stringify(config, null, 2), {
168
- flag: "wx",
169
- mode: 0o600,
170
- });
171
- output({ status: "initialized", configPath, ...config });
172
- }
173
- try {
174
- if (["setup", "update"].includes(command)) {
175
- mkdirSync(home, { recursive: true, mode: 0o700 });
176
- const lockPath = join(home, ".installation.lock");
177
- const lock = openSync(lockPath, "wx", 0o600);
213
+ run() {
178
214
  try {
179
- setup();
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}`);
180
241
  }
181
- finally {
182
- closeSync(lock);
183
- unlinkSync(lockPath);
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;
184
248
  }
185
249
  }
186
- else if (command === "init")
187
- init();
188
- else if (command === "location")
189
- output(location());
190
- else if (command === "help")
191
- output({
192
- setup: "npx @nextclaw/x-harness@latest setup --target codex",
193
- update: "npx @nextclaw/x-harness@latest update --target codex",
194
- init: "node <cliPath> init --directory ./x-workspace --account YOUR_HANDLE",
195
- targets: ["codex", "claude"],
196
- requires: "Node 22+; compatible CUA browser host and your own X login",
197
- });
198
- else
199
- throw Error(`Unknown command: ${command}`);
200
- }
201
- catch (error) {
202
- process.stderr.write(JSON.stringify({
203
- status: "failed",
204
- reason: error instanceof Error ? error.message : String(error),
205
- }) + "\n");
206
- process.exitCode = 1;
207
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;