@agentuity/cli 2.0.5 → 2.0.7
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 +11 -0
- package/dist/cmd/build/patch/otel-llm.js +2 -2
- package/dist/cmd/build/patch/otel-llm.js.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.js +1 -0
- package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
- package/dist/cmd/build/vite/index.d.ts +0 -28
- package/dist/cmd/build/vite/index.d.ts.map +1 -1
- package/dist/cmd/build/vite/index.js +1 -104
- package/dist/cmd/build/vite/index.js.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.js +8 -2
- package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts +2 -0
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js +5 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.d.ts +2 -0
- package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.js +2 -1
- package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +143 -2
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/cloud/task/close.d.ts +3 -0
- package/dist/cmd/cloud/task/close.d.ts.map +1 -0
- package/dist/cmd/cloud/task/close.js +286 -0
- package/dist/cmd/cloud/task/close.js.map +1 -0
- package/dist/cmd/cloud/task/delete.d.ts +1 -5
- package/dist/cmd/cloud/task/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/task/delete.js +15 -38
- package/dist/cmd/cloud/task/delete.js.map +1 -1
- package/dist/cmd/cloud/task/index.d.ts.map +1 -1
- package/dist/cmd/cloud/task/index.js +10 -0
- package/dist/cmd/cloud/task/index.js.map +1 -1
- package/dist/cmd/cloud/task/list.d.ts.map +1 -1
- package/dist/cmd/cloud/task/list.js +97 -3
- package/dist/cmd/cloud/task/list.js.map +1 -1
- package/dist/cmd/cloud/task/util.d.ts +10 -0
- package/dist/cmd/cloud/task/util.d.ts.map +1 -1
- package/dist/cmd/cloud/task/util.js +47 -3
- package/dist/cmd/cloud/task/util.js.map +1 -1
- package/dist/cmd/coder/config/index.d.ts +2 -0
- package/dist/cmd/coder/config/index.d.ts.map +1 -0
- package/dist/cmd/coder/config/index.js +20 -0
- package/dist/cmd/coder/config/index.js.map +1 -0
- package/dist/cmd/coder/config/set.d.ts +2 -0
- package/dist/cmd/coder/config/set.d.ts.map +1 -0
- package/dist/cmd/coder/config/set.js +100 -0
- package/dist/cmd/coder/config/set.js.map +1 -0
- package/dist/cmd/coder/hub-url.d.ts +21 -10
- package/dist/cmd/coder/hub-url.d.ts.map +1 -1
- package/dist/cmd/coder/hub-url.js +97 -55
- package/dist/cmd/coder/hub-url.js.map +1 -1
- package/dist/cmd/coder/index.d.ts.map +1 -1
- package/dist/cmd/coder/index.js +6 -1
- package/dist/cmd/coder/index.js.map +1 -1
- package/dist/cmd/coder/inspect.d.ts.map +1 -1
- package/dist/cmd/coder/inspect.js +15 -7
- package/dist/cmd/coder/inspect.js.map +1 -1
- package/dist/cmd/coder/list.d.ts.map +1 -1
- package/dist/cmd/coder/list.js +14 -7
- package/dist/cmd/coder/list.js.map +1 -1
- package/dist/cmd/coder/start.d.ts.map +1 -1
- package/dist/cmd/coder/start.js +38 -23
- package/dist/cmd/coder/start.js.map +1 -1
- package/dist/cmd/coder/tui-init.d.ts +4 -1
- package/dist/cmd/coder/tui-init.d.ts.map +1 -1
- package/dist/cmd/coder/tui-init.js +3 -2
- package/dist/cmd/coder/tui-init.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +1 -0
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/dev/sync.js +5 -5
- package/dist/cmd/dev/sync.js.map +1 -1
- package/dist/coder-config.d.ts +14 -0
- package/dist/coder-config.d.ts.map +1 -0
- package/dist/coder-config.js +119 -0
- package/dist/coder-config.js.map +1 -0
- package/dist/coder-hub-url.d.ts +3 -0
- package/dist/coder-hub-url.d.ts.map +1 -0
- package/dist/coder-hub-url.js +32 -0
- package/dist/coder-hub-url.js.map +1 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -0
- package/dist/config.js.map +1 -1
- package/dist/internal-logger.d.ts +4 -0
- package/dist/internal-logger.d.ts.map +1 -1
- package/dist/internal-logger.js +64 -2
- package/dist/internal-logger.js.map +1 -1
- package/dist/keychain.d.ts +3 -0
- package/dist/keychain.d.ts.map +1 -1
- package/dist/keychain.js +47 -28
- package/dist/keychain.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/package.json +6 -6
- package/src/cmd/build/patch/otel-llm.ts +2 -2
- package/src/cmd/build/vite/bun-dev-server.ts +1 -0
- package/src/cmd/build/vite/index.ts +1 -148
- package/src/cmd/build/vite/metadata-generator.ts +8 -2
- package/src/cmd/build/vite/vite-asset-server-config.ts +16 -1
- package/src/cmd/build/vite/vite-asset-server.ts +4 -0
- package/src/cmd/build/vite/vite-builder.ts +171 -9
- package/src/cmd/cloud/task/close.ts +319 -0
- package/src/cmd/cloud/task/delete.ts +15 -43
- package/src/cmd/cloud/task/index.ts +10 -0
- package/src/cmd/cloud/task/list.ts +111 -4
- package/src/cmd/cloud/task/util.ts +59 -5
- package/src/cmd/coder/config/index.ts +20 -0
- package/src/cmd/coder/config/set.ts +112 -0
- package/src/cmd/coder/hub-url.ts +147 -53
- package/src/cmd/coder/index.ts +6 -1
- package/src/cmd/coder/inspect.ts +33 -10
- package/src/cmd/coder/list.ts +33 -10
- package/src/cmd/coder/start.ts +62 -26
- package/src/cmd/coder/tui-init.ts +7 -2
- package/src/cmd/dev/index.ts +1 -0
- package/src/cmd/dev/sync.ts +5 -5
- package/src/coder-config.ts +141 -0
- package/src/coder-hub-url.ts +32 -0
- package/src/config.ts +13 -0
- package/src/internal-logger.ts +83 -2
- package/src/keychain.ts +68 -39
- package/src/types.ts +10 -0
package/src/cmd/coder/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createCommand } from '../../types';
|
|
2
|
+
import { configSubcommand } from './config';
|
|
2
3
|
import { listSubcommand } from './list';
|
|
3
4
|
import { inspectSubcommand } from './inspect';
|
|
4
5
|
import { startSubcommand } from './start';
|
|
@@ -21,7 +22,11 @@ export const command = createCommand({
|
|
|
21
22
|
command: getCommand('coder inspect <session-id>'),
|
|
22
23
|
description: 'Show detailed session information',
|
|
23
24
|
},
|
|
25
|
+
{
|
|
26
|
+
command: getCommand('coder config set url https://hub.example.com'),
|
|
27
|
+
description: 'Set the default Coder Hub URL for this profile',
|
|
28
|
+
},
|
|
24
29
|
],
|
|
25
|
-
subcommands: [startSubcommand, listSubcommand, inspectSubcommand],
|
|
30
|
+
subcommands: [startSubcommand, listSubcommand, inspectSubcommand, configSubcommand],
|
|
26
31
|
optional: { auth: true },
|
|
27
32
|
});
|
package/src/cmd/coder/inspect.ts
CHANGED
|
@@ -3,7 +3,17 @@ import { createSubcommand } from '../../types';
|
|
|
3
3
|
import * as tui from '../../tui';
|
|
4
4
|
import { getCommand } from '../../command-prefix';
|
|
5
5
|
import { ErrorCode } from '../../errors';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
clearStoredHubApiKeyOnUnauthorized,
|
|
8
|
+
formatHubUnauthorizedMessage,
|
|
9
|
+
formatMissingHubUrlMessage,
|
|
10
|
+
getHubResponseErrorMessage,
|
|
11
|
+
getHubUrlSetupGuidance,
|
|
12
|
+
hubFetchHeaders,
|
|
13
|
+
isHubUnauthorizedStatus,
|
|
14
|
+
resolveHubApiKey,
|
|
15
|
+
resolveHubUrl,
|
|
16
|
+
} from './hub-url';
|
|
7
17
|
|
|
8
18
|
function formatRelativeTime(isoDate: string): string {
|
|
9
19
|
const diffMs = Date.now() - new Date(isoDate).getTime();
|
|
@@ -52,18 +62,17 @@ export const inspectSubcommand = createSubcommand({
|
|
|
52
62
|
}),
|
|
53
63
|
},
|
|
54
64
|
async handler(ctx) {
|
|
55
|
-
const { args, options, opts } = ctx;
|
|
65
|
+
const { args, options, opts, config } = ctx;
|
|
56
66
|
const sessionId = args.session_id;
|
|
57
|
-
const hubUrl = await resolveHubUrl(opts?.hubUrl);
|
|
67
|
+
const hubUrl = await resolveHubUrl(opts?.hubUrl, config);
|
|
58
68
|
|
|
59
69
|
if (!hubUrl) {
|
|
60
|
-
tui.fatal(
|
|
61
|
-
'Could not find a running Coder Hub.\n\nEither:\n - Start the Hub with: bun run dev\n - Set AGENTUITY_CODER_HUB_URL environment variable\n - Pass --hub-url flag',
|
|
62
|
-
ErrorCode.NETWORK_ERROR
|
|
63
|
-
);
|
|
70
|
+
tui.fatal(formatMissingHubUrlMessage(), ErrorCode.NETWORK_ERROR);
|
|
64
71
|
return;
|
|
65
72
|
}
|
|
66
73
|
|
|
74
|
+
const resolvedHubApiKey = await resolveHubApiKey(config);
|
|
75
|
+
|
|
67
76
|
let data: {
|
|
68
77
|
sessionId: string;
|
|
69
78
|
label: string;
|
|
@@ -104,9 +113,22 @@ export const inspectSubcommand = createSubcommand({
|
|
|
104
113
|
|
|
105
114
|
try {
|
|
106
115
|
const resp = await fetch(`${hubUrl}/api/hub/session/${encodeURIComponent(sessionId)}`, {
|
|
107
|
-
headers: hubFetchHeaders(),
|
|
116
|
+
headers: hubFetchHeaders(undefined, resolvedHubApiKey.apiKey),
|
|
108
117
|
signal: AbortSignal.timeout(10_000),
|
|
109
118
|
});
|
|
119
|
+
if (isHubUnauthorizedStatus(resp.status)) {
|
|
120
|
+
const message = await getHubResponseErrorMessage(resp);
|
|
121
|
+
const clearedStoredKey = await clearStoredHubApiKeyOnUnauthorized(
|
|
122
|
+
resp.status,
|
|
123
|
+
resolvedHubApiKey,
|
|
124
|
+
config
|
|
125
|
+
);
|
|
126
|
+
tui.fatal(
|
|
127
|
+
formatHubUnauthorizedMessage(hubUrl, message, { clearedStoredKey }),
|
|
128
|
+
ErrorCode.API_ERROR
|
|
129
|
+
);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
110
132
|
if (resp.status === 404) {
|
|
111
133
|
tui.fatal(`Session not found: ${sessionId}`, ErrorCode.RESOURCE_NOT_FOUND);
|
|
112
134
|
return;
|
|
@@ -116,8 +138,9 @@ export const inspectSubcommand = createSubcommand({
|
|
|
116
138
|
return;
|
|
117
139
|
}
|
|
118
140
|
if (!resp.ok) {
|
|
141
|
+
const message = await getHubResponseErrorMessage(resp);
|
|
119
142
|
tui.fatal(
|
|
120
|
-
`Hub returned ${resp.status}: ${
|
|
143
|
+
`Hub returned ${resp.status}: ${message}. Is the Coder Hub running at ${hubUrl}?`,
|
|
121
144
|
ErrorCode.API_ERROR
|
|
122
145
|
);
|
|
123
146
|
return;
|
|
@@ -126,7 +149,7 @@ export const inspectSubcommand = createSubcommand({
|
|
|
126
149
|
} catch (err) {
|
|
127
150
|
const msg = err instanceof Error ? err.message : String(err);
|
|
128
151
|
tui.fatal(
|
|
129
|
-
`Could not connect to Coder Hub at ${hubUrl}: ${msg}\n\
|
|
152
|
+
`Could not connect to Coder Hub at ${hubUrl}: ${msg}\n\n${getHubUrlSetupGuidance()}`,
|
|
130
153
|
ErrorCode.NETWORK_ERROR
|
|
131
154
|
);
|
|
132
155
|
return;
|
package/src/cmd/coder/list.ts
CHANGED
|
@@ -3,7 +3,17 @@ import { createSubcommand } from '../../types';
|
|
|
3
3
|
import * as tui from '../../tui';
|
|
4
4
|
import { getCommand } from '../../command-prefix';
|
|
5
5
|
import { ErrorCode } from '../../errors';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
clearStoredHubApiKeyOnUnauthorized,
|
|
8
|
+
formatHubUnauthorizedMessage,
|
|
9
|
+
formatMissingHubUrlMessage,
|
|
10
|
+
getHubResponseErrorMessage,
|
|
11
|
+
getHubUrlSetupGuidance,
|
|
12
|
+
hubFetchHeaders,
|
|
13
|
+
isHubUnauthorizedStatus,
|
|
14
|
+
resolveHubApiKey,
|
|
15
|
+
resolveHubUrl,
|
|
16
|
+
} from './hub-url';
|
|
7
17
|
|
|
8
18
|
function formatRelativeTime(isoDate: string): string {
|
|
9
19
|
const diffMs = Date.now() - new Date(isoDate).getTime();
|
|
@@ -54,17 +64,16 @@ export const listSubcommand = createSubcommand({
|
|
|
54
64
|
response: SessionListResponseSchema,
|
|
55
65
|
},
|
|
56
66
|
async handler(ctx) {
|
|
57
|
-
const { options, opts } = ctx;
|
|
58
|
-
const hubUrl = await resolveHubUrl(opts?.hubUrl);
|
|
67
|
+
const { options, opts, config } = ctx;
|
|
68
|
+
const hubUrl = await resolveHubUrl(opts?.hubUrl, config);
|
|
59
69
|
|
|
60
70
|
if (!hubUrl) {
|
|
61
|
-
tui.fatal(
|
|
62
|
-
'Could not find a running Coder Hub.\n\nEither:\n - Start the Hub with: bun run dev\n - Set AGENTUITY_CODER_HUB_URL environment variable\n - Pass --hub-url flag',
|
|
63
|
-
ErrorCode.NETWORK_ERROR
|
|
64
|
-
);
|
|
71
|
+
tui.fatal(formatMissingHubUrlMessage(), ErrorCode.NETWORK_ERROR);
|
|
65
72
|
return [];
|
|
66
73
|
}
|
|
67
74
|
|
|
75
|
+
const resolvedHubApiKey = await resolveHubApiKey(config);
|
|
76
|
+
|
|
68
77
|
let data: {
|
|
69
78
|
sessions: {
|
|
70
79
|
websocket: Array<{
|
|
@@ -85,12 +94,26 @@ export const listSubcommand = createSubcommand({
|
|
|
85
94
|
|
|
86
95
|
try {
|
|
87
96
|
const resp = await fetch(`${hubUrl}/api/hub/sessions`, {
|
|
88
|
-
headers: hubFetchHeaders(),
|
|
97
|
+
headers: hubFetchHeaders(undefined, resolvedHubApiKey.apiKey),
|
|
89
98
|
signal: AbortSignal.timeout(10_000),
|
|
90
99
|
});
|
|
91
100
|
if (!resp.ok) {
|
|
101
|
+
const message = await getHubResponseErrorMessage(resp);
|
|
102
|
+
if (isHubUnauthorizedStatus(resp.status)) {
|
|
103
|
+
const clearedStoredKey = await clearStoredHubApiKeyOnUnauthorized(
|
|
104
|
+
resp.status,
|
|
105
|
+
resolvedHubApiKey,
|
|
106
|
+
config
|
|
107
|
+
);
|
|
108
|
+
tui.fatal(
|
|
109
|
+
formatHubUnauthorizedMessage(hubUrl, message, { clearedStoredKey }),
|
|
110
|
+
ErrorCode.API_ERROR
|
|
111
|
+
);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
|
|
92
115
|
tui.fatal(
|
|
93
|
-
`Hub returned ${resp.status}: ${
|
|
116
|
+
`Hub returned ${resp.status}: ${message}. Is the Coder Hub running at ${hubUrl}?`,
|
|
94
117
|
ErrorCode.API_ERROR
|
|
95
118
|
);
|
|
96
119
|
return [];
|
|
@@ -99,7 +122,7 @@ export const listSubcommand = createSubcommand({
|
|
|
99
122
|
} catch (err) {
|
|
100
123
|
const msg = err instanceof Error ? err.message : String(err);
|
|
101
124
|
tui.fatal(
|
|
102
|
-
`Could not connect to Coder Hub at ${hubUrl}: ${msg}\n\
|
|
125
|
+
`Could not connect to Coder Hub at ${hubUrl}: ${msg}\n\n${getHubUrlSetupGuidance()}`,
|
|
103
126
|
ErrorCode.NETWORK_ERROR
|
|
104
127
|
);
|
|
105
128
|
return [];
|
package/src/cmd/coder/start.ts
CHANGED
|
@@ -5,7 +5,17 @@ import { createSubcommand } from '../../types';
|
|
|
5
5
|
import * as tui from '../../tui';
|
|
6
6
|
import { getCommand } from '../../command-prefix';
|
|
7
7
|
import { ErrorCode } from '../../errors';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
clearStoredHubApiKeyOnUnauthorized,
|
|
10
|
+
formatHubUnauthorizedMessage,
|
|
11
|
+
formatMissingHubUrlMessage,
|
|
12
|
+
getHubResponseErrorMessage,
|
|
13
|
+
hubFetchHeaders,
|
|
14
|
+
isHubUnauthorizedStatus,
|
|
15
|
+
resolveHubApiKey,
|
|
16
|
+
resolveHubUrl,
|
|
17
|
+
toHubWsUrl,
|
|
18
|
+
} from './hub-url';
|
|
9
19
|
import { probeHubInitAccess } from './tui-init';
|
|
10
20
|
|
|
11
21
|
/**
|
|
@@ -127,24 +137,44 @@ export const startSubcommand = createSubcommand({
|
|
|
127
137
|
}),
|
|
128
138
|
},
|
|
129
139
|
async handler(ctx) {
|
|
130
|
-
const { opts, options } = ctx;
|
|
140
|
+
const { opts, options, config } = ctx;
|
|
131
141
|
|
|
132
142
|
// Resolve Hub URL
|
|
133
|
-
const hubHttpUrl = await resolveHubUrl(opts?.hubUrl);
|
|
143
|
+
const hubHttpUrl = await resolveHubUrl(opts?.hubUrl, config);
|
|
134
144
|
if (!hubHttpUrl) {
|
|
135
|
-
tui.fatal(
|
|
136
|
-
'Could not find a running Coder Hub.\n\nEither:\n - Start the Hub with: bun run dev\n - Set AGENTUITY_CODER_HUB_URL environment variable\n - Pass --hub-url flag',
|
|
137
|
-
ErrorCode.NETWORK_ERROR
|
|
138
|
-
);
|
|
145
|
+
tui.fatal(formatMissingHubUrlMessage(), ErrorCode.NETWORK_ERROR);
|
|
139
146
|
return;
|
|
140
147
|
}
|
|
141
148
|
const hubWsUrl = toHubWsUrl(hubHttpUrl);
|
|
149
|
+
const resolvedHubApiKey = await resolveHubApiKey(config);
|
|
150
|
+
|
|
151
|
+
const handleUnauthorizedResponse = async (
|
|
152
|
+
response: Response,
|
|
153
|
+
errorCode: ErrorCode = ErrorCode.NETWORK_ERROR
|
|
154
|
+
): Promise<void> => {
|
|
155
|
+
const message = await getHubResponseErrorMessage(response);
|
|
156
|
+
const clearedStoredKey = await clearStoredHubApiKeyOnUnauthorized(
|
|
157
|
+
response.status,
|
|
158
|
+
resolvedHubApiKey,
|
|
159
|
+
config
|
|
160
|
+
);
|
|
161
|
+
tui.fatal(
|
|
162
|
+
formatHubUnauthorizedMessage(hubHttpUrl, message, { clearedStoredKey }),
|
|
163
|
+
errorCode
|
|
164
|
+
);
|
|
165
|
+
};
|
|
142
166
|
|
|
143
|
-
const initProbe = await probeHubInitAccess(hubHttpUrl
|
|
167
|
+
const initProbe = await probeHubInitAccess(hubHttpUrl, {
|
|
168
|
+
apiKey: resolvedHubApiKey.apiKey,
|
|
169
|
+
});
|
|
144
170
|
if (!initProbe.ok) {
|
|
145
171
|
if (initProbe.code === 'unauthorized') {
|
|
172
|
+
const clearedStoredKey =
|
|
173
|
+
resolvedHubApiKey.source === 'stored'
|
|
174
|
+
? await clearStoredHubApiKeyOnUnauthorized(401, resolvedHubApiKey, config)
|
|
175
|
+
: false;
|
|
146
176
|
tui.fatal(
|
|
147
|
-
|
|
177
|
+
formatHubUnauthorizedMessage(hubHttpUrl, initProbe.message, { clearedStoredKey }),
|
|
148
178
|
ErrorCode.NETWORK_ERROR
|
|
149
179
|
);
|
|
150
180
|
return;
|
|
@@ -192,11 +222,15 @@ export const startSubcommand = createSubcommand({
|
|
|
192
222
|
message: 'Fetching connectable sessions…',
|
|
193
223
|
callback: async () => {
|
|
194
224
|
const resp = await fetch(`${hubHttpUrl}/api/hub/sessions/connectable`, {
|
|
195
|
-
headers: hubFetchHeaders(),
|
|
225
|
+
headers: hubFetchHeaders(undefined, resolvedHubApiKey.apiKey),
|
|
196
226
|
signal: AbortSignal.timeout(10_000),
|
|
197
227
|
});
|
|
228
|
+
if (isHubUnauthorizedStatus(resp.status)) {
|
|
229
|
+
await handleUnauthorizedResponse(resp);
|
|
230
|
+
throw new Error('Hub authentication failed');
|
|
231
|
+
}
|
|
198
232
|
if (!resp.ok) {
|
|
199
|
-
throw new Error(`${resp.status} ${resp
|
|
233
|
+
throw new Error(`${resp.status} ${await getHubResponseErrorMessage(resp)}`);
|
|
200
234
|
}
|
|
201
235
|
const data = (await resp.json()) as { sessions: SessionInfo[] };
|
|
202
236
|
return data.sessions;
|
|
@@ -205,7 +239,7 @@ export const startSubcommand = createSubcommand({
|
|
|
205
239
|
|
|
206
240
|
if (sessions.length === 0) {
|
|
207
241
|
tui.fatal(
|
|
208
|
-
|
|
242
|
+
`No connectable sandbox sessions found.\n\nCreate one with:\n ${getCommand('coder start --sandbox "your task"')}`,
|
|
209
243
|
ErrorCode.CONFIG_INVALID
|
|
210
244
|
);
|
|
211
245
|
return;
|
|
@@ -229,7 +263,7 @@ export const startSubcommand = createSubcommand({
|
|
|
229
263
|
});
|
|
230
264
|
} catch (err) {
|
|
231
265
|
const msg = err instanceof Error ? err.message : String(err);
|
|
232
|
-
if (msg === 'User cancelled') return;
|
|
266
|
+
if (msg === 'User cancelled' || msg === 'Hub authentication failed') return;
|
|
233
267
|
tui.fatal(`Failed to fetch connectable sessions: ${msg}`, ErrorCode.NETWORK_ERROR);
|
|
234
268
|
return;
|
|
235
269
|
}
|
|
@@ -273,12 +307,6 @@ export const startSubcommand = createSubcommand({
|
|
|
273
307
|
return;
|
|
274
308
|
}
|
|
275
309
|
|
|
276
|
-
const hubHttpUrl = await resolveHubUrl(opts?.hubUrl);
|
|
277
|
-
if (!hubHttpUrl) {
|
|
278
|
-
tui.fatal('Could not find Hub URL for sandbox creation.', ErrorCode.NETWORK_ERROR);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
310
|
// Build request body
|
|
283
311
|
const body: Record<string, unknown> = { task };
|
|
284
312
|
if (opts?.repo) {
|
|
@@ -293,14 +321,20 @@ export const startSubcommand = createSubcommand({
|
|
|
293
321
|
try {
|
|
294
322
|
const resp = await fetch(`${hubHttpUrl}/api/hub/session`, {
|
|
295
323
|
method: 'POST',
|
|
296
|
-
headers: hubFetchHeaders(
|
|
324
|
+
headers: hubFetchHeaders(
|
|
325
|
+
{ 'Content-Type': 'application/json' },
|
|
326
|
+
resolvedHubApiKey.apiKey
|
|
327
|
+
),
|
|
297
328
|
body: JSON.stringify(body),
|
|
298
329
|
signal: AbortSignal.timeout(10_000),
|
|
299
330
|
});
|
|
331
|
+
if (isHubUnauthorizedStatus(resp.status)) {
|
|
332
|
+
await handleUnauthorizedResponse(resp);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
300
335
|
if (!resp.ok) {
|
|
301
|
-
const errText = await resp.text();
|
|
302
336
|
tui.fatal(
|
|
303
|
-
`Failed to create sandbox session: ${resp.status} ${
|
|
337
|
+
`Failed to create sandbox session: ${resp.status} ${await getHubResponseErrorMessage(resp)}`,
|
|
304
338
|
ErrorCode.NETWORK_ERROR
|
|
305
339
|
);
|
|
306
340
|
return;
|
|
@@ -328,9 +362,13 @@ export const startSubcommand = createSubcommand({
|
|
|
328
362
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
329
363
|
try {
|
|
330
364
|
const pollResp = await fetch(`${hubHttpUrl}/api/hub/session/${sessionId}`, {
|
|
331
|
-
headers: hubFetchHeaders(),
|
|
365
|
+
headers: hubFetchHeaders(undefined, resolvedHubApiKey.apiKey),
|
|
332
366
|
signal: AbortSignal.timeout(5_000),
|
|
333
367
|
});
|
|
368
|
+
if (isHubUnauthorizedStatus(pollResp.status)) {
|
|
369
|
+
await handleUnauthorizedResponse(pollResp);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
334
372
|
if (pollResp.ok) {
|
|
335
373
|
const data = (await pollResp.json()) as {
|
|
336
374
|
participants?: Array<{ role: string }>;
|
|
@@ -374,9 +412,7 @@ export const startSubcommand = createSubcommand({
|
|
|
374
412
|
...(process.env as Record<string, string>),
|
|
375
413
|
AGENTUITY_CODER_HUB_URL: hubWsUrl,
|
|
376
414
|
};
|
|
377
|
-
|
|
378
|
-
const cliApiKey = process.env.AGENTUITY_CODER_API_KEY;
|
|
379
|
-
if (cliApiKey) env.AGENTUITY_CODER_API_KEY = cliApiKey;
|
|
415
|
+
if (resolvedHubApiKey.apiKey) env.AGENTUITY_CODER_API_KEY = resolvedHubApiKey.apiKey;
|
|
380
416
|
|
|
381
417
|
if (opts?.agent) {
|
|
382
418
|
env.AGENTUITY_CODER_AGENT = opts.agent;
|
|
@@ -21,11 +21,16 @@ function normalizeErrorMessage(payload: unknown, fallback: string): string {
|
|
|
21
21
|
|
|
22
22
|
export async function probeHubInitAccess(
|
|
23
23
|
hubHttpUrl: string,
|
|
24
|
-
|
|
24
|
+
options?: {
|
|
25
|
+
apiKey?: string | null;
|
|
26
|
+
fetchImpl?: typeof fetch;
|
|
27
|
+
}
|
|
25
28
|
): Promise<HubInitProbeResult> {
|
|
29
|
+
const fetchImpl = options?.fetchImpl ?? fetch;
|
|
30
|
+
|
|
26
31
|
try {
|
|
27
32
|
const response = await fetchImpl(`${hubHttpUrl}/api/hub/init`, {
|
|
28
|
-
headers: hubFetchHeaders({ accept: 'application/json' }),
|
|
33
|
+
headers: hubFetchHeaders({ accept: 'application/json' }, options?.apiKey),
|
|
29
34
|
signal: AbortSignal.timeout(5_000),
|
|
30
35
|
});
|
|
31
36
|
|
package/src/cmd/dev/index.ts
CHANGED
package/src/cmd/dev/sync.ts
CHANGED
|
@@ -169,7 +169,7 @@ class DevmodeSyncService implements IDevmodeSyncService {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
-
this.logger.debug('Previous metadata found with %d
|
|
172
|
+
this.logger.debug('Previous metadata found with %d evaluations', prevEvalCount);
|
|
173
173
|
} else {
|
|
174
174
|
this.logger.debug('No previous metadata, all evals will be treated as new');
|
|
175
175
|
}
|
|
@@ -182,7 +182,7 @@ class DevmodeSyncService implements IDevmodeSyncService {
|
|
|
182
182
|
if (agent.evals) {
|
|
183
183
|
currentEvalCount += agent.evals.length;
|
|
184
184
|
this.logger.debug(
|
|
185
|
-
'[CLI EVAL SYNC] Agent "%s" has %d
|
|
185
|
+
'[CLI EVAL SYNC] Agent "%s" has %d evaluations',
|
|
186
186
|
agent.name,
|
|
187
187
|
agent.evals.length
|
|
188
188
|
);
|
|
@@ -196,7 +196,7 @@ class DevmodeSyncService implements IDevmodeSyncService {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
-
this.logger.debug('[CLI EVAL SYNC] Total current
|
|
199
|
+
this.logger.debug('[CLI EVAL SYNC] Total current evaluations: %d', currentEvalCount);
|
|
200
200
|
|
|
201
201
|
// Get agents and evals to sync using shared diff logic
|
|
202
202
|
const { create: agentsToCreate, delete: agentsToDelete } = getAgentsToSync(
|
|
@@ -241,7 +241,7 @@ class DevmodeSyncService implements IDevmodeSyncService {
|
|
|
241
241
|
}
|
|
242
242
|
if (evalsToCreate.length > 0 || evalsToDelete.length > 0) {
|
|
243
243
|
this.logger.debug(
|
|
244
|
-
'Successfully bulk synced %d
|
|
244
|
+
'Successfully bulk synced %d evaluations to create, %d evaluations to delete',
|
|
245
245
|
evalsToCreate.length,
|
|
246
246
|
evalsToDelete.length
|
|
247
247
|
);
|
|
@@ -369,7 +369,7 @@ class MockDevmodeSyncService implements IDevmodeSyncService {
|
|
|
369
369
|
|
|
370
370
|
if (evalsToCreate.length > 0 || evalsToDelete.length > 0) {
|
|
371
371
|
this.logger.debug(
|
|
372
|
-
'[MOCK] Would make request: POST /cli/devmode/eval with %d
|
|
372
|
+
'[MOCK] Would make request: POST /cli/devmode/eval with %d evaluations to create, %d evaluations to delete',
|
|
373
373
|
evalsToCreate.length,
|
|
374
374
|
evalsToDelete.length
|
|
375
375
|
);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { defaultProfileName, getOrInitConfig, loadConfig, saveConfig } from './config';
|
|
2
|
+
import { normalizeCoderHubHttpUrl } from './coder-hub-url';
|
|
3
|
+
import {
|
|
4
|
+
deleteCoderApiKeyFromKeychain,
|
|
5
|
+
getCoderApiKeyFromKeychain,
|
|
6
|
+
isMacOS,
|
|
7
|
+
saveCoderApiKeyToKeychain,
|
|
8
|
+
} from './keychain';
|
|
9
|
+
import type { Config } from './types';
|
|
10
|
+
|
|
11
|
+
function getProfileName(config?: Config | null): string {
|
|
12
|
+
return config?.name || defaultProfileName;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function pruneCoderConfig(config: Config): void {
|
|
16
|
+
if (!config.coder) return;
|
|
17
|
+
|
|
18
|
+
const nextCoder = { ...config.coder };
|
|
19
|
+
if (!nextCoder.hubUrl) delete nextCoder.hubUrl;
|
|
20
|
+
if (!nextCoder.apiKey) delete nextCoder.apiKey;
|
|
21
|
+
|
|
22
|
+
if (Object.keys(nextCoder).length === 0) {
|
|
23
|
+
delete config.coder;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
config.coder = nextCoder;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function saveCoderHubUrl(
|
|
31
|
+
hubUrl: string
|
|
32
|
+
): Promise<{ profileName: string; hubUrl: string }> {
|
|
33
|
+
const normalized = normalizeCoderHubHttpUrl(hubUrl);
|
|
34
|
+
const config = await getOrInitConfig();
|
|
35
|
+
const profileName = getProfileName(config);
|
|
36
|
+
|
|
37
|
+
config.coder = {
|
|
38
|
+
...config.coder,
|
|
39
|
+
hubUrl: normalized,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
await saveConfig(config);
|
|
43
|
+
return { profileName, hubUrl: normalized };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function getStoredCoderHubUrl(config?: Config | null): Promise<string | null> {
|
|
47
|
+
const loadedConfig = config ?? (await loadConfig());
|
|
48
|
+
const hubUrl = loadedConfig?.coder?.hubUrl?.trim();
|
|
49
|
+
if (!hubUrl) return null;
|
|
50
|
+
return normalizeCoderHubHttpUrl(hubUrl);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function saveCoderApiKey(apiKey: string): Promise<{ profileName: string }> {
|
|
54
|
+
const trimmed = apiKey.trim();
|
|
55
|
+
const config = await getOrInitConfig();
|
|
56
|
+
const profileName = getProfileName(config);
|
|
57
|
+
|
|
58
|
+
if (isMacOS()) {
|
|
59
|
+
try {
|
|
60
|
+
await saveCoderApiKeyToKeychain(profileName, trimmed);
|
|
61
|
+
if (config.coder?.apiKey) {
|
|
62
|
+
config.coder = {
|
|
63
|
+
...config.coder,
|
|
64
|
+
};
|
|
65
|
+
delete config.coder.apiKey;
|
|
66
|
+
}
|
|
67
|
+
pruneCoderConfig(config);
|
|
68
|
+
await saveConfig(config);
|
|
69
|
+
return { profileName };
|
|
70
|
+
} catch {
|
|
71
|
+
// Fall back to config-file storage below.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
config.coder = {
|
|
76
|
+
...config.coder,
|
|
77
|
+
apiKey: trimmed,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
await saveConfig(config);
|
|
81
|
+
return { profileName };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function getStoredCoderApiKey(config?: Config | null): Promise<string | null> {
|
|
85
|
+
const loadedConfig = config ?? (await loadConfig());
|
|
86
|
+
const profileName = getProfileName(loadedConfig);
|
|
87
|
+
|
|
88
|
+
if (isMacOS()) {
|
|
89
|
+
try {
|
|
90
|
+
const keychainValue = await getCoderApiKeyFromKeychain(profileName);
|
|
91
|
+
if (keychainValue) {
|
|
92
|
+
if (loadedConfig?.coder?.apiKey) {
|
|
93
|
+
const configCopy = {
|
|
94
|
+
...loadedConfig,
|
|
95
|
+
coder: {
|
|
96
|
+
...loadedConfig.coder,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
delete configCopy.coder.apiKey;
|
|
100
|
+
pruneCoderConfig(configCopy);
|
|
101
|
+
await saveConfig(configCopy);
|
|
102
|
+
}
|
|
103
|
+
return keychainValue.trim() || null;
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
// Fall back to config-file storage below.
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const storedValue = loadedConfig?.coder?.apiKey?.trim();
|
|
111
|
+
return storedValue || null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function clearStoredCoderApiKey(
|
|
115
|
+
config?: Config | null
|
|
116
|
+
): Promise<{ profileName: string }> {
|
|
117
|
+
const loadedConfig = config ?? (await getOrInitConfig());
|
|
118
|
+
const profileName = getProfileName(loadedConfig);
|
|
119
|
+
|
|
120
|
+
if (isMacOS()) {
|
|
121
|
+
try {
|
|
122
|
+
await deleteCoderApiKeyFromKeychain(profileName);
|
|
123
|
+
} catch {
|
|
124
|
+
// Ignore keychain cleanup errors.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (loadedConfig.coder?.apiKey) {
|
|
129
|
+
const configToSave = {
|
|
130
|
+
...loadedConfig,
|
|
131
|
+
coder: {
|
|
132
|
+
...loadedConfig.coder,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
delete configToSave.coder.apiKey;
|
|
136
|
+
pruneCoderConfig(configToSave);
|
|
137
|
+
await saveConfig(configToSave);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { profileName };
|
|
141
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function normalizeCoderHubHttpUrl(url: string): string {
|
|
2
|
+
let normalized = url.trim().replace(/\/+$/, '');
|
|
3
|
+
|
|
4
|
+
if (normalized.startsWith('ws://')) normalized = 'http://' + normalized.slice(5);
|
|
5
|
+
else if (normalized.startsWith('wss://')) normalized = 'https://' + normalized.slice(6);
|
|
6
|
+
|
|
7
|
+
normalized = normalized.replace(/\/api\/ws\b.*$/, '');
|
|
8
|
+
normalized = normalized.replace(/\/ws\b.*$/, '');
|
|
9
|
+
normalized = normalized.replace(/\/api\/hub\b.*$/, '');
|
|
10
|
+
|
|
11
|
+
return normalized.replace(/\/+$/, '');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function toCoderHubWsUrl(hubHttpUrl: string): string {
|
|
15
|
+
let wsUrl = hubHttpUrl;
|
|
16
|
+
if (wsUrl.startsWith('http://')) wsUrl = 'ws://' + wsUrl.slice(7);
|
|
17
|
+
else if (wsUrl.startsWith('https://')) wsUrl = 'wss://' + wsUrl.slice(8);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const parsed = new URL(wsUrl);
|
|
21
|
+
if (parsed.pathname !== '/api/ws') {
|
|
22
|
+
parsed.pathname = '/api/ws';
|
|
23
|
+
wsUrl = parsed.toString().replace(/\/$/, '');
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
if (!wsUrl.endsWith('/api/ws')) {
|
|
27
|
+
wsUrl = wsUrl.replace(/\/?$/, '/api/ws');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return wsUrl;
|
|
32
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -28,6 +28,14 @@ import { ConfigSchema, ProjectSchema } from './types';
|
|
|
28
28
|
export const defaultProfileName = 'production';
|
|
29
29
|
|
|
30
30
|
export function getDefaultConfigDir(): string {
|
|
31
|
+
const configDirOverride = process.env.AGENTUITY_CONFIG_DIR?.trim();
|
|
32
|
+
if (configDirOverride) {
|
|
33
|
+
if (configDirOverride.startsWith('~/')) {
|
|
34
|
+
return resolve(join(homedir(), configDirOverride.slice(2)));
|
|
35
|
+
}
|
|
36
|
+
return resolve(configDirOverride);
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
return join(homedir(), '.config', 'agentuity');
|
|
32
40
|
}
|
|
33
41
|
|
|
@@ -144,6 +152,11 @@ let cachedConfig: Config | null | undefined;
|
|
|
144
152
|
// Track the resolved config path so saveConfig writes back to the same file
|
|
145
153
|
let cachedConfigPath: string | undefined;
|
|
146
154
|
|
|
155
|
+
export function resetConfigCache(): void {
|
|
156
|
+
cachedConfig = undefined;
|
|
157
|
+
cachedConfigPath = undefined;
|
|
158
|
+
}
|
|
159
|
+
|
|
147
160
|
export async function loadConfig(
|
|
148
161
|
customPath?: string,
|
|
149
162
|
skipCache = false,
|