@agent-api/app-engine 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/README.md +46 -0
- package/dist/agent/runner.d.ts +117 -0
- package/dist/agent/runner.js +486 -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 +66 -0
- package/dist/config.js +201 -0
- package/dist/conversation/index.d.ts +17 -0
- package/dist/conversation/index.js +54 -0
- package/dist/engine/agent-engine.d.ts +38 -0
- package/dist/engine/agent-engine.js +146 -0
- package/dist/engine/index.d.ts +50 -0
- package/dist/engine/index.js +26 -0
- package/dist/engine/services.d.ts +20 -0
- package/dist/engine/services.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/profile.d.ts +57 -0
- package/dist/profile.js +211 -0
- package/dist/runtime/index.d.ts +23 -0
- package/dist/runtime/index.js +177 -0
- package/dist/update.d.ts +16 -0
- package/dist/update.js +74 -0
- package/dist/workbench/auth-controller.d.ts +43 -0
- package/dist/workbench/auth-controller.js +84 -0
- package/dist/workbench/auth-gate-controller.d.ts +62 -0
- package/dist/workbench/auth-gate-controller.js +231 -0
- package/dist/workbench/command-controller.d.ts +29 -0
- package/dist/workbench/command-controller.js +426 -0
- package/dist/workbench/conversation-controller.d.ts +32 -0
- package/dist/workbench/conversation-controller.js +53 -0
- package/dist/workbench/engine.d.ts +66 -0
- package/dist/workbench/engine.js +291 -0
- package/dist/workbench/input-controller.d.ts +44 -0
- package/dist/workbench/input-controller.js +71 -0
- package/dist/workbench/isolator-installer.d.ts +29 -0
- package/dist/workbench/isolator-installer.js +208 -0
- package/dist/workbench/lifecycle-controller.d.ts +30 -0
- package/dist/workbench/lifecycle-controller.js +75 -0
- package/dist/workbench/local-controller.d.ts +21 -0
- package/dist/workbench/local-controller.js +94 -0
- package/dist/workbench/render-model.d.ts +46 -0
- package/dist/workbench/render-model.js +61 -0
- package/dist/workbench/runtime-controller.d.ts +12 -0
- package/dist/workbench/runtime-controller.js +57 -0
- package/dist/workbench/session.d.ts +29 -0
- package/dist/workbench/session.js +42 -0
- package/dist/workbench/settings-controller.d.ts +80 -0
- package/dist/workbench/settings-controller.js +309 -0
- package/dist/workbench/shell-isolation.d.ts +20 -0
- package/dist/workbench/shell-isolation.js +13 -0
- package/dist/workbench/state.d.ts +187 -0
- package/dist/workbench/state.js +392 -0
- package/dist/workbench/turn-controller.d.ts +25 -0
- package/dist/workbench/turn-controller.js +164 -0
- package/dist/workbench/view-model.d.ts +34 -0
- package/dist/workbench/view-model.js +121 -0
- package/dist/workdir/index.d.ts +22 -0
- package/dist/workdir/index.js +46 -0
- package/package.json +50 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./engine/index.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./engine/index.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AgentAPI, type ApprovedDeviceAuth } from "@agent-api/sdk";
|
|
2
|
+
import { Profile } from "./config.js";
|
|
3
|
+
export interface RuntimeProfile {
|
|
4
|
+
profile: Profile;
|
|
5
|
+
token: string;
|
|
6
|
+
client: AgentAPI;
|
|
7
|
+
}
|
|
8
|
+
export interface AuthStatus {
|
|
9
|
+
profile: string;
|
|
10
|
+
baseURL: string;
|
|
11
|
+
authType: Profile["auth"]["type"];
|
|
12
|
+
me?: unknown;
|
|
13
|
+
}
|
|
14
|
+
export declare class AuthSessionExpiredError extends Error {
|
|
15
|
+
readonly profile: string;
|
|
16
|
+
readonly baseURL: string;
|
|
17
|
+
constructor(profile: Profile, message?: string);
|
|
18
|
+
}
|
|
19
|
+
export declare function loginWithAPIKey(options: {
|
|
20
|
+
profile: string;
|
|
21
|
+
baseURL?: string;
|
|
22
|
+
apiKey: string;
|
|
23
|
+
}): Promise<Profile>;
|
|
24
|
+
export declare function loginWithBrowser(options: {
|
|
25
|
+
profile: string;
|
|
26
|
+
baseURL?: string;
|
|
27
|
+
clientName?: string;
|
|
28
|
+
openBrowser?: boolean;
|
|
29
|
+
}): Promise<Profile>;
|
|
30
|
+
export declare function startBrowserAuthChallenge(options: {
|
|
31
|
+
baseURL?: string;
|
|
32
|
+
clientName?: string;
|
|
33
|
+
}): Promise<import("@agent-api/sdk").DeviceAuthStart>;
|
|
34
|
+
export declare function waitForBrowserAuthChallenge(options: {
|
|
35
|
+
baseURL?: string;
|
|
36
|
+
challenge: Awaited<ReturnType<typeof startBrowserAuthChallenge>>;
|
|
37
|
+
on_poll?: Parameters<AgentAPI["auth"]["waitForDeviceAuth"]>[0]["on_poll"];
|
|
38
|
+
}): Promise<ApprovedDeviceAuth>;
|
|
39
|
+
export declare function saveBrowserProfile(name: string, baseURL: string, session: ApprovedDeviceAuth): Promise<Profile>;
|
|
40
|
+
export declare function resolveRuntimeProfile(profileName?: string): Promise<RuntimeProfile>;
|
|
41
|
+
export declare function getAuthStatus(profileName?: string): Promise<AuthStatus>;
|
|
42
|
+
export declare function refreshActiveProfileIfNeeded(profileName?: string, refreshWindowMs?: number): Promise<{
|
|
43
|
+
profile: Profile;
|
|
44
|
+
refreshed: boolean;
|
|
45
|
+
}>;
|
|
46
|
+
export declare function refreshIfNeeded(profile: Profile, refreshWindowMs?: number): Promise<Profile>;
|
|
47
|
+
export declare function browserAccessTokenExpiresWithin(profile: Profile, refreshWindowMs: number, now?: number): boolean;
|
|
48
|
+
export declare function refreshBrowserSession(profile: Profile): Promise<Profile>;
|
|
49
|
+
export declare function listProfiles(): Promise<{
|
|
50
|
+
active: string;
|
|
51
|
+
profiles: Profile[];
|
|
52
|
+
}>;
|
|
53
|
+
export declare function useProfile(name: string): Promise<void>;
|
|
54
|
+
export declare function deleteProfile(name: string): Promise<void>;
|
|
55
|
+
export declare function profileSummary(profile: Profile, active?: boolean): string;
|
|
56
|
+
export declare function formatDeviceUserCode(code: string): string;
|
|
57
|
+
export declare function openBrowserURL(url: string): Promise<void>;
|
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,23 @@
|
|
|
1
|
+
import type { LocalRuntime, LocalRuntimeOptions } from "@agent-api/sdk/local";
|
|
2
|
+
export declare const defaultAppName = "agent-tui";
|
|
3
|
+
export declare const defaultAppAuthor = "AgentsWay";
|
|
4
|
+
export declare const defaultAppVersion = "0.3.0";
|
|
5
|
+
export interface AgentAppRuntimeOptions extends Omit<LocalRuntimeOptions, "appName" | "appAuthor"> {
|
|
6
|
+
appName?: string;
|
|
7
|
+
appAuthor?: string;
|
|
8
|
+
appVersion?: string;
|
|
9
|
+
legacyAppName?: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface AgentAppRuntimeContext {
|
|
12
|
+
appName: string;
|
|
13
|
+
appAuthor: string;
|
|
14
|
+
appVersion: string;
|
|
15
|
+
legacyAppName?: string | null;
|
|
16
|
+
runtime: LocalRuntime;
|
|
17
|
+
legacyRuntime?: LocalRuntime;
|
|
18
|
+
}
|
|
19
|
+
export declare let runtime: LocalRuntime;
|
|
20
|
+
export declare function configureAgentAppRuntime(options?: AgentAppRuntimeOptions): AgentAppRuntimeContext;
|
|
21
|
+
export declare function currentAgentAppRuntime(): AgentAppRuntimeContext;
|
|
22
|
+
export declare function appVersion(): string;
|
|
23
|
+
export declare function ensureRuntime(): Promise<LocalRuntime>;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createLocalRuntime } from "@agent-api/sdk/local";
|
|
2
|
+
import { cp, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export const defaultAppName = "agent-tui";
|
|
5
|
+
export const defaultAppAuthor = "AgentsWay";
|
|
6
|
+
export const defaultAppVersion = "0.3.0";
|
|
7
|
+
const defaultLegacyAppName = "agent-api-cli";
|
|
8
|
+
let migrationPromise = null;
|
|
9
|
+
let runtimeContext = createRuntimeContext();
|
|
10
|
+
export let runtime = runtimeContext.runtime;
|
|
11
|
+
export function configureAgentAppRuntime(options = {}) {
|
|
12
|
+
runtimeContext = createRuntimeContext(options);
|
|
13
|
+
runtime = runtimeContext.runtime;
|
|
14
|
+
migrationPromise = null;
|
|
15
|
+
return runtimeContext;
|
|
16
|
+
}
|
|
17
|
+
export function currentAgentAppRuntime() {
|
|
18
|
+
return runtimeContext;
|
|
19
|
+
}
|
|
20
|
+
export function appVersion() {
|
|
21
|
+
return runtimeContext.appVersion;
|
|
22
|
+
}
|
|
23
|
+
export async function ensureRuntime() {
|
|
24
|
+
migrationPromise ??= migrateLegacyRuntime();
|
|
25
|
+
await migrationPromise;
|
|
26
|
+
await runtime.ensure();
|
|
27
|
+
await splitMonolithicConfig();
|
|
28
|
+
return runtime;
|
|
29
|
+
}
|
|
30
|
+
async function migrateLegacyRuntime() {
|
|
31
|
+
if (!runtimeContext.legacyRuntime)
|
|
32
|
+
return;
|
|
33
|
+
await migrateLegacyConfigDirectory(runtimeContext.legacyRuntime);
|
|
34
|
+
for (const key of ["data", "cache", "logs"]) {
|
|
35
|
+
await moveDirectoryIfNeeded(runtimeContext.legacyRuntime.dirs[key], runtime.dirs[key]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function moveDirectoryIfNeeded(from, to) {
|
|
39
|
+
if (from === to || !(await isDirectory(from)))
|
|
40
|
+
return;
|
|
41
|
+
await mkdir(path.dirname(to), { recursive: true });
|
|
42
|
+
if (await pathExists(to)) {
|
|
43
|
+
await cp(from, to, { recursive: true, errorOnExist: false, force: false });
|
|
44
|
+
await rm(from, { recursive: true, force: true });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
await rename(from, to);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error?.code !== "EXDEV")
|
|
52
|
+
throw error;
|
|
53
|
+
await cp(from, to, { recursive: true, errorOnExist: false });
|
|
54
|
+
await rm(from, { recursive: true, force: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function migrateLegacyConfigDirectory(legacyRuntime) {
|
|
58
|
+
const from = legacyRuntime.dirs.config;
|
|
59
|
+
const to = runtime.dirs.config;
|
|
60
|
+
if (from === to || !(await isDirectory(from)))
|
|
61
|
+
return;
|
|
62
|
+
await mkdir(to, { recursive: true });
|
|
63
|
+
const legacyProfilesPath = path.join(from, "profiles.json");
|
|
64
|
+
const profilesPath = path.join(to, "profiles.json");
|
|
65
|
+
const legacyRaw = await readJSONRecord(legacyProfilesPath);
|
|
66
|
+
if (legacyRaw) {
|
|
67
|
+
const nextRaw = await readJSONRecord(profilesPath) ?? {};
|
|
68
|
+
const legacyProfiles = recordValue(legacyRaw.profiles);
|
|
69
|
+
const nextProfiles = recordValue(nextRaw.profiles);
|
|
70
|
+
const mergedProfiles = { ...legacyProfiles, ...nextProfiles };
|
|
71
|
+
const activeProfile = typeof nextRaw.activeProfile === "string"
|
|
72
|
+
? nextRaw.activeProfile
|
|
73
|
+
: typeof legacyRaw.activeProfile === "string"
|
|
74
|
+
? legacyRaw.activeProfile
|
|
75
|
+
: "default";
|
|
76
|
+
await writeJSON(profilesPath, { ...nextRaw, activeProfile, profiles: mergedProfiles });
|
|
77
|
+
const configurationPath = path.join(to, "configuration.json");
|
|
78
|
+
if ("workbench" in legacyRaw && !(await pathExists(configurationPath))) {
|
|
79
|
+
await writeJSON(configurationPath, { workbench: recordValue(legacyRaw.workbench) });
|
|
80
|
+
}
|
|
81
|
+
const conversationsPath = path.join(to, "conversations.json");
|
|
82
|
+
if ("conversations" in legacyRaw && !(await pathExists(conversationsPath))) {
|
|
83
|
+
await writeJSON(conversationsPath, { conversations: recordValue(legacyRaw.conversations) });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
await copyFileIfMissing(path.join(from, "configuration.json"), path.join(to, "configuration.json"));
|
|
87
|
+
await copyFileIfMissing(path.join(from, "conversations.json"), path.join(to, "conversations.json"));
|
|
88
|
+
await rm(from, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
async function splitMonolithicConfig() {
|
|
91
|
+
const profilesPath = path.join(runtime.dirs.config, "profiles.json");
|
|
92
|
+
const raw = await readJSONRecord(profilesPath);
|
|
93
|
+
if (!raw)
|
|
94
|
+
return;
|
|
95
|
+
let changed = false;
|
|
96
|
+
if ("workbench" in raw && !(await pathExists(path.join(runtime.dirs.config, "configuration.json")))) {
|
|
97
|
+
await writeJSON(path.join(runtime.dirs.config, "configuration.json"), { workbench: raw.workbench && typeof raw.workbench === "object" ? raw.workbench : {} });
|
|
98
|
+
}
|
|
99
|
+
if ("workbench" in raw) {
|
|
100
|
+
delete raw.workbench;
|
|
101
|
+
changed = true;
|
|
102
|
+
}
|
|
103
|
+
if ("conversations" in raw && !(await pathExists(path.join(runtime.dirs.config, "conversations.json")))) {
|
|
104
|
+
await writeJSON(path.join(runtime.dirs.config, "conversations.json"), {
|
|
105
|
+
conversations: raw.conversations && typeof raw.conversations === "object" ? raw.conversations : {},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if ("conversations" in raw) {
|
|
109
|
+
delete raw.conversations;
|
|
110
|
+
changed = true;
|
|
111
|
+
}
|
|
112
|
+
if (changed) {
|
|
113
|
+
await writeJSON(profilesPath, raw);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function recordValue(value) {
|
|
117
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
118
|
+
}
|
|
119
|
+
async function readJSONRecord(file) {
|
|
120
|
+
try {
|
|
121
|
+
const value = JSON.parse(await readFile(file, "utf8"));
|
|
122
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (error?.code === "ENOENT")
|
|
126
|
+
return null;
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function writeJSON(file, value) {
|
|
131
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
132
|
+
await writeFile(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
133
|
+
}
|
|
134
|
+
async function copyFileIfMissing(from, to) {
|
|
135
|
+
if (!(await pathExists(from)) || await pathExists(to))
|
|
136
|
+
return;
|
|
137
|
+
await mkdir(path.dirname(to), { recursive: true });
|
|
138
|
+
await cp(from, to, { errorOnExist: true, force: false });
|
|
139
|
+
}
|
|
140
|
+
async function pathExists(file) {
|
|
141
|
+
try {
|
|
142
|
+
await stat(file);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (error?.code === "ENOENT")
|
|
147
|
+
return false;
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function isDirectory(file) {
|
|
152
|
+
try {
|
|
153
|
+
return (await stat(file)).isDirectory();
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (error?.code === "ENOENT")
|
|
157
|
+
return false;
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function createRuntimeContext(options = {}) {
|
|
162
|
+
const appName = options.appName || defaultAppName;
|
|
163
|
+
const appAuthor = options.appAuthor || defaultAppAuthor;
|
|
164
|
+
const appVersion = options.appVersion || defaultAppVersion;
|
|
165
|
+
const runtimeOptions = { ...options, appName, appAuthor };
|
|
166
|
+
delete runtimeOptions.appVersion;
|
|
167
|
+
delete runtimeOptions.legacyAppName;
|
|
168
|
+
const legacyAppName = "legacyAppName" in options ? options.legacyAppName : defaultLegacyAppName;
|
|
169
|
+
return {
|
|
170
|
+
appName,
|
|
171
|
+
appAuthor,
|
|
172
|
+
appVersion,
|
|
173
|
+
legacyAppName,
|
|
174
|
+
runtime: createLocalRuntime(runtimeOptions),
|
|
175
|
+
legacyRuntime: legacyAppName ? createLocalRuntime({ ...runtimeOptions, appName: legacyAppName }) : undefined,
|
|
176
|
+
};
|
|
177
|
+
}
|
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface UpdateCheckResult {
|
|
2
|
+
current: string;
|
|
3
|
+
latest: string;
|
|
4
|
+
packageName: string;
|
|
5
|
+
updateAvailable: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface UpdateCheckOptions {
|
|
8
|
+
currentVersion?: string;
|
|
9
|
+
packageName?: string;
|
|
10
|
+
registryURL?: string;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function checkForUpdate(options?: UpdateCheckOptions): Promise<UpdateCheckResult | null>;
|
|
15
|
+
export declare function formatUpdateNotice(result: UpdateCheckResult): string;
|
|
16
|
+
export declare function compareVersions(a: string, b: string): number;
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { appVersion } from "./runtime/index.js";
|
|
2
|
+
const defaultPackageName = "@agent-api/cli";
|
|
3
|
+
const defaultRegistryURL = "https://registry.npmjs.org";
|
|
4
|
+
export async function checkForUpdate(options = {}) {
|
|
5
|
+
const packageName = options.packageName || process.env.AGENT_TUI_UPDATE_PACKAGE || defaultPackageName;
|
|
6
|
+
const current = options.currentVersion || appVersion();
|
|
7
|
+
const registryURL = (options.registryURL || process.env.AGENT_TUI_NPM_REGISTRY || defaultRegistryURL).replace(/\/+$/, "");
|
|
8
|
+
const timeoutMs = options.timeoutMs ?? 1500;
|
|
9
|
+
const controller = new AbortController();
|
|
10
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
11
|
+
const signal = combineSignals(options.signal, controller.signal);
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch(`${registryURL}/${encodeURIComponent(packageName).replace(/^%40/, "@")}/latest`, {
|
|
14
|
+
headers: { Accept: "application/json" },
|
|
15
|
+
signal,
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok)
|
|
18
|
+
return null;
|
|
19
|
+
const payload = await response.json().catch(() => undefined);
|
|
20
|
+
const latest = typeof payload?.version === "string" ? payload.version : "";
|
|
21
|
+
if (!latest)
|
|
22
|
+
return null;
|
|
23
|
+
return {
|
|
24
|
+
current,
|
|
25
|
+
latest,
|
|
26
|
+
packageName,
|
|
27
|
+
updateAvailable: compareVersions(latest, current) > 0,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function formatUpdateNotice(result) {
|
|
38
|
+
return `Update available: ${result.packageName} ${result.current} -> ${result.latest}. Run: npm install -g ${result.packageName}@latest`;
|
|
39
|
+
}
|
|
40
|
+
export function compareVersions(a, b) {
|
|
41
|
+
const left = parseVersion(a);
|
|
42
|
+
const right = parseVersion(b);
|
|
43
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
44
|
+
const delta = (left[index] ?? 0) - (right[index] ?? 0);
|
|
45
|
+
if (delta !== 0)
|
|
46
|
+
return delta > 0 ? 1 : -1;
|
|
47
|
+
}
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
function parseVersion(version) {
|
|
51
|
+
return version
|
|
52
|
+
.replace(/^[^\d]*/, "")
|
|
53
|
+
.split(/[.-]/)
|
|
54
|
+
.slice(0, 3)
|
|
55
|
+
.map((part) => {
|
|
56
|
+
const parsed = Number.parseInt(part, 10);
|
|
57
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function combineSignals(...signals) {
|
|
61
|
+
const active = signals.filter((signal) => Boolean(signal));
|
|
62
|
+
if (active.length === 1)
|
|
63
|
+
return active[0];
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const abort = () => controller.abort();
|
|
66
|
+
for (const signal of active) {
|
|
67
|
+
if (signal.aborted) {
|
|
68
|
+
controller.abort();
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
72
|
+
}
|
|
73
|
+
return controller.signal;
|
|
74
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { deleteProfile, getAuthStatus, loginWithAPIKey, openBrowserURL, refreshActiveProfileIfNeeded, saveBrowserProfile, startBrowserAuthChallenge, waitForBrowserAuthChallenge, type AuthStatus } from "../profile.js";
|
|
2
|
+
export interface WorkbenchAuthController {
|
|
3
|
+
check(profile?: string, refreshWindowMs?: number): Promise<{
|
|
4
|
+
profileName: string;
|
|
5
|
+
refreshed: boolean;
|
|
6
|
+
}>;
|
|
7
|
+
loginAPIKey(input: {
|
|
8
|
+
profile: string;
|
|
9
|
+
baseURL: string;
|
|
10
|
+
apiKey: string;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
profileName: string;
|
|
13
|
+
}>;
|
|
14
|
+
loginBrowser(input: {
|
|
15
|
+
profile: string;
|
|
16
|
+
baseURL: string;
|
|
17
|
+
clientName?: string;
|
|
18
|
+
onChallenge?: (challenge: {
|
|
19
|
+
url: string;
|
|
20
|
+
code: string;
|
|
21
|
+
}) => void;
|
|
22
|
+
onStatus?: (status: string) => void;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
profileName: string;
|
|
25
|
+
}>;
|
|
26
|
+
deleteProfile(name: string): Promise<void>;
|
|
27
|
+
statusText(profile?: string): Promise<string>;
|
|
28
|
+
refresh(profile?: string, refreshWindowMs?: number): Promise<{
|
|
29
|
+
refreshed: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export interface WorkbenchAuthControllerOptions {
|
|
33
|
+
refreshActiveProfileIfNeededImpl?: typeof refreshActiveProfileIfNeeded;
|
|
34
|
+
loginWithAPIKeyImpl?: typeof loginWithAPIKey;
|
|
35
|
+
startBrowserAuthChallengeImpl?: typeof startBrowserAuthChallenge;
|
|
36
|
+
openBrowserURLImpl?: typeof openBrowserURL;
|
|
37
|
+
waitForBrowserAuthChallengeImpl?: typeof waitForBrowserAuthChallenge;
|
|
38
|
+
saveBrowserProfileImpl?: typeof saveBrowserProfile;
|
|
39
|
+
deleteProfileImpl?: typeof deleteProfile;
|
|
40
|
+
getAuthStatusImpl?: typeof getAuthStatus;
|
|
41
|
+
}
|
|
42
|
+
export declare function createWorkbenchAuthController(options?: WorkbenchAuthControllerOptions): WorkbenchAuthController;
|
|
43
|
+
export declare function authStatusText(status: AuthStatus): string;
|