@tamfitronics/tamcoder 0.0.0-stage → 0.1.89
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 +29 -2
- package/bin/tamcoder.mjs +23 -0
- package/index.mjs +1 -0
- package/install.mjs +150 -0
- package/package.json +35 -4
package/README.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tamfitronics/tamcoder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Tamcoder – TypeScript AI coding-agent runtime, packaged for npm.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @tamfitronics/tamcoder
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
tamcoder --version
|
|
15
|
+
tamcoder --help
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## How it works
|
|
19
|
+
|
|
20
|
+
This npm package contains a small installer. On `npm install` it downloads the
|
|
21
|
+
official TypeScript release from `https://gpt.tamfitronics.com/tamcoder`,
|
|
22
|
+
verifies its SHA-256 checksum, and extracts it into
|
|
23
|
+
`~/.local/share/tamcoder/releases/<version>`. The `tamcoder` and `tamcoder-bin`
|
|
24
|
+
binaries are symlinked into `~/.local/bin`.
|
|
25
|
+
|
|
26
|
+
## Environment variables
|
|
27
|
+
|
|
28
|
+
- `TAMCODER_RELEASE_BASE` – override the download URL base
|
|
29
|
+
- `TAMCODER_INSTALL_PREFIX` – default `~/.local`
|
|
30
|
+
- `TAMCODER_LOCAL_RELEASE_ROOT` – install from a local directory instead of the network
|
package/bin/tamcoder.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { spawnSync } from 'child_process';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
|
|
8
|
+
const version = '0.1.10-ts';
|
|
9
|
+
const prefix = process.env.TAMCODER_INSTALL_PREFIX || join(homedir(), '.local');
|
|
10
|
+
const releaseDir = join(prefix, 'share', 'tamcoder', 'releases', version);
|
|
11
|
+
const binary = join(releaseDir, 'app', 'bin', 'tamcoder');
|
|
12
|
+
|
|
13
|
+
if (!existsSync(binary)) {
|
|
14
|
+
console.error('tamcoder: engine not installed. Run "npm install" in the package directory, or reinstall @tamfitronics/tamcoder.');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = spawnSync(binary, process.argv.slice(2), {
|
|
19
|
+
stdio: 'inherit',
|
|
20
|
+
shell: false,
|
|
21
|
+
env: process.env
|
|
22
|
+
});
|
|
23
|
+
process.exit(result.status ?? 1);
|
package/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { install as default, verify } from './install.mjs';
|
package/install.mjs
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createWriteStream, existsSync, mkdirSync, readFileSync, symlinkSync, unlinkSync, chmodSync, writeFileSync } from 'fs';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { dirname, join, resolve } from 'path';
|
|
6
|
+
import { Readable } from 'stream';
|
|
7
|
+
import { finished } from 'stream/promises';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8'));
|
|
12
|
+
const version = pkg.version; // npm package version (metadata only)
|
|
13
|
+
const platform = 'linux-x64';
|
|
14
|
+
let resolvedAsset = null;
|
|
15
|
+
const base = process.env.TAMCODER_RELEASE_BASE || 'https://gpt.tamfitronics.com/tamcoder';
|
|
16
|
+
const prefix = process.env.TAMCODER_INSTALL_PREFIX || join(homedir(), '.local');
|
|
17
|
+
const releaseDir = join(prefix, 'share', 'tamcoder', 'releases', version);
|
|
18
|
+
const binDir = join(prefix, 'bin');
|
|
19
|
+
const localReleaseRoot = process.env.TAMCODER_LOCAL_RELEASE_ROOT;
|
|
20
|
+
|
|
21
|
+
async function resolveAsset() {
|
|
22
|
+
if (resolvedAsset) return resolvedAsset;
|
|
23
|
+
if (localReleaseRoot) {
|
|
24
|
+
const latest = JSON.parse(await fetchText(`${base}/latest.json`));
|
|
25
|
+
resolvedAsset = latest.asset || `tamcoder-${latest.version}-${platform}.tar.gz`;
|
|
26
|
+
return resolvedAsset;
|
|
27
|
+
}
|
|
28
|
+
const latest = JSON.parse(await fetchText(`${base}/latest.json`));
|
|
29
|
+
resolvedAsset = latest.asset || `tamcoder-${latest.version}-${platform}.tar.gz`;
|
|
30
|
+
return resolvedAsset;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function sha256(path) {
|
|
34
|
+
return createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function fetchText(url) {
|
|
38
|
+
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
|
39
|
+
if (!res.ok) throw new Error(`fetch ${url}: ${res.status} ${res.statusText}`);
|
|
40
|
+
return res.text();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function fetchBinary(url, dest) {
|
|
44
|
+
const res = await fetch(url);
|
|
45
|
+
if (!res.ok) throw new Error(`fetch ${url}: ${res.status} ${res.statusText}`);
|
|
46
|
+
const out = createWriteStream(dest);
|
|
47
|
+
await finished(Readable.fromWeb(res.body).pipe(out));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ensureDir(dir) {
|
|
51
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function linkBin(name) {
|
|
55
|
+
const src = join(releaseDir, 'app', 'bin', 'tamcoder');
|
|
56
|
+
const dst = join(binDir, name);
|
|
57
|
+
ensureDir(binDir);
|
|
58
|
+
if (existsSync(dst)) unlinkSync(dst);
|
|
59
|
+
symlinkSync(src, dst);
|
|
60
|
+
chmodSync(src, 0o755);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function installFromLocal() {
|
|
64
|
+
const dir = localReleaseRoot;
|
|
65
|
+
if (!existsSync(dir)) throw new Error(`TAMCODER_LOCAL_RELEASE_ROOT not found: ${dir}`);
|
|
66
|
+
const asset = await resolveAsset();
|
|
67
|
+
const tarball = join(dir, asset);
|
|
68
|
+
const checksum = join(dir, `${asset}.sha256`);
|
|
69
|
+
if (!existsSync(tarball)) throw new Error(`local asset missing: ${tarball}`);
|
|
70
|
+
ensureDir(releaseDir);
|
|
71
|
+
await import('child_process').then(({ execFileSync }) => {
|
|
72
|
+
execFileSync('tar', ['-xzf', tarball, '-C', releaseDir], { stdio: 'inherit' });
|
|
73
|
+
});
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function installFromNetwork() {
|
|
78
|
+
const tmp = join(homedir(), '.cache', 'tamcoder', 'downloads', version);
|
|
79
|
+
ensureDir(tmp);
|
|
80
|
+
const asset = await resolveAsset();
|
|
81
|
+
const tarball = join(tmp, asset);
|
|
82
|
+
|
|
83
|
+
const shaUrl = `${base}/releases/${asset}.sha256`;
|
|
84
|
+
const binUrl = `${base}/releases/${asset}`;
|
|
85
|
+
|
|
86
|
+
const expected = (await fetchText(shaUrl)).trim().split(/\s+/)[0];
|
|
87
|
+
if (!/^[a-f0-9]{64}$/i.test(expected)) throw new Error('invalid sha256 from server');
|
|
88
|
+
|
|
89
|
+
await fetchBinary(binUrl, tarball);
|
|
90
|
+
const actual = sha256(tarball);
|
|
91
|
+
if (actual !== expected) throw new Error(`checksum mismatch: expected ${expected}, got ${actual}`);
|
|
92
|
+
|
|
93
|
+
ensureDir(releaseDir);
|
|
94
|
+
await import('child_process').then(({ execFileSync }) => {
|
|
95
|
+
execFileSync('tar', ['-xzf', tarball, '-C', releaseDir], { stdio: 'inherit' });
|
|
96
|
+
});
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function ensureArtifacts() {
|
|
101
|
+
// Matches install.sh behavior for shared user artifact directory.
|
|
102
|
+
const artifactRoot = '/mnt/tamcoder/users';
|
|
103
|
+
const artifactBase = dirname(artifactRoot);
|
|
104
|
+
const { execFileSync } = await import('child_process');
|
|
105
|
+
for (const [dir, mode] of [[artifactBase, '0755'], [artifactRoot, '1777']]) {
|
|
106
|
+
if (!existsSync(dir)) {
|
|
107
|
+
try { execFileSync('install', ['-d', '-m', mode, dir], { stdio: 'ignore' }); }
|
|
108
|
+
catch { /* user may manage this separately */ }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function install() {
|
|
114
|
+
if (existsSync(join(releaseDir, 'app', 'bin', 'tamcoder'))) {
|
|
115
|
+
console.log(`Tamcoder ${version} already installed at ${releaseDir}`);
|
|
116
|
+
linkBin('tamcoder');
|
|
117
|
+
linkBin('tamcoder-bin');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (localReleaseRoot) {
|
|
122
|
+
await installFromLocal();
|
|
123
|
+
} else {
|
|
124
|
+
await installFromNetwork();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!existsSync(join(releaseDir, 'app', 'bin', 'tamcoder'))) {
|
|
128
|
+
throw new Error('extracted asset is missing app/bin/tamcoder');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
linkBin('tamcoder');
|
|
132
|
+
linkBin('tamcoder-bin');
|
|
133
|
+
await ensureArtifacts();
|
|
134
|
+
console.log(`Installed Tamcoder ${version} to ${binDir}/tamcoder`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function verify() {
|
|
138
|
+
// No strict version check: npm package version is metadata only.
|
|
139
|
+
// Ensure the remote asset can be fetched.
|
|
140
|
+
const latest = JSON.parse(await fetchText(`${base}/latest.json`));
|
|
141
|
+
const remoteAsset = latest.asset || `tamcoder-${latest.version}-${platform}.tar.gz`;
|
|
142
|
+
const shaUrl = `${base}/releases/${remoteAsset}.sha256`;
|
|
143
|
+
// Verify checksum endpoint exists (404 will throw)
|
|
144
|
+
await fetchText(shaUrl);
|
|
145
|
+
console.log(`Verified remote asset ${remoteAsset}`);
|
|
146
|
+
}
|
|
147
|
+
install().catch(err => {
|
|
148
|
+
console.error(err.message);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tamfitronics/tamcoder",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.89",
|
|
4
|
+
"description": "Tamcoder – TypeScript AI coding-agent runtime",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.0.0"
|
|
9
|
+
},
|
|
10
|
+
"main": "index.mjs",
|
|
11
|
+
"bin": {
|
|
12
|
+
"tamcoder": "bin/tamcoder.mjs",
|
|
13
|
+
"tamcoder-bin": "bin/tamcoder.mjs"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"index.mjs",
|
|
17
|
+
"bin/",
|
|
18
|
+
"install.mjs",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"postinstall": "node install.mjs",
|
|
24
|
+
"prepublishOnly": "node -e \"import('./install.mjs').then(m=>m.verify())\""
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/tamfitronics/tamcoder.git"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://tamfitronics.com/tamcoder",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/tamfitronics/tamcoder/issues"
|
|
36
|
+
}
|
|
37
|
+
}
|