@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 CHANGED
@@ -1,3 +1,30 @@
1
- # Temporary Holding Version
1
+ # @tamfitronics/tamcoder
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }