ultrahope 0.0.1

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 (3) hide show
  1. package/README.md +62 -0
  2. package/dist/index.js +225 -0
  3. package/package.json +35 -0
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # ultrahope
2
+
3
+ LLM-powered development workflow assistant CLI.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g ultrahope
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Login
14
+
15
+ Authenticate with your ultrahope account using device flow:
16
+
17
+ ```bash
18
+ ultrahope login
19
+ ```
20
+
21
+ This will display a URL and code. Open the URL in your browser, sign in, and enter the code to authorize the CLI.
22
+
23
+ ### Translate
24
+
25
+ Translate input to various formats. Pipe content to the command:
26
+
27
+ ```bash
28
+ # Generate a commit message from git diff
29
+ git diff --staged | ultrahope translate --target vcs-commit-message
30
+
31
+ # Generate PR title and body from diff
32
+ git diff main | ultrahope translate --target pr-title-body
33
+
34
+ # Analyze PR intent
35
+ git diff main | ultrahope translate --target pr-intent
36
+ ```
37
+
38
+ #### Targets
39
+
40
+ - `vcs-commit-message` - Generate a commit message
41
+ - `pr-title-body` - Generate PR title and body
42
+ - `pr-intent` - Analyze the intent of changes
43
+
44
+ ## Configuration
45
+
46
+ ### Environment Variables
47
+
48
+ - `ULTRAHOPE_API_URL` - API endpoint (default: `https://ultrahope.dev`)
49
+
50
+ ### Credentials
51
+
52
+ Credentials are stored in `~/.config/ultrahope/credentials.json`.
53
+
54
+ ## Development
55
+
56
+ ```bash
57
+ # Build
58
+ pnpm run build
59
+
60
+ # Link for local testing
61
+ pnpm link --global
62
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,225 @@
1
+ // src/lib/api-client.ts
2
+ var API_BASE_URL = process.env.ULTRAHOPE_API_URL ?? "https://ultrahope.dev";
3
+ var InsufficientBalanceError = class extends Error {
4
+ constructor(balance) {
5
+ super("Token balance exhausted");
6
+ this.balance = balance;
7
+ this.name = "InsufficientBalanceError";
8
+ }
9
+ };
10
+ function createApiClient(token) {
11
+ const headers = {
12
+ "Content-Type": "application/json"
13
+ };
14
+ if (token) {
15
+ headers.Authorization = `Bearer ${token}`;
16
+ }
17
+ return {
18
+ async translate(req) {
19
+ const res = await fetch(`${API_BASE_URL}/api/v1/translate`, {
20
+ method: "POST",
21
+ headers,
22
+ body: JSON.stringify(req)
23
+ });
24
+ if (!res.ok) {
25
+ if (res.status === 402) {
26
+ const data = await res.json();
27
+ throw new InsufficientBalanceError(data.balance ?? 0);
28
+ }
29
+ const text = await res.text();
30
+ throw new Error(`API error: ${res.status} ${text}`);
31
+ }
32
+ return res.json();
33
+ },
34
+ async requestDeviceCode() {
35
+ const res = await fetch(`${API_BASE_URL}/api/auth/device/code`, {
36
+ method: "POST",
37
+ headers,
38
+ body: JSON.stringify({ client_id: "ultrahope-cli" })
39
+ });
40
+ if (!res.ok) {
41
+ const text = await res.text();
42
+ throw new Error(`API error: ${res.status} ${text}`);
43
+ }
44
+ return res.json();
45
+ },
46
+ async pollDeviceToken(deviceCode) {
47
+ const res = await fetch(`${API_BASE_URL}/api/auth/device/token`, {
48
+ method: "POST",
49
+ headers,
50
+ body: JSON.stringify({
51
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
52
+ device_code: deviceCode,
53
+ client_id: "ultrahope-cli"
54
+ })
55
+ });
56
+ if (!res.ok && res.status !== 400) {
57
+ const text = await res.text();
58
+ throw new Error(`API error: ${res.status} ${text}`);
59
+ }
60
+ return res.json();
61
+ }
62
+ };
63
+ }
64
+
65
+ // src/lib/auth.ts
66
+ import * as fs from "fs";
67
+ import * as os from "os";
68
+ import * as path from "path";
69
+ function getCredentialsPath() {
70
+ const configDir = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
71
+ return path.join(configDir, "ultrahope", "credentials.json");
72
+ }
73
+ async function getToken() {
74
+ const credPath = getCredentialsPath();
75
+ try {
76
+ const content = await fs.promises.readFile(credPath, "utf-8");
77
+ const creds = JSON.parse(content);
78
+ return creds.access_token ?? null;
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+ async function saveToken(token) {
84
+ const credPath = getCredentialsPath();
85
+ const dir = path.dirname(credPath);
86
+ await fs.promises.mkdir(dir, { recursive: true });
87
+ await fs.promises.writeFile(
88
+ credPath,
89
+ JSON.stringify({ access_token: token }, null, 2),
90
+ { mode: 384 }
91
+ );
92
+ }
93
+
94
+ // src/commands/login.ts
95
+ async function login(_args) {
96
+ const api = createApiClient();
97
+ console.log("Requesting device code...");
98
+ const deviceCode = await api.requestDeviceCode();
99
+ console.log();
100
+ console.log(`Open this URL in your browser: ${deviceCode.verification_uri}`);
101
+ console.log(`Enter code: ${deviceCode.user_code}`);
102
+ console.log();
103
+ console.log("Waiting for authorization...");
104
+ const token = await pollForToken(
105
+ api,
106
+ deviceCode.device_code,
107
+ deviceCode.interval,
108
+ deviceCode.expires_in
109
+ );
110
+ await saveToken(token);
111
+ console.log("Successfully authenticated!");
112
+ }
113
+ async function pollForToken(api, deviceCode, interval, expiresIn) {
114
+ const deadline = Date.now() + expiresIn * 1e3;
115
+ while (Date.now() < deadline) {
116
+ await sleep(interval * 1e3);
117
+ const result = await api.pollDeviceToken(deviceCode);
118
+ if (result.access_token) {
119
+ return result.access_token;
120
+ }
121
+ if (result.error && result.error !== "authorization_pending") {
122
+ throw new Error(`Authentication failed: ${result.error}`);
123
+ }
124
+ }
125
+ throw new Error("Authentication timed out");
126
+ }
127
+ function sleep(ms) {
128
+ return new Promise((resolve) => setTimeout(resolve, ms));
129
+ }
130
+
131
+ // src/lib/stdin.ts
132
+ async function stdin() {
133
+ const chunks = [];
134
+ for await (const chunk of process.stdin) {
135
+ chunks.push(chunk);
136
+ }
137
+ return Buffer.concat(chunks).toString("utf-8");
138
+ }
139
+
140
+ // src/commands/translate.ts
141
+ var VALID_TARGETS = [
142
+ "vcs-commit-message",
143
+ "pr-title-body",
144
+ "pr-intent"
145
+ ];
146
+ async function translate(args2) {
147
+ const target = parseTarget(args2);
148
+ const input = await stdin();
149
+ if (!input.trim()) {
150
+ console.error(
151
+ "Error: No input provided. Pipe content to ultrahope translate."
152
+ );
153
+ process.exit(1);
154
+ }
155
+ const token = await getToken();
156
+ if (!token) {
157
+ console.error("Error: Not authenticated. Run `ultrahope login` first.");
158
+ process.exit(1);
159
+ }
160
+ const api = createApiClient(token);
161
+ try {
162
+ const result = await api.translate({ input, target });
163
+ console.log(result.output);
164
+ } catch (error) {
165
+ if (error instanceof InsufficientBalanceError) {
166
+ console.error(
167
+ "Error: Token balance exhausted. Upgrade your plan at https://ultrahope.dev/pricing"
168
+ );
169
+ process.exit(1);
170
+ }
171
+ throw error;
172
+ }
173
+ }
174
+ function parseTarget(args2) {
175
+ const idx = args2.findIndex((a) => a === "--target" || a === "-t");
176
+ if (idx === -1 || !args2[idx + 1]) {
177
+ console.error("Error: Missing --target option");
178
+ console.error(
179
+ "Usage: ultrahope translate --target <vcs-commit-message|pr-title-body|pr-intent>"
180
+ );
181
+ process.exit(1);
182
+ }
183
+ const value = args2[idx + 1];
184
+ if (!VALID_TARGETS.includes(value)) {
185
+ console.error(`Error: Invalid target "${value}"`);
186
+ console.error(`Valid targets: ${VALID_TARGETS.join(", ")}`);
187
+ process.exit(1);
188
+ }
189
+ return value;
190
+ }
191
+
192
+ // src/index.ts
193
+ var [command, ...args] = process.argv.slice(2);
194
+ async function main() {
195
+ switch (command) {
196
+ case "translate":
197
+ await translate(args);
198
+ break;
199
+ case "login":
200
+ await login(args);
201
+ break;
202
+ case "--help":
203
+ case "-h":
204
+ case void 0:
205
+ printHelp();
206
+ break;
207
+ default:
208
+ console.error(`Unknown command: ${command}`);
209
+ process.exit(1);
210
+ }
211
+ }
212
+ function printHelp() {
213
+ console.log(`Usage: ultrahope <command>
214
+
215
+ Commands:
216
+ translate Translate input to various formats
217
+ login Authenticate with device flow
218
+
219
+ Options:
220
+ --help, -h Show this help message`);
221
+ }
222
+ main().catch((err) => {
223
+ console.error(err);
224
+ process.exit(1);
225
+ });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "ultrahope",
3
+ "version": "0.0.1",
4
+ "description": "LLM-powered development workflow assistant",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/toyamarinyon/ultrahope.git"
10
+ },
11
+ "keywords": [
12
+ "cli",
13
+ "llm",
14
+ "ai",
15
+ "git",
16
+ "commit-message",
17
+ "pull-request"
18
+ ],
19
+ "bin": {
20
+ "ultrahope": "dist/index.js"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "typecheck": "tsc --noEmit",
28
+ "prepublishOnly": "pnpm run build"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.15.29",
32
+ "tsup": "8.5.0",
33
+ "typescript": "^5.8.3"
34
+ }
35
+ }