@agent-api/cli 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.
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/dist/agent/runner.d.ts +115 -0
- package/dist/agent/runner.js +483 -0
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +2 -0
- package/dist/chat-options.d.ts +37 -0
- package/dist/chat-options.js +42 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +111 -0
- package/dist/conversation/index.d.ts +17 -0
- package/dist/conversation/index.js +54 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +308 -0
- package/dist/profile.d.ts +57 -0
- package/dist/profile.js +211 -0
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.js +12 -0
- package/dist/tui/chat.d.ts +5 -0
- package/dist/tui/chat.js +1276 -0
- package/dist/tui/workbench.d.ts +154 -0
- package/dist/tui/workbench.js +288 -0
- package/dist/update.d.ts +16 -0
- package/dist/update.js +74 -0
- package/dist/workdir/index.d.ts +22 -0
- package/dist/workdir/index.js +46 -0
- package/package.json +61 -0
package/dist/profile.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { AgentAPI, browserAuthSessionExpiresWithin } from "@agent-api/sdk";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { platform } from "node:os";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { activeProfile, defaultBaseURL, loadConfig, redactSecret, saveConfig, upsertProfile, } from "./config.js";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
export class AuthSessionExpiredError extends Error {
|
|
8
|
+
profile;
|
|
9
|
+
baseURL;
|
|
10
|
+
constructor(profile, message = "browser session refresh failed") {
|
|
11
|
+
super([
|
|
12
|
+
"Browser session expired or could not be refreshed.",
|
|
13
|
+
`Run: agent-api auth login --profile ${profile.name} --base-url ${profile.baseURL}`,
|
|
14
|
+
`Details: ${message}`,
|
|
15
|
+
].join("\n"));
|
|
16
|
+
this.name = "AuthSessionExpiredError";
|
|
17
|
+
this.profile = profile.name;
|
|
18
|
+
this.baseURL = profile.baseURL;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function loginWithAPIKey(options) {
|
|
22
|
+
const profile = await upsertProfile({
|
|
23
|
+
name: options.profile,
|
|
24
|
+
baseURL: normalizeBaseURL(options.baseURL),
|
|
25
|
+
auth: { type: "api_key", apiKey: options.apiKey.trim() },
|
|
26
|
+
});
|
|
27
|
+
return profile;
|
|
28
|
+
}
|
|
29
|
+
export async function loginWithBrowser(options) {
|
|
30
|
+
const baseURL = normalizeBaseURL(options.baseURL);
|
|
31
|
+
const challenge = await startBrowserAuthChallenge({ baseURL, clientName: options.clientName });
|
|
32
|
+
console.log(`Open this URL to authorize the CLI:\n${challenge.verification_uri_complete}\n`);
|
|
33
|
+
console.log(`Code: ${formatDeviceUserCode(challenge.user_code)}\n`);
|
|
34
|
+
if (options.openBrowser !== false) {
|
|
35
|
+
await openBrowserURL(challenge.verification_uri_complete).catch((error) => {
|
|
36
|
+
console.warn(`Could not open browser automatically: ${error instanceof Error ? error.message : String(error)}`);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
console.log("Waiting for browser approval...");
|
|
40
|
+
const session = await waitForBrowserAuthChallenge({
|
|
41
|
+
baseURL,
|
|
42
|
+
challenge,
|
|
43
|
+
on_poll(result) {
|
|
44
|
+
if (result.status && result.status !== "pending") {
|
|
45
|
+
console.log(`Status: ${result.status}`);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
const profile = await saveBrowserProfile(options.profile, baseURL, session);
|
|
50
|
+
console.log(`Signed in as profile "${profile.name}".`);
|
|
51
|
+
return profile;
|
|
52
|
+
}
|
|
53
|
+
export async function startBrowserAuthChallenge(options) {
|
|
54
|
+
const baseURL = normalizeBaseURL(options.baseURL);
|
|
55
|
+
const client = new AgentAPI({ baseURL });
|
|
56
|
+
return await client.auth.startDeviceAuth({ client_name: options.clientName || "Agent API CLI" });
|
|
57
|
+
}
|
|
58
|
+
export async function waitForBrowserAuthChallenge(options) {
|
|
59
|
+
const baseURL = normalizeBaseURL(options.baseURL);
|
|
60
|
+
const client = new AgentAPI({ baseURL });
|
|
61
|
+
return await client.auth.waitForDeviceAuth({
|
|
62
|
+
device_code: options.challenge.device_code,
|
|
63
|
+
interval_seconds: options.challenge.interval_seconds,
|
|
64
|
+
timeout_ms: Math.max(0, options.challenge.expires_at * 1000 - Date.now()),
|
|
65
|
+
on_poll: options.on_poll,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export async function saveBrowserProfile(name, baseURL, session) {
|
|
69
|
+
return await upsertProfile({
|
|
70
|
+
name,
|
|
71
|
+
baseURL,
|
|
72
|
+
auth: {
|
|
73
|
+
type: "browser",
|
|
74
|
+
accessToken: session.access_token,
|
|
75
|
+
refreshToken: session.refresh_token,
|
|
76
|
+
accessTokenExpiresAt: session.access_token_expires_at,
|
|
77
|
+
refreshTokenExpiresAt: session.refresh_token_expires_at,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export async function resolveRuntimeProfile(profileName) {
|
|
82
|
+
const { profile: fresh } = await refreshActiveProfileIfNeeded(profileName);
|
|
83
|
+
const token = fresh.auth.type === "api_key" ? fresh.auth.apiKey : fresh.auth.accessToken;
|
|
84
|
+
return {
|
|
85
|
+
profile: fresh,
|
|
86
|
+
token,
|
|
87
|
+
client: new AgentAPI({ apiKey: token, baseURL: fresh.baseURL }),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export async function getAuthStatus(profileName) {
|
|
91
|
+
const runtime = await resolveRuntimeProfile(profileName);
|
|
92
|
+
const response = await fetch(`${runtime.profile.baseURL}/v1/me`, {
|
|
93
|
+
headers: { Authorization: `Bearer ${runtime.token}` },
|
|
94
|
+
});
|
|
95
|
+
const payload = await response.json().catch(() => undefined);
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
throw new Error(errorMessageFromPayload(payload) || `whoami failed with ${response.status}`);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
profile: runtime.profile.name,
|
|
101
|
+
baseURL: runtime.profile.baseURL,
|
|
102
|
+
authType: runtime.profile.auth.type,
|
|
103
|
+
me: payload,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export async function refreshActiveProfileIfNeeded(profileName, refreshWindowMs = 60_000) {
|
|
107
|
+
const profile = await activeProfile(profileName);
|
|
108
|
+
const shouldRefresh = browserAccessTokenExpiresWithin(profile, refreshWindowMs);
|
|
109
|
+
const fresh = await refreshIfNeeded(profile, refreshWindowMs);
|
|
110
|
+
return { profile: fresh, refreshed: shouldRefresh && profile.auth.type === "browser" };
|
|
111
|
+
}
|
|
112
|
+
export async function refreshIfNeeded(profile, refreshWindowMs = 60_000) {
|
|
113
|
+
if (profile.auth.type !== "browser")
|
|
114
|
+
return profile;
|
|
115
|
+
const expiresAtMs = profile.auth.accessTokenExpiresAt * 1000;
|
|
116
|
+
if (expiresAtMs - Date.now() > refreshWindowMs)
|
|
117
|
+
return profile;
|
|
118
|
+
const refreshed = await refreshBrowserSession(profile);
|
|
119
|
+
const config = await loadConfig();
|
|
120
|
+
config.profiles[profile.name] = refreshed;
|
|
121
|
+
await saveConfig(config);
|
|
122
|
+
return refreshed;
|
|
123
|
+
}
|
|
124
|
+
export function browserAccessTokenExpiresWithin(profile, refreshWindowMs, now = Date.now()) {
|
|
125
|
+
if (profile.auth.type !== "browser")
|
|
126
|
+
return false;
|
|
127
|
+
return browserAuthSessionExpiresWithin({ access_token_expires_at: profile.auth.accessTokenExpiresAt }, refreshWindowMs, now);
|
|
128
|
+
}
|
|
129
|
+
export async function refreshBrowserSession(profile) {
|
|
130
|
+
if (profile.auth.type !== "browser")
|
|
131
|
+
return profile;
|
|
132
|
+
const client = new AgentAPI({ baseURL: profile.baseURL });
|
|
133
|
+
let session;
|
|
134
|
+
try {
|
|
135
|
+
session = await client.auth.refreshBrowserSession({ refresh_token: profile.auth.refreshToken });
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
throw new AuthSessionExpiredError(profile, error instanceof Error ? error.message : String(error));
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
...profile,
|
|
142
|
+
auth: {
|
|
143
|
+
type: "browser",
|
|
144
|
+
accessToken: session.access_token,
|
|
145
|
+
refreshToken: session.refresh_token || profile.auth.refreshToken,
|
|
146
|
+
accessTokenExpiresAt: session.access_token_expires_at,
|
|
147
|
+
refreshTokenExpiresAt: session.refresh_token_expires_at || profile.auth.refreshTokenExpiresAt,
|
|
148
|
+
},
|
|
149
|
+
updatedAt: Math.floor(Date.now() / 1000),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
export async function listProfiles() {
|
|
153
|
+
const config = await loadConfig();
|
|
154
|
+
return { active: config.activeProfile, profiles: Object.values(config.profiles).sort((a, b) => a.name.localeCompare(b.name)) };
|
|
155
|
+
}
|
|
156
|
+
export async function useProfile(name) {
|
|
157
|
+
const config = await loadConfig();
|
|
158
|
+
if (!config.profiles[name])
|
|
159
|
+
throw new Error(`Profile not found: ${name}`);
|
|
160
|
+
config.activeProfile = name;
|
|
161
|
+
await saveConfig(config);
|
|
162
|
+
}
|
|
163
|
+
export async function deleteProfile(name) {
|
|
164
|
+
const config = await loadConfig();
|
|
165
|
+
delete config.profiles[name];
|
|
166
|
+
if (config.activeProfile === name) {
|
|
167
|
+
config.activeProfile = Object.keys(config.profiles).sort()[0] || "default";
|
|
168
|
+
}
|
|
169
|
+
await saveConfig(config);
|
|
170
|
+
}
|
|
171
|
+
export function profileSummary(profile, active = false) {
|
|
172
|
+
const auth = profile.auth.type === "api_key"
|
|
173
|
+
? `api_key ${redactSecret(profile.auth.apiKey)}`
|
|
174
|
+
: `browser ${redactSecret(profile.auth.accessToken)}`;
|
|
175
|
+
return `${active ? "*" : " "} ${profile.name}\t${profile.baseURL}\t${auth}`;
|
|
176
|
+
}
|
|
177
|
+
function errorMessageFromPayload(payload) {
|
|
178
|
+
if (payload && typeof payload === "object") {
|
|
179
|
+
const error = payload.error;
|
|
180
|
+
if (error && typeof error === "object") {
|
|
181
|
+
const message = error.message;
|
|
182
|
+
if (typeof message === "string")
|
|
183
|
+
return message;
|
|
184
|
+
}
|
|
185
|
+
const message = payload.message;
|
|
186
|
+
if (typeof message === "string")
|
|
187
|
+
return message;
|
|
188
|
+
}
|
|
189
|
+
return "";
|
|
190
|
+
}
|
|
191
|
+
function normalizeBaseURL(baseURL) {
|
|
192
|
+
return (baseURL || process.env.AGENT_API_BASE_URL || defaultBaseURL).replace(/\/+$/, "");
|
|
193
|
+
}
|
|
194
|
+
export function formatDeviceUserCode(code) {
|
|
195
|
+
const normalized = code.replace(/[-\s]/g, "").toUpperCase();
|
|
196
|
+
if (normalized.length <= 4)
|
|
197
|
+
return normalized;
|
|
198
|
+
return `${normalized.slice(0, 4)}-${normalized.slice(4)}`;
|
|
199
|
+
}
|
|
200
|
+
export async function openBrowserURL(url) {
|
|
201
|
+
const current = platform();
|
|
202
|
+
if (current === "darwin") {
|
|
203
|
+
await execFileAsync("open", [url]);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (current === "win32") {
|
|
207
|
+
await execFileAsync("cmd", ["/c", "start", "", url]);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
await execFileAsync("xdg-open", [url]);
|
|
211
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const cliName = "agent-api-cli";
|
|
2
|
+
export declare const cliAuthor = "AgentsWay";
|
|
3
|
+
export declare const cliVersion = "0.0.1";
|
|
4
|
+
export declare const runtime: import("@agent-api/sdk/local").LocalRuntime;
|
|
5
|
+
export declare function ensureRuntime(): Promise<import("@agent-api/sdk/local").LocalRuntime>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createLocalRuntime } from "@agent-api/sdk/local";
|
|
2
|
+
export const cliName = "agent-api-cli";
|
|
3
|
+
export const cliAuthor = "AgentsWay";
|
|
4
|
+
export const cliVersion = "0.0.1";
|
|
5
|
+
export const runtime = createLocalRuntime({
|
|
6
|
+
appName: cliName,
|
|
7
|
+
appAuthor: cliAuthor,
|
|
8
|
+
});
|
|
9
|
+
export async function ensureRuntime() {
|
|
10
|
+
await runtime.ensure();
|
|
11
|
+
return runtime;
|
|
12
|
+
}
|