@oatnil/ud 0.33.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 (4) hide show
  1. package/README.md +31 -0
  2. package/install.js +170 -0
  3. package/package.json +38 -0
  4. package/run.js +21 -0
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # @oatnil/ud
2
+
3
+ UnderControl CLI - task and expense management from the terminal.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @oatnil/ud
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ # Run directly
15
+ ud task list
16
+ ud query "status = 'todo'"
17
+
18
+ # Or use npx (no install needed)
19
+ npx @oatnil/ud task list
20
+ npx @oatnil/ud --help
21
+ ```
22
+
23
+ ## Other Installation Methods
24
+
25
+ - **Homebrew**: `brew tap oatnil-top/ud && brew install ud`
26
+ - **Direct download**: `curl -fsSL https://get.oatnil.com/ud | bash`
27
+ - **APT (Ubuntu/Debian)**: See [documentation](https://docs.oatnil.com/cli)
28
+
29
+ ## License
30
+
31
+ MIT
package/install.js ADDED
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+
3
+ const https = require("https");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const { execSync } = require("child_process");
7
+ const zlib = require("zlib");
8
+
9
+ const CDN_BASE =
10
+ "https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/cli/releases";
11
+
12
+ const PLATFORM_MAP = {
13
+ darwin: "darwin",
14
+ linux: "linux",
15
+ win32: "windows",
16
+ };
17
+
18
+ const ARCH_MAP = {
19
+ x64: "amd64",
20
+ arm64: "arm64",
21
+ };
22
+
23
+ function getBinaryName(version, goos, goarch) {
24
+ const name = `ud_${version}_${goos}_${goarch}`;
25
+ return goos === "windows" ? `${name}.exe` : name;
26
+ }
27
+
28
+ function getArchiveName(version, goos, goarch) {
29
+ const base = `ud_${version}_${goos}_${goarch}`;
30
+ return goos === "windows" ? `${base}.zip` : `${base}.tar.gz`;
31
+ }
32
+
33
+ function fetch(url) {
34
+ return new Promise((resolve, reject) => {
35
+ https
36
+ .get(url, (res) => {
37
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
38
+ return fetch(res.headers.location).then(resolve, reject);
39
+ }
40
+ if (res.statusCode !== 200) {
41
+ return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
42
+ }
43
+ const chunks = [];
44
+ res.on("data", (chunk) => chunks.push(chunk));
45
+ res.on("end", () => resolve(Buffer.concat(chunks)));
46
+ res.on("error", reject);
47
+ })
48
+ .on("error", reject);
49
+ });
50
+ }
51
+
52
+ function extractTarGz(buffer, binaryName, destPath) {
53
+ // tar.gz: use tar command for simplicity
54
+ const tmpDir = path.join(path.dirname(destPath), ".ud-tmp");
55
+ fs.mkdirSync(tmpDir, { recursive: true });
56
+
57
+ const archivePath = path.join(tmpDir, "archive.tar.gz");
58
+ fs.writeFileSync(archivePath, buffer);
59
+
60
+ try {
61
+ execSync(`tar -xzf "${archivePath}" -C "${tmpDir}"`, { stdio: "pipe" });
62
+
63
+ const extractedBinary = path.join(tmpDir, binaryName);
64
+ if (!fs.existsSync(extractedBinary)) {
65
+ // try finding any file in tmpDir
66
+ const files = fs.readdirSync(tmpDir).filter((f) => f !== "archive.tar.gz");
67
+ if (files.length === 0) {
68
+ throw new Error(`No binary found in archive`);
69
+ }
70
+ fs.renameSync(path.join(tmpDir, files[0]), destPath);
71
+ } else {
72
+ fs.renameSync(extractedBinary, destPath);
73
+ }
74
+ } finally {
75
+ fs.rmSync(tmpDir, { recursive: true, force: true });
76
+ }
77
+ }
78
+
79
+ function extractZip(buffer, binaryName, destPath) {
80
+ const tmpDir = path.join(path.dirname(destPath), ".ud-tmp");
81
+ fs.mkdirSync(tmpDir, { recursive: true });
82
+
83
+ const archivePath = path.join(tmpDir, "archive.zip");
84
+ fs.writeFileSync(archivePath, buffer);
85
+
86
+ try {
87
+ // Use PowerShell on Windows, unzip on others
88
+ if (process.platform === "win32") {
89
+ execSync(
90
+ `powershell -command "Expand-Archive -Path '${archivePath}' -DestinationPath '${tmpDir}'"`,
91
+ { stdio: "pipe" }
92
+ );
93
+ } else {
94
+ execSync(`unzip -o "${archivePath}" -d "${tmpDir}"`, { stdio: "pipe" });
95
+ }
96
+
97
+ const extractedBinary = path.join(tmpDir, binaryName);
98
+ if (!fs.existsSync(extractedBinary)) {
99
+ const files = fs
100
+ .readdirSync(tmpDir)
101
+ .filter((f) => f !== "archive.zip");
102
+ if (files.length === 0) {
103
+ throw new Error(`No binary found in archive`);
104
+ }
105
+ fs.renameSync(path.join(tmpDir, files[0]), destPath);
106
+ } else {
107
+ fs.renameSync(extractedBinary, destPath);
108
+ }
109
+ } finally {
110
+ fs.rmSync(tmpDir, { recursive: true, force: true });
111
+ }
112
+ }
113
+
114
+ async function main() {
115
+ const pkg = require("./package.json");
116
+ const version = pkg.version;
117
+
118
+ const goos = PLATFORM_MAP[process.platform];
119
+ const goarch = ARCH_MAP[process.arch];
120
+
121
+ if (!goos) {
122
+ console.error(`Unsupported platform: ${process.platform}`);
123
+ process.exit(1);
124
+ }
125
+ if (!goarch) {
126
+ console.error(`Unsupported architecture: ${process.arch}`);
127
+ process.exit(1);
128
+ }
129
+
130
+ const binDir = path.join(__dirname, "bin");
131
+ fs.mkdirSync(binDir, { recursive: true });
132
+
133
+ const binaryExt = goos === "windows" ? ".exe" : "";
134
+ const destPath = path.join(binDir, `ud${binaryExt}`);
135
+
136
+ // Skip if binary already exists
137
+ if (fs.existsSync(destPath)) {
138
+ console.log(`ud binary already exists at ${destPath}`);
139
+ return;
140
+ }
141
+
142
+ const archiveName = getArchiveName(version, goos, goarch);
143
+ const binaryName = getBinaryName(version, goos, goarch);
144
+ const url = `${CDN_BASE}/${version}/${archiveName}`;
145
+
146
+ console.log(`Downloading ud v${version} for ${goos}/${goarch}...`);
147
+ console.log(` ${url}`);
148
+
149
+ try {
150
+ const buffer = await fetch(url);
151
+
152
+ if (archiveName.endsWith(".zip")) {
153
+ extractZip(buffer, binaryName, destPath);
154
+ } else {
155
+ extractTarGz(buffer, binaryName, destPath);
156
+ }
157
+
158
+ // Make executable
159
+ if (goos !== "windows") {
160
+ fs.chmodSync(destPath, 0o755);
161
+ }
162
+
163
+ console.log(`ud v${version} installed successfully!`);
164
+ } catch (err) {
165
+ console.error(`Failed to install ud: ${err.message}`);
166
+ process.exit(1);
167
+ }
168
+ }
169
+
170
+ main();
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@oatnil/ud",
3
+ "version": "0.33.0",
4
+ "description": "UnderControl CLI - task and expense management from the terminal",
5
+ "homepage": "https://github.com/oatnil-top/ud",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/oatnil-top/ud.git"
9
+ },
10
+ "license": "MIT",
11
+ "bin": {
12
+ "ud": "run.js"
13
+ },
14
+ "scripts": {
15
+ "postinstall": "node install.js"
16
+ },
17
+ "keywords": [
18
+ "cli",
19
+ "task",
20
+ "undercontrol",
21
+ "tui",
22
+ "productivity"
23
+ ],
24
+ "os": [
25
+ "darwin",
26
+ "linux",
27
+ "win32"
28
+ ],
29
+ "cpu": [
30
+ "x64",
31
+ "arm64"
32
+ ],
33
+ "files": [
34
+ "install.js",
35
+ "run.js",
36
+ "README.md"
37
+ ]
38
+ }
package/run.js ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execFileSync } = require("child_process");
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+
7
+ const ext = process.platform === "win32" ? ".exe" : "";
8
+ const binPath = path.join(__dirname, "bin", `ud${ext}`);
9
+
10
+ if (!fs.existsSync(binPath)) {
11
+ console.error(
12
+ "ud binary not found. Try reinstalling: npm install -g @oatnil/ud"
13
+ );
14
+ process.exit(1);
15
+ }
16
+
17
+ try {
18
+ execFileSync(binPath, process.argv.slice(2), { stdio: "inherit" });
19
+ } catch (err) {
20
+ process.exit(err.status || 1);
21
+ }