@hasna/codewith 0.1.2
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 +52 -0
- package/bin/codex.js +255 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<p align="center"><strong>Codewith</strong> is a command-line coding agent from Hasna that runs locally on your computer.</p>
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Quickstart
|
|
6
|
+
|
|
7
|
+
### Installing and running Codewith
|
|
8
|
+
|
|
9
|
+
Run the following on Mac, Linux, or Windows to install Codewith:
|
|
10
|
+
|
|
11
|
+
```shell
|
|
12
|
+
bun install -g @hasna/codewith
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Codewith can also be installed through npm:
|
|
16
|
+
|
|
17
|
+
```shell
|
|
18
|
+
npm install -g @hasna/codewith
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then run `codewith` to get started. Codewith stores its local state in `~/.codewith` by default.
|
|
22
|
+
|
|
23
|
+
<details>
|
|
24
|
+
<summary>You can also download a platform binary from the latest Codewith GitHub Release.</summary>
|
|
25
|
+
|
|
26
|
+
Each GitHub Release contains many executables, but in practice, you likely want one of these:
|
|
27
|
+
|
|
28
|
+
- macOS
|
|
29
|
+
- Apple Silicon/arm64: `codewith-aarch64-apple-darwin.tar.gz`
|
|
30
|
+
- x86_64 (older Mac hardware): `codewith-x86_64-apple-darwin.tar.gz`
|
|
31
|
+
- Linux
|
|
32
|
+
- x86_64: `codewith-x86_64-unknown-linux-musl.tar.gz`
|
|
33
|
+
- arm64: `codewith-aarch64-unknown-linux-musl.tar.gz`
|
|
34
|
+
|
|
35
|
+
Each archive contains a single entry with the platform baked into the name (e.g., `codewith-x86_64-unknown-linux-musl`), so you likely want to rename it to `codewith` after extracting it.
|
|
36
|
+
|
|
37
|
+
</details>
|
|
38
|
+
|
|
39
|
+
### Using Codewith with your ChatGPT plan
|
|
40
|
+
|
|
41
|
+
Run `codewith` and select **Sign in with ChatGPT**. We recommend signing into your ChatGPT account to use Codewith as part of your Plus, Pro, Business, Edu, or Enterprise plan. [Learn more about what's included in your ChatGPT plan](https://help.openai.com/en/articles/11369540-codex-in-chatgpt).
|
|
42
|
+
|
|
43
|
+
You can also use Codewith with an API key.
|
|
44
|
+
|
|
45
|
+
## Docs
|
|
46
|
+
|
|
47
|
+
- [**Authentication**](./docs/authentication.md)
|
|
48
|
+
- [**Contributing**](./docs/contributing.md)
|
|
49
|
+
- [**Installing & building**](./docs/install.md)
|
|
50
|
+
- [**Open source fund**](./docs/open-source-fund.md)
|
|
51
|
+
|
|
52
|
+
This repository is licensed under the [Apache-2.0 License](LICENSE).
|
package/bin/codex.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Unified entry point for the Codewith CLI.
|
|
3
|
+
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { existsSync, mkdirSync, realpathSync, chmodSync } from "fs";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
|
|
11
|
+
// __dirname equivalent in ESM
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
|
|
16
|
+
const PLATFORM_PACKAGE_BY_TARGET = {
|
|
17
|
+
"x86_64-unknown-linux-musl": "@hasna/codewith-linux-x64",
|
|
18
|
+
"aarch64-unknown-linux-musl": "@hasna/codewith-linux-arm64",
|
|
19
|
+
"x86_64-apple-darwin": "@hasna/codewith-darwin-x64",
|
|
20
|
+
"aarch64-apple-darwin": "@hasna/codewith-darwin-arm64",
|
|
21
|
+
"x86_64-pc-windows-msvc": "@hasna/codewith-win32-x64",
|
|
22
|
+
"aarch64-pc-windows-msvc": "@hasna/codewith-win32-arm64",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const { platform, arch } = process;
|
|
26
|
+
|
|
27
|
+
let targetTriple = null;
|
|
28
|
+
switch (platform) {
|
|
29
|
+
case "linux":
|
|
30
|
+
case "android":
|
|
31
|
+
switch (arch) {
|
|
32
|
+
case "x64":
|
|
33
|
+
targetTriple = "x86_64-unknown-linux-musl";
|
|
34
|
+
break;
|
|
35
|
+
case "arm64":
|
|
36
|
+
targetTriple = "aarch64-unknown-linux-musl";
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
case "darwin":
|
|
43
|
+
switch (arch) {
|
|
44
|
+
case "x64":
|
|
45
|
+
targetTriple = "x86_64-apple-darwin";
|
|
46
|
+
break;
|
|
47
|
+
case "arm64":
|
|
48
|
+
targetTriple = "aarch64-apple-darwin";
|
|
49
|
+
break;
|
|
50
|
+
default:
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
case "win32":
|
|
55
|
+
switch (arch) {
|
|
56
|
+
case "x64":
|
|
57
|
+
targetTriple = "x86_64-pc-windows-msvc";
|
|
58
|
+
break;
|
|
59
|
+
case "arm64":
|
|
60
|
+
targetTriple = "aarch64-pc-windows-msvc";
|
|
61
|
+
break;
|
|
62
|
+
default:
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!targetTriple) {
|
|
71
|
+
throw new Error(`Unsupported platform: ${platform} (${arch})`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const platformPackage = PLATFORM_PACKAGE_BY_TARGET[targetTriple];
|
|
75
|
+
if (!platformPackage) {
|
|
76
|
+
throw new Error(`Unsupported target triple: ${targetTriple}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
|
|
80
|
+
const localVendorRoot = path.join(__dirname, "..", "vendor");
|
|
81
|
+
const packageBinaryPath = (vendorRoot) =>
|
|
82
|
+
path.join(vendorRoot, targetTriple, "bin", codexBinaryName);
|
|
83
|
+
const legacyBinaryPath = (vendorRoot) =>
|
|
84
|
+
path.join(vendorRoot, targetTriple, "codex", codexBinaryName);
|
|
85
|
+
|
|
86
|
+
function resolveNativePackage(vendorRoot) {
|
|
87
|
+
const packageRoot = path.join(vendorRoot, targetTriple);
|
|
88
|
+
const binaryPath = packageBinaryPath(vendorRoot);
|
|
89
|
+
if (existsSync(binaryPath)) {
|
|
90
|
+
return {
|
|
91
|
+
binaryPath,
|
|
92
|
+
pathDir: path.join(packageRoot, "codex-path"),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const legacyPath = legacyBinaryPath(vendorRoot);
|
|
97
|
+
if (existsSync(legacyPath)) {
|
|
98
|
+
return {
|
|
99
|
+
binaryPath: legacyPath,
|
|
100
|
+
pathDir: path.join(packageRoot, "path"),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let nativePackage;
|
|
108
|
+
try {
|
|
109
|
+
const packageJsonPath = require.resolve(`${platformPackage}/package.json`);
|
|
110
|
+
nativePackage = resolveNativePackage(
|
|
111
|
+
path.join(path.dirname(packageJsonPath), "vendor"),
|
|
112
|
+
);
|
|
113
|
+
} catch {
|
|
114
|
+
nativePackage = resolveNativePackage(localVendorRoot);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!nativePackage) {
|
|
118
|
+
const packageManager = detectPackageManager();
|
|
119
|
+
const updateCommand =
|
|
120
|
+
packageManager === "bun"
|
|
121
|
+
? "bun install -g @hasna/codewith@latest"
|
|
122
|
+
: "npm install -g @hasna/codewith@latest";
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Missing optional dependency ${platformPackage}. Reinstall Codewith: ${updateCommand}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const { binaryPath, pathDir } = nativePackage;
|
|
129
|
+
|
|
130
|
+
// Use an asynchronous spawn instead of spawnSync so that Node is able to
|
|
131
|
+
// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
|
|
132
|
+
// executing. This allows us to forward those signals to the child process
|
|
133
|
+
// and guarantees that when either the child terminates or the parent
|
|
134
|
+
// receives a fatal signal, both processes exit in a predictable manner.
|
|
135
|
+
|
|
136
|
+
function getUpdatedPath(newDirs) {
|
|
137
|
+
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
138
|
+
const existingPath = process.env.PATH || "";
|
|
139
|
+
const updatedPath = [
|
|
140
|
+
...newDirs,
|
|
141
|
+
...existingPath.split(pathSep).filter(Boolean),
|
|
142
|
+
].join(pathSep);
|
|
143
|
+
return updatedPath;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Use heuristics to detect the package manager that was used to install
|
|
148
|
+
* Codewith in order to give the user a hint about how to update it.
|
|
149
|
+
*/
|
|
150
|
+
function detectPackageManager() {
|
|
151
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
152
|
+
if (/\bbun\//.test(userAgent)) {
|
|
153
|
+
return "bun";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const execPath = process.env.npm_execpath || "";
|
|
157
|
+
if (execPath.includes("bun")) {
|
|
158
|
+
return "bun";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (
|
|
162
|
+
__dirname.includes(".bun/install/global") ||
|
|
163
|
+
__dirname.includes(".bun\\install\\global")
|
|
164
|
+
) {
|
|
165
|
+
return "bun";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return userAgent ? "npm" : null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const additionalDirs = [];
|
|
172
|
+
if (existsSync(pathDir)) {
|
|
173
|
+
additionalDirs.push(pathDir);
|
|
174
|
+
}
|
|
175
|
+
const updatedPath = getUpdatedPath(additionalDirs);
|
|
176
|
+
|
|
177
|
+
const env = { ...process.env, PATH: updatedPath };
|
|
178
|
+
function defaultCodewithHome() {
|
|
179
|
+
const home = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
180
|
+
return path.join(home, ".codewith");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function resolveCodewithHome() {
|
|
184
|
+
return env.CODEWITH_HOME || defaultCodewithHome();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
env.CODEX_HOME = resolveCodewithHome();
|
|
188
|
+
env.CODEWITH_HOME = env.CODEX_HOME;
|
|
189
|
+
mkdirSync(env.CODEX_HOME, { recursive: true, mode: 0o700 });
|
|
190
|
+
if (process.platform !== "win32") {
|
|
191
|
+
chmodSync(env.CODEX_HOME, 0o700);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const packageManagerEnvVar =
|
|
195
|
+
detectPackageManager() === "bun"
|
|
196
|
+
? "CODEX_MANAGED_BY_BUN"
|
|
197
|
+
: "CODEX_MANAGED_BY_NPM";
|
|
198
|
+
env[packageManagerEnvVar] = "1";
|
|
199
|
+
env.CODEX_MANAGED_PACKAGE_ROOT = realpathSync(path.join(__dirname, ".."));
|
|
200
|
+
|
|
201
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
202
|
+
stdio: "inherit",
|
|
203
|
+
env,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
child.on("error", (err) => {
|
|
207
|
+
// Typically triggered when the binary is missing or not executable.
|
|
208
|
+
// Re-throwing here will terminate the parent with a non-zero exit code
|
|
209
|
+
// while still printing a helpful stack trace.
|
|
210
|
+
// eslint-disable-next-line no-console
|
|
211
|
+
console.error(err);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Forward common termination signals to the child so that it shuts down
|
|
216
|
+
// gracefully. In the handler we temporarily disable the default behavior of
|
|
217
|
+
// exiting immediately; once the child has been signaled we simply wait for
|
|
218
|
+
// its exit event which will in turn terminate the parent (see below).
|
|
219
|
+
const forwardSignal = (signal) => {
|
|
220
|
+
if (child.killed) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
child.kill(signal);
|
|
225
|
+
} catch {
|
|
226
|
+
/* ignore */
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
["SIGINT", "SIGTERM", "SIGHUP"].forEach((sig) => {
|
|
231
|
+
process.on(sig, () => forwardSignal(sig));
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// When the child exits, mirror its termination reason in the parent so that
|
|
235
|
+
// shell scripts and other tooling observe the correct exit status.
|
|
236
|
+
// Wrap the lifetime of the child process in a Promise so that we can await
|
|
237
|
+
// its termination in a structured way. The Promise resolves with an object
|
|
238
|
+
// describing how the child exited: either via exit code or due to a signal.
|
|
239
|
+
const childResult = await new Promise((resolve) => {
|
|
240
|
+
child.on("exit", (code, signal) => {
|
|
241
|
+
if (signal) {
|
|
242
|
+
resolve({ type: "signal", signal });
|
|
243
|
+
} else {
|
|
244
|
+
resolve({ type: "code", exitCode: code ?? 1 });
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (childResult.type === "signal") {
|
|
250
|
+
// Re-emit the same signal so that the parent terminates with the expected
|
|
251
|
+
// semantics (this also sets the correct exit code of 128 + n).
|
|
252
|
+
process.kill(process.pid, childResult.signal);
|
|
253
|
+
} else {
|
|
254
|
+
process.exit(childResult.exitCode);
|
|
255
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasna/codewith",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Codewith command-line coding agent from Hasna.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codewith": "bin/codex.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=16"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"registry": "https://registry.npmjs.org",
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin/codex.js"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/hasna/codewith.git",
|
|
23
|
+
"directory": "codex-cli"
|
|
24
|
+
},
|
|
25
|
+
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
|
|
26
|
+
"optionalDependencies": {
|
|
27
|
+
"@hasna/codewith-linux-x64": "0.1.2",
|
|
28
|
+
"@hasna/codewith-linux-arm64": "0.1.2",
|
|
29
|
+
"@hasna/codewith-darwin-x64": "0.1.2",
|
|
30
|
+
"@hasna/codewith-darwin-arm64": "0.1.2",
|
|
31
|
+
"@hasna/codewith-win32-x64": "0.1.2",
|
|
32
|
+
"@hasna/codewith-win32-arm64": "0.1.2"
|
|
33
|
+
}
|
|
34
|
+
}
|