autohand-cli 0.7.4 → 0.7.6
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/LICENSE +0 -43
- package/README.md +3 -1
- package/dist/SessionManager-M5ZLCLCW.cjs +9 -0
- package/dist/{SessionManager-IMW2HGR3.js → SessionManager-XDBEQUPG.js} +1 -1
- package/dist/chunk-2W3QTBNG.cjs +151 -0
- package/dist/{chunk-546JQ3ND.js → chunk-4KZCGK7D.js} +4 -101
- package/dist/chunk-5MCDN53U.js +102 -0
- package/dist/{chunk-UPR5PKX4.cjs → chunk-67NJKV5A.cjs} +5 -1
- package/dist/chunk-723DZKBU.js +613 -0
- package/dist/chunk-JYTXG6OV.cjs +102 -0
- package/dist/chunk-K6NBYSME.cjs +613 -0
- package/dist/{chunk-XJZYEURA.cjs → chunk-KJ67C72C.cjs} +6 -103
- package/dist/chunk-R5KNHJ27.js +151 -0
- package/dist/{chunk-2FLBGPE3.js → chunk-VO3JKFUH.js} +5 -1
- package/dist/index.cjs +1979 -585
- package/dist/index.js +1941 -547
- package/dist/resume-ANISKRWL.cjs +9 -0
- package/dist/{resume-2NERFSTD.js → resume-CWYAK6XR.js} +2 -1
- package/dist/share-3PSV53CQ.js +10 -0
- package/dist/share-4ACH6626.cjs +10 -0
- package/dist/status-4U5CPUVT.cjs +9 -0
- package/dist/{status-UT4UQN3H.js → status-GPAZ67ZZ.js} +2 -1
- package/package.json +3 -2
- package/dist/SessionManager-VZNWGX4O.cjs +0 -9
- package/dist/chunk-55DQY6B5.js +0 -49
- package/dist/chunk-RYY5I7QN.cjs +0 -49
- package/dist/resume-OYZMJRNO.cjs +0 -8
- package/dist/status-U5NH6SYY.cjs +0 -8
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
import {
|
|
2
|
+
package_default
|
|
3
|
+
} from "./chunk-5MCDN53U.js";
|
|
4
|
+
import {
|
|
5
|
+
AUTOHAND_PATHS
|
|
6
|
+
} from "./chunk-FUEL6BK7.js";
|
|
7
|
+
|
|
8
|
+
// src/commands/share.ts
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import enquirer from "enquirer";
|
|
11
|
+
import ora from "ora";
|
|
12
|
+
|
|
13
|
+
// src/share/costEstimator.ts
|
|
14
|
+
var DEFAULT_COST_PER_1K_TOKENS = 3e-3;
|
|
15
|
+
function estimateCost(totalTokens) {
|
|
16
|
+
if (totalTokens <= 0) return 0;
|
|
17
|
+
const cost = totalTokens / 1e3 * DEFAULT_COST_PER_1K_TOKENS;
|
|
18
|
+
return Math.round(cost * 1e4) / 1e4;
|
|
19
|
+
}
|
|
20
|
+
function formatCost(cost) {
|
|
21
|
+
if (cost < 0.01) {
|
|
22
|
+
if (cost === 0) return "$0.00";
|
|
23
|
+
return `$${cost.toFixed(4)}`;
|
|
24
|
+
}
|
|
25
|
+
return `$${cost.toFixed(2)}`;
|
|
26
|
+
}
|
|
27
|
+
function formatTokens(tokens) {
|
|
28
|
+
if (tokens >= 1e6) {
|
|
29
|
+
return `${(tokens / 1e6).toFixed(1)}M`;
|
|
30
|
+
}
|
|
31
|
+
if (tokens >= 1e3) {
|
|
32
|
+
return `${(tokens / 1e3).toFixed(1)}K`;
|
|
33
|
+
}
|
|
34
|
+
return tokens.toString();
|
|
35
|
+
}
|
|
36
|
+
function formatDuration(seconds) {
|
|
37
|
+
if (seconds < 60) {
|
|
38
|
+
return `${seconds}s`;
|
|
39
|
+
}
|
|
40
|
+
const minutes = Math.floor(seconds / 60);
|
|
41
|
+
const remainingSeconds = seconds % 60;
|
|
42
|
+
if (minutes < 60) {
|
|
43
|
+
if (remainingSeconds === 0) return `${minutes}m`;
|
|
44
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
45
|
+
}
|
|
46
|
+
const hours = Math.floor(minutes / 60);
|
|
47
|
+
const remainingMinutes = minutes % 60;
|
|
48
|
+
if (remainingMinutes === 0) return `${hours}h`;
|
|
49
|
+
return `${hours}h ${remainingMinutes}m`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/share/sessionSerializer.ts
|
|
53
|
+
function extractToolUsage(messages) {
|
|
54
|
+
const toolCounts = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const message of messages) {
|
|
56
|
+
if (message.role === "assistant" && message.toolCalls) {
|
|
57
|
+
for (const toolCall of message.toolCalls) {
|
|
58
|
+
const toolName = toolCall.function?.name || toolCall.name || "unknown";
|
|
59
|
+
const current = toolCounts.get(toolName) || { total: 0, success: 0 };
|
|
60
|
+
current.total++;
|
|
61
|
+
toolCounts.set(toolName, current);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (message.role === "tool" && message.name) {
|
|
65
|
+
const current = toolCounts.get(message.name);
|
|
66
|
+
if (current) {
|
|
67
|
+
const isError = message.content.toLowerCase().includes("error") || message.content.toLowerCase().includes("failed");
|
|
68
|
+
if (!isError) {
|
|
69
|
+
current.success++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const result = [];
|
|
75
|
+
for (const [name, counts] of toolCounts) {
|
|
76
|
+
result.push({
|
|
77
|
+
name,
|
|
78
|
+
count: counts.total,
|
|
79
|
+
successRate: counts.total > 0 ? counts.success / counts.total : void 0
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return result.sort((a, b) => b.count - a.count);
|
|
83
|
+
}
|
|
84
|
+
function estimateTokensFromMessages(messages) {
|
|
85
|
+
let input = 0;
|
|
86
|
+
let output = 0;
|
|
87
|
+
for (const message of messages) {
|
|
88
|
+
const chars = message.content.length;
|
|
89
|
+
const tokens = Math.ceil(chars / 4);
|
|
90
|
+
if (message.role === "user" || message.role === "system") {
|
|
91
|
+
input += tokens;
|
|
92
|
+
} else {
|
|
93
|
+
output += tokens;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
total: input + output,
|
|
98
|
+
input,
|
|
99
|
+
output
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function parseGitDiff(diffContent) {
|
|
103
|
+
if (!diffContent || diffContent.trim().length === 0) {
|
|
104
|
+
return void 0;
|
|
105
|
+
}
|
|
106
|
+
const lines = diffContent.split("\n");
|
|
107
|
+
const filesChanged = /* @__PURE__ */ new Set();
|
|
108
|
+
let linesAdded = 0;
|
|
109
|
+
let linesRemoved = 0;
|
|
110
|
+
for (const line of lines) {
|
|
111
|
+
if (line.startsWith("diff --git")) {
|
|
112
|
+
const match = line.match(/diff --git a\/(.+) b\/(.+)/);
|
|
113
|
+
if (match) {
|
|
114
|
+
filesChanged.add(match[2]);
|
|
115
|
+
}
|
|
116
|
+
} else if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
117
|
+
linesAdded++;
|
|
118
|
+
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
119
|
+
linesRemoved++;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (filesChanged.size === 0 && linesAdded === 0 && linesRemoved === 0) {
|
|
123
|
+
return void 0;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
filesChanged: Array.from(filesChanged),
|
|
127
|
+
linesAdded,
|
|
128
|
+
linesRemoved,
|
|
129
|
+
diffContent
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function calculateDuration(startedAt, endedAt) {
|
|
133
|
+
const start = new Date(startedAt).getTime();
|
|
134
|
+
const end = new Date(endedAt).getTime();
|
|
135
|
+
return Math.max(0, Math.floor((end - start) / 1e3));
|
|
136
|
+
}
|
|
137
|
+
function serializeSession(session, options) {
|
|
138
|
+
const messages = session.getMessages();
|
|
139
|
+
const { metadata: metadata2 } = session;
|
|
140
|
+
const endedAt = metadata2.closedAt || (/* @__PURE__ */ new Date()).toISOString();
|
|
141
|
+
const durationSeconds = calculateDuration(metadata2.createdAt, endedAt);
|
|
142
|
+
const sessionMetadata = {
|
|
143
|
+
sessionId: metadata2.sessionId,
|
|
144
|
+
projectName: metadata2.projectName,
|
|
145
|
+
model: options.model || metadata2.model,
|
|
146
|
+
provider: options.provider || "openrouter",
|
|
147
|
+
startedAt: metadata2.createdAt,
|
|
148
|
+
endedAt,
|
|
149
|
+
durationSeconds,
|
|
150
|
+
messageCount: messages.length,
|
|
151
|
+
status: metadata2.status,
|
|
152
|
+
summary: metadata2.summary
|
|
153
|
+
};
|
|
154
|
+
let usage;
|
|
155
|
+
if (options.totalTokens && options.totalTokens > 0) {
|
|
156
|
+
const inputTokens = Math.floor(options.totalTokens * 0.3);
|
|
157
|
+
const outputTokens = options.totalTokens - inputTokens;
|
|
158
|
+
usage = {
|
|
159
|
+
totalTokens: options.totalTokens,
|
|
160
|
+
inputTokens,
|
|
161
|
+
outputTokens,
|
|
162
|
+
estimatedCost: estimateCost(options.totalTokens)
|
|
163
|
+
};
|
|
164
|
+
} else {
|
|
165
|
+
const estimated = estimateTokensFromMessages(messages);
|
|
166
|
+
usage = {
|
|
167
|
+
totalTokens: estimated.total,
|
|
168
|
+
inputTokens: estimated.input,
|
|
169
|
+
outputTokens: estimated.output,
|
|
170
|
+
estimatedCost: estimateCost(estimated.total)
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const toolUsage = extractToolUsage(messages);
|
|
174
|
+
const gitDiff = options.gitDiff ? parseGitDiff(options.gitDiff) : void 0;
|
|
175
|
+
const client = {
|
|
176
|
+
cliVersion: package_default.version,
|
|
177
|
+
platform: process.platform,
|
|
178
|
+
deviceId: options.deviceId
|
|
179
|
+
};
|
|
180
|
+
const sharedMessages = messages.map((msg) => ({
|
|
181
|
+
role: msg.role,
|
|
182
|
+
content: msg.content,
|
|
183
|
+
timestamp: msg.timestamp,
|
|
184
|
+
name: msg.name,
|
|
185
|
+
toolCalls: msg.toolCalls,
|
|
186
|
+
tool_call_id: msg.tool_call_id
|
|
187
|
+
}));
|
|
188
|
+
return {
|
|
189
|
+
metadata: sessionMetadata,
|
|
190
|
+
usage,
|
|
191
|
+
toolUsage,
|
|
192
|
+
gitDiff,
|
|
193
|
+
messages: sharedMessages,
|
|
194
|
+
visibility: options.visibility,
|
|
195
|
+
client,
|
|
196
|
+
userId: options.userId
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/share/ShareApiClient.ts
|
|
201
|
+
import fs from "fs-extra";
|
|
202
|
+
import path from "path";
|
|
203
|
+
function getBaseUrl() {
|
|
204
|
+
return process.env.AUTOHAND_SHARE_URL || "https://autohand.link/api";
|
|
205
|
+
}
|
|
206
|
+
var DEFAULT_CONFIG = {
|
|
207
|
+
baseUrl: getBaseUrl(),
|
|
208
|
+
timeout: 3e4,
|
|
209
|
+
// 30s for large payloads
|
|
210
|
+
maxRetries: 3,
|
|
211
|
+
offlineQueue: true,
|
|
212
|
+
cliVersion: package_default.version
|
|
213
|
+
};
|
|
214
|
+
var ShareApiClient = class {
|
|
215
|
+
constructor(configOverrides) {
|
|
216
|
+
this.deviceId = null;
|
|
217
|
+
this.config = { ...DEFAULT_CONFIG, ...configOverrides };
|
|
218
|
+
const dataDir = path.join(AUTOHAND_PATHS.config, "share");
|
|
219
|
+
this.queuePath = path.join(dataDir, "queue.json");
|
|
220
|
+
this.deviceIdPath = path.join(AUTOHAND_PATHS.feedback, ".device-id");
|
|
221
|
+
}
|
|
222
|
+
// ============ Device ID ============
|
|
223
|
+
/**
|
|
224
|
+
* Get or create anonymous device identifier
|
|
225
|
+
* Shared with FeedbackApiClient for consistency
|
|
226
|
+
*/
|
|
227
|
+
async getDeviceId() {
|
|
228
|
+
if (this.deviceId) return this.deviceId;
|
|
229
|
+
try {
|
|
230
|
+
if (await fs.pathExists(this.deviceIdPath)) {
|
|
231
|
+
this.deviceId = (await fs.readFile(this.deviceIdPath, "utf8")).trim();
|
|
232
|
+
return this.deviceId;
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
this.deviceId = `anon_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
237
|
+
try {
|
|
238
|
+
await fs.ensureDir(path.dirname(this.deviceIdPath));
|
|
239
|
+
await fs.writeFile(this.deviceIdPath, this.deviceId);
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
return this.deviceId;
|
|
243
|
+
}
|
|
244
|
+
// ============ Create Share ============
|
|
245
|
+
/**
|
|
246
|
+
* Upload a session to create a shareable link
|
|
247
|
+
*/
|
|
248
|
+
async createShare(payload) {
|
|
249
|
+
try {
|
|
250
|
+
const response = await this.sendToApi("POST", "/share", payload);
|
|
251
|
+
if (response.success) {
|
|
252
|
+
this.flushQueue().catch(() => {
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return response;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
if (this.config.offlineQueue) {
|
|
258
|
+
await this.addToQueue(payload);
|
|
259
|
+
return {
|
|
260
|
+
success: false,
|
|
261
|
+
error: `Queued for retry: ${error.message}`
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
error: error.message
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// ============ Delete Share ============
|
|
271
|
+
/**
|
|
272
|
+
* Delete a shared session (owner only)
|
|
273
|
+
*/
|
|
274
|
+
async deleteShare(shareId) {
|
|
275
|
+
const deviceId = await this.getDeviceId();
|
|
276
|
+
try {
|
|
277
|
+
const response = await this.sendToApi(
|
|
278
|
+
"DELETE",
|
|
279
|
+
`/share/${shareId}`,
|
|
280
|
+
void 0,
|
|
281
|
+
{ "X-Device-ID": deviceId }
|
|
282
|
+
);
|
|
283
|
+
return response;
|
|
284
|
+
} catch (error) {
|
|
285
|
+
return {
|
|
286
|
+
success: false,
|
|
287
|
+
error: error.message
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// ============ API Communication ============
|
|
292
|
+
/**
|
|
293
|
+
* Send request to API endpoint
|
|
294
|
+
*/
|
|
295
|
+
async sendToApi(method, endpoint, body, extraHeaders) {
|
|
296
|
+
const controller = new AbortController();
|
|
297
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
298
|
+
try {
|
|
299
|
+
const headers = {
|
|
300
|
+
"Content-Type": "application/json",
|
|
301
|
+
"X-CLI-Version": this.config.cliVersion,
|
|
302
|
+
...extraHeaders
|
|
303
|
+
};
|
|
304
|
+
const response = await fetch(`${this.config.baseUrl}${endpoint}`, {
|
|
305
|
+
method,
|
|
306
|
+
headers,
|
|
307
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
308
|
+
signal: controller.signal
|
|
309
|
+
});
|
|
310
|
+
clearTimeout(timeoutId);
|
|
311
|
+
if (!response.ok) {
|
|
312
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
313
|
+
throw new Error(`API error: ${response.status} ${errorText}`);
|
|
314
|
+
}
|
|
315
|
+
const data = await response.json();
|
|
316
|
+
return data;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
clearTimeout(timeoutId);
|
|
319
|
+
if (error.name === "AbortError") {
|
|
320
|
+
throw new Error("Request timeout");
|
|
321
|
+
}
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// ============ Offline Queue ============
|
|
326
|
+
/**
|
|
327
|
+
* Add share to offline queue
|
|
328
|
+
*/
|
|
329
|
+
async addToQueue(payload) {
|
|
330
|
+
try {
|
|
331
|
+
let queue = [];
|
|
332
|
+
if (await fs.pathExists(this.queuePath)) {
|
|
333
|
+
queue = await fs.readJson(this.queuePath);
|
|
334
|
+
}
|
|
335
|
+
queue.push({
|
|
336
|
+
payload,
|
|
337
|
+
attempts: 0,
|
|
338
|
+
lastAttempt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
339
|
+
id: `q_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`
|
|
340
|
+
});
|
|
341
|
+
if (queue.length > 10) {
|
|
342
|
+
queue = queue.slice(-10);
|
|
343
|
+
}
|
|
344
|
+
await fs.ensureDir(path.dirname(this.queuePath));
|
|
345
|
+
await fs.writeJson(this.queuePath, queue, { spaces: 2 });
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Attempt to flush queued shares
|
|
351
|
+
*/
|
|
352
|
+
async flushQueue() {
|
|
353
|
+
let sent = 0;
|
|
354
|
+
let failed = 0;
|
|
355
|
+
try {
|
|
356
|
+
if (!await fs.pathExists(this.queuePath)) {
|
|
357
|
+
return { sent, failed };
|
|
358
|
+
}
|
|
359
|
+
const queue = await fs.readJson(this.queuePath);
|
|
360
|
+
const remaining = [];
|
|
361
|
+
for (const item of queue) {
|
|
362
|
+
if (item.attempts >= this.config.maxRetries) {
|
|
363
|
+
failed++;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
const response = await this.sendToApi("POST", "/share", item.payload);
|
|
368
|
+
if (response.success) {
|
|
369
|
+
sent++;
|
|
370
|
+
} else {
|
|
371
|
+
item.attempts++;
|
|
372
|
+
item.lastAttempt = (/* @__PURE__ */ new Date()).toISOString();
|
|
373
|
+
remaining.push(item);
|
|
374
|
+
failed++;
|
|
375
|
+
}
|
|
376
|
+
} catch {
|
|
377
|
+
item.attempts++;
|
|
378
|
+
item.lastAttempt = (/* @__PURE__ */ new Date()).toISOString();
|
|
379
|
+
remaining.push(item);
|
|
380
|
+
failed++;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (remaining.length > 0) {
|
|
384
|
+
await fs.writeJson(this.queuePath, remaining, { spaces: 2 });
|
|
385
|
+
} else {
|
|
386
|
+
await fs.remove(this.queuePath);
|
|
387
|
+
}
|
|
388
|
+
} catch {
|
|
389
|
+
}
|
|
390
|
+
return { sent, failed };
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Get queue status
|
|
394
|
+
*/
|
|
395
|
+
async getQueueStatus() {
|
|
396
|
+
try {
|
|
397
|
+
if (!await fs.pathExists(this.queuePath)) {
|
|
398
|
+
return { pending: 0, oldestItem: null };
|
|
399
|
+
}
|
|
400
|
+
const queue = await fs.readJson(this.queuePath);
|
|
401
|
+
return {
|
|
402
|
+
pending: queue.length,
|
|
403
|
+
oldestItem: queue[0]?.lastAttempt || null
|
|
404
|
+
};
|
|
405
|
+
} catch {
|
|
406
|
+
return { pending: 0, oldestItem: null };
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
// ============ Health Check ============
|
|
410
|
+
/**
|
|
411
|
+
* Check if API is reachable
|
|
412
|
+
*/
|
|
413
|
+
async healthCheck() {
|
|
414
|
+
try {
|
|
415
|
+
const controller = new AbortController();
|
|
416
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
417
|
+
const response = await fetch(`${this.config.baseUrl}/health`, {
|
|
418
|
+
method: "GET",
|
|
419
|
+
signal: controller.signal
|
|
420
|
+
});
|
|
421
|
+
clearTimeout(timeoutId);
|
|
422
|
+
return response.ok;
|
|
423
|
+
} catch {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
var instance = null;
|
|
429
|
+
var instanceBaseUrl = null;
|
|
430
|
+
function getShareApiClient(config) {
|
|
431
|
+
const currentBaseUrl = config?.baseUrl || getBaseUrl();
|
|
432
|
+
if (instance && instanceBaseUrl !== currentBaseUrl) {
|
|
433
|
+
instance = null;
|
|
434
|
+
}
|
|
435
|
+
if (!instance) {
|
|
436
|
+
instance = new ShareApiClient(config);
|
|
437
|
+
instanceBaseUrl = currentBaseUrl;
|
|
438
|
+
}
|
|
439
|
+
return instance;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/commands/share.ts
|
|
443
|
+
function terminalLink(url, text) {
|
|
444
|
+
const displayText = text || url;
|
|
445
|
+
const OSC = "\x1B]8;;";
|
|
446
|
+
const ST = "\x1B\\";
|
|
447
|
+
return `${OSC}${url}${ST}${chalk.cyan.underline(displayText)}${OSC}${ST}`;
|
|
448
|
+
}
|
|
449
|
+
var metadata = {
|
|
450
|
+
command: "/share",
|
|
451
|
+
description: "Share current session via public URL",
|
|
452
|
+
implemented: true
|
|
453
|
+
};
|
|
454
|
+
async function execute(_args, context) {
|
|
455
|
+
if (context?.config?.share?.enabled === false) {
|
|
456
|
+
console.log(chalk.yellow("Session sharing is disabled."));
|
|
457
|
+
console.log(
|
|
458
|
+
chalk.gray("To enable, set share.enabled: true in your config file.")
|
|
459
|
+
);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (!context?.currentSession) {
|
|
463
|
+
console.log(chalk.yellow("No active session to share."));
|
|
464
|
+
console.log(
|
|
465
|
+
chalk.gray("Start a conversation first, then use /share to share it.")
|
|
466
|
+
);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const session = context.currentSession;
|
|
470
|
+
const messages = session.getMessages();
|
|
471
|
+
if (messages.length === 0) {
|
|
472
|
+
console.log(chalk.yellow("Session has no messages to share."));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const client = getShareApiClient();
|
|
476
|
+
const deviceId = await client.getDeviceId();
|
|
477
|
+
const totalTokens = context.getTotalTokensUsed?.() ?? 0;
|
|
478
|
+
const duration = calculateDuration2(session.metadata.createdAt);
|
|
479
|
+
console.log();
|
|
480
|
+
console.log(chalk.bold("Session Summary"));
|
|
481
|
+
console.log(chalk.gray("\u2500".repeat(40)));
|
|
482
|
+
console.log(` Project: ${chalk.cyan(session.metadata.projectName)}`);
|
|
483
|
+
console.log(` Model: ${chalk.cyan(context.model)}`);
|
|
484
|
+
console.log(` Messages: ${chalk.cyan(messages.length)}`);
|
|
485
|
+
console.log(` Tokens: ${chalk.cyan(formatTokens(totalTokens))}`);
|
|
486
|
+
console.log(
|
|
487
|
+
` Est. Cost: ${chalk.green(formatCost(totalTokens / 1e3 * 3e-3))}`
|
|
488
|
+
);
|
|
489
|
+
console.log(` Duration: ${chalk.cyan(formatDuration(duration))}`);
|
|
490
|
+
console.log();
|
|
491
|
+
const { Select, Confirm } = enquirer;
|
|
492
|
+
const visibilityPrompt = new Select({
|
|
493
|
+
name: "visibility",
|
|
494
|
+
message: "Share visibility:",
|
|
495
|
+
choices: [
|
|
496
|
+
{ name: "public", message: "Public - Anyone with the link can view" },
|
|
497
|
+
{ name: "private", message: "Private - Requires one-time passcode" }
|
|
498
|
+
]
|
|
499
|
+
});
|
|
500
|
+
let visibility;
|
|
501
|
+
try {
|
|
502
|
+
visibility = await visibilityPrompt.run();
|
|
503
|
+
} catch {
|
|
504
|
+
console.log(chalk.gray("Cancelled."));
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const confirmPrompt = new Confirm({
|
|
508
|
+
name: "confirm",
|
|
509
|
+
message: `Share this session as ${visibility}?`,
|
|
510
|
+
initial: true
|
|
511
|
+
});
|
|
512
|
+
let confirmed;
|
|
513
|
+
try {
|
|
514
|
+
confirmed = await confirmPrompt.run();
|
|
515
|
+
} catch {
|
|
516
|
+
console.log(chalk.gray("Cancelled."));
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (!confirmed) {
|
|
520
|
+
console.log(chalk.gray("Cancelled."));
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
console.log();
|
|
524
|
+
const spinner = ora("Uploading session...").start();
|
|
525
|
+
try {
|
|
526
|
+
const payload = serializeSession(session, {
|
|
527
|
+
model: context.model,
|
|
528
|
+
provider: context.provider,
|
|
529
|
+
totalTokens,
|
|
530
|
+
visibility,
|
|
531
|
+
deviceId
|
|
532
|
+
});
|
|
533
|
+
const response = await client.createShare(payload);
|
|
534
|
+
spinner.stop();
|
|
535
|
+
if (response.success && response.url) {
|
|
536
|
+
console.log();
|
|
537
|
+
console.log(chalk.green.bold("Session shared successfully!"));
|
|
538
|
+
console.log();
|
|
539
|
+
console.log(`${chalk.bold("URL:")} ${terminalLink(response.url)}`);
|
|
540
|
+
if (visibility === "private" && response.passcode) {
|
|
541
|
+
console.log();
|
|
542
|
+
console.log(`${chalk.bold("Passcode:")} ${chalk.yellow.bold(response.passcode)}`);
|
|
543
|
+
console.log(
|
|
544
|
+
chalk.gray(" Share this passcode with people who need access.")
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
console.log();
|
|
548
|
+
console.log(chalk.gray("Tip: Click the URL or copy it to share with others!"));
|
|
549
|
+
console.log();
|
|
550
|
+
} else {
|
|
551
|
+
console.log();
|
|
552
|
+
console.log(
|
|
553
|
+
chalk.red(`Failed to share: ${response.error || "Unknown error"}`)
|
|
554
|
+
);
|
|
555
|
+
console.log();
|
|
556
|
+
}
|
|
557
|
+
} catch (error) {
|
|
558
|
+
spinner.stop();
|
|
559
|
+
console.log();
|
|
560
|
+
console.log(chalk.red(`Failed to share: ${error.message}`));
|
|
561
|
+
console.log();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function calculateDuration2(startedAt) {
|
|
565
|
+
const start = new Date(startedAt).getTime();
|
|
566
|
+
const now = Date.now();
|
|
567
|
+
return Math.max(0, Math.floor((now - start) / 1e3));
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export {
|
|
571
|
+
metadata,
|
|
572
|
+
execute
|
|
573
|
+
};
|
|
574
|
+
/**
|
|
575
|
+
* @license
|
|
576
|
+
* Copyright 2025 Autohand AI LLC
|
|
577
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
578
|
+
*
|
|
579
|
+
* Cost Estimator
|
|
580
|
+
* Simplified token cost estimation for session sharing
|
|
581
|
+
*/
|
|
582
|
+
/**
|
|
583
|
+
* @license
|
|
584
|
+
* Copyright 2025 Autohand AI LLC
|
|
585
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
586
|
+
*
|
|
587
|
+
* Session Serializer
|
|
588
|
+
* Converts session data to ShareSessionPayload format
|
|
589
|
+
*/
|
|
590
|
+
/**
|
|
591
|
+
* @license
|
|
592
|
+
* Copyright 2025 Autohand AI LLC
|
|
593
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
594
|
+
*
|
|
595
|
+
* Share API Client
|
|
596
|
+
* Handles communication with autohand.link for session sharing
|
|
597
|
+
*/
|
|
598
|
+
/**
|
|
599
|
+
* @license
|
|
600
|
+
* Copyright 2025 Autohand AI LLC
|
|
601
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
602
|
+
*
|
|
603
|
+
* Share Module
|
|
604
|
+
* Session sharing functionality
|
|
605
|
+
*/
|
|
606
|
+
/**
|
|
607
|
+
* @license
|
|
608
|
+
* Copyright 2025 Autohand AI LLC
|
|
609
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
610
|
+
*
|
|
611
|
+
* /share Command
|
|
612
|
+
* Share current session via public URL
|
|
613
|
+
*/
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});// package.json
|
|
2
|
+
var package_default = {
|
|
3
|
+
name: "autohand-cli",
|
|
4
|
+
version: "0.7.6",
|
|
5
|
+
license: "Apache-2.0",
|
|
6
|
+
description: "Autohand interactive coding agent CLI powered by LLMs.",
|
|
7
|
+
repository: {
|
|
8
|
+
type: "git",
|
|
9
|
+
url: "https://github.com/autohandai/code-cli.git"
|
|
10
|
+
},
|
|
11
|
+
homepage: "https://autohand.ai/cli/",
|
|
12
|
+
bugs: {
|
|
13
|
+
url: "https://github.com/autohandai/code-cli/issues"
|
|
14
|
+
},
|
|
15
|
+
type: "module",
|
|
16
|
+
bin: {
|
|
17
|
+
autohand: "dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
main: "dist/index.js",
|
|
20
|
+
files: [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
scripts: {
|
|
24
|
+
go: 'bun run build && ./install-local.sh && echo "COMPLETED"',
|
|
25
|
+
build: "tsup",
|
|
26
|
+
dev: "bun src/index.ts",
|
|
27
|
+
typecheck: "tsc --noEmit",
|
|
28
|
+
lint: "eslint .",
|
|
29
|
+
test: "vitest run",
|
|
30
|
+
start: "node dist/index.js",
|
|
31
|
+
"compile:macos-arm64": "bun build ./src/index.ts --compile --target=bun-darwin-arm64 --external react-devtools-core --outfile ./binaries/autohand-macos-arm64",
|
|
32
|
+
"compile:macos-x64": "bun build ./src/index.ts --compile --target=bun-darwin-x64 --external react-devtools-core --outfile ./binaries/autohand-macos-x64",
|
|
33
|
+
"compile:linux-x64": "bun build ./src/index.ts --compile --target=bun-linux-x64 --external react-devtools-core --outfile ./binaries/autohand-linux-x64",
|
|
34
|
+
"compile:linux-arm64": "bun build ./src/index.ts --compile --target=bun-linux-arm64 --external react-devtools-core --outfile ./binaries/autohand-linux-arm64",
|
|
35
|
+
"compile:windows-x64": "bun build ./src/index.ts --compile --target=bun-windows-x64 --external react-devtools-core --outfile ./binaries/autohand-windows-x64.exe",
|
|
36
|
+
"compile:all": "bun run compile:macos-arm64 && bun run compile:macos-x64 && bun run compile:linux-x64 && bun run compile:linux-arm64 && bun run compile:windows-x64",
|
|
37
|
+
link: "bun link"
|
|
38
|
+
},
|
|
39
|
+
keywords: [
|
|
40
|
+
"cli",
|
|
41
|
+
"llm",
|
|
42
|
+
"agent",
|
|
43
|
+
"autohand"
|
|
44
|
+
],
|
|
45
|
+
engines: {
|
|
46
|
+
node: ">=18.17.0"
|
|
47
|
+
},
|
|
48
|
+
dependencies: {
|
|
49
|
+
chalk: "^5.6.2",
|
|
50
|
+
commander: "^14.0.2",
|
|
51
|
+
diff: "^8.0.2",
|
|
52
|
+
dotenv: "^17.2.3",
|
|
53
|
+
enquirer: "^2.4.1",
|
|
54
|
+
"fs-extra": "^11.3.2",
|
|
55
|
+
ignore: "^5.3.1",
|
|
56
|
+
ink: "^4.4.1",
|
|
57
|
+
"ink-spinner": "^5.0.0",
|
|
58
|
+
minimatch: "^10.1.1",
|
|
59
|
+
open: "^10.1.0",
|
|
60
|
+
ora: "^9.0.0",
|
|
61
|
+
react: "^18.2.0",
|
|
62
|
+
"react-devtools-core": "^7.0.1",
|
|
63
|
+
"terminal-link": "^3.0.0",
|
|
64
|
+
yaml: "^2.8.2",
|
|
65
|
+
zod: "^4.1.12"
|
|
66
|
+
},
|
|
67
|
+
devDependencies: {
|
|
68
|
+
"@types/diff": "^8.0.0",
|
|
69
|
+
"@types/fs-extra": "^11.0.4",
|
|
70
|
+
"@types/minimatch": "^6.0.0",
|
|
71
|
+
"@types/node": "^24.10.1",
|
|
72
|
+
"@types/react": "^18.3.3",
|
|
73
|
+
"@types/terminal-link": "^1.2.0",
|
|
74
|
+
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
|
75
|
+
"@typescript-eslint/parser": "^8.48.1",
|
|
76
|
+
eslint: "^9.39.1",
|
|
77
|
+
memfs: "^4.51.1",
|
|
78
|
+
tsup: "^8.5.1",
|
|
79
|
+
tsx: "^4.20.6",
|
|
80
|
+
typescript: "^5.9.3",
|
|
81
|
+
vitest: "^1.6.0"
|
|
82
|
+
},
|
|
83
|
+
overrides: {
|
|
84
|
+
ink: {
|
|
85
|
+
"slice-ansi": {
|
|
86
|
+
"ansi-styles": "^6.2.1"
|
|
87
|
+
},
|
|
88
|
+
"wrap-ansi": {
|
|
89
|
+
"ansi-styles": "^6.2.1"
|
|
90
|
+
},
|
|
91
|
+
"cli-truncate": {
|
|
92
|
+
"slice-ansi": {
|
|
93
|
+
"ansi-styles": "^6.2.1"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
exports.package_default = package_default;
|