@agentuity/cli 2.0.7 → 2.0.9
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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/route-discovery.js +6 -0
- package/dist/cmd/build/vite/route-discovery.js.map +1 -1
- package/dist/cmd/cloud/sandbox/fs/rm.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/fs/rm.js +9 -3
- package/dist/cmd/cloud/sandbox/fs/rm.js.map +1 -1
- package/dist/cmd/cloud/sandbox/fs/rmdir.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/fs/rmdir.js +9 -3
- package/dist/cmd/cloud/sandbox/fs/rmdir.js.map +1 -1
- package/dist/cmd/coder/archive.d.ts +2 -0
- package/dist/cmd/coder/archive.d.ts.map +1 -0
- package/dist/cmd/coder/archive.js +57 -0
- package/dist/cmd/coder/archive.js.map +1 -0
- package/dist/cmd/coder/create.d.ts +2 -0
- package/dist/cmd/coder/create.d.ts.map +1 -0
- package/dist/cmd/coder/create.js +245 -0
- package/dist/cmd/coder/create.js.map +1 -0
- package/dist/cmd/coder/delete.d.ts +2 -0
- package/dist/cmd/coder/delete.d.ts.map +1 -0
- package/dist/cmd/coder/delete.js +64 -0
- package/dist/cmd/coder/delete.js.map +1 -0
- package/dist/cmd/coder/events.d.ts +2 -0
- package/dist/cmd/coder/events.d.ts.map +1 -0
- package/dist/cmd/coder/events.js +99 -0
- package/dist/cmd/coder/events.js.map +1 -0
- package/dist/cmd/coder/extension-path.d.ts +8 -0
- package/dist/cmd/coder/extension-path.d.ts.map +1 -0
- package/dist/cmd/coder/extension-path.js +59 -0
- package/dist/cmd/coder/extension-path.js.map +1 -0
- package/dist/cmd/coder/get.d.ts +2 -0
- package/dist/cmd/coder/get.d.ts.map +1 -0
- package/dist/cmd/coder/{inspect.js → get.js} +38 -42
- package/dist/cmd/coder/get.js.map +1 -0
- package/dist/cmd/coder/index.d.ts.map +1 -1
- package/dist/cmd/coder/index.js +52 -7
- package/dist/cmd/coder/index.js.map +1 -1
- package/dist/cmd/coder/list.d.ts.map +1 -1
- package/dist/cmd/coder/list.js +26 -42
- package/dist/cmd/coder/list.js.map +1 -1
- package/dist/cmd/coder/loop.d.ts +2 -0
- package/dist/cmd/coder/loop.d.ts.map +1 -0
- package/dist/cmd/coder/loop.js +78 -0
- package/dist/cmd/coder/loop.js.map +1 -0
- package/dist/cmd/coder/participants.d.ts +2 -0
- package/dist/cmd/coder/participants.d.ts.map +1 -0
- package/dist/cmd/coder/participants.js +93 -0
- package/dist/cmd/coder/participants.js.map +1 -0
- package/dist/cmd/coder/replay.d.ts +2 -0
- package/dist/cmd/coder/replay.d.ts.map +1 -0
- package/dist/cmd/coder/replay.js +53 -0
- package/dist/cmd/coder/replay.js.map +1 -0
- package/dist/cmd/coder/resolve-repo.d.ts +27 -0
- package/dist/cmd/coder/resolve-repo.d.ts.map +1 -0
- package/dist/cmd/coder/resolve-repo.js +97 -0
- package/dist/cmd/coder/resolve-repo.js.map +1 -0
- package/dist/cmd/coder/skill/buckets.d.ts +2 -0
- package/dist/cmd/coder/skill/buckets.d.ts.map +1 -0
- package/dist/cmd/coder/skill/buckets.js +174 -0
- package/dist/cmd/coder/skill/buckets.js.map +1 -0
- package/dist/cmd/coder/skill/delete.d.ts +2 -0
- package/dist/cmd/coder/skill/delete.d.ts.map +1 -0
- package/dist/cmd/coder/skill/delete.js +64 -0
- package/dist/cmd/coder/skill/delete.js.map +1 -0
- package/dist/cmd/coder/skill/index.d.ts +2 -0
- package/dist/cmd/coder/skill/index.d.ts.map +1 -0
- package/dist/cmd/coder/skill/index.js +33 -0
- package/dist/cmd/coder/skill/index.js.map +1 -0
- package/dist/cmd/coder/skill/list.d.ts +2 -0
- package/dist/cmd/coder/skill/list.d.ts.map +1 -0
- package/dist/cmd/coder/skill/list.js +93 -0
- package/dist/cmd/coder/skill/list.js.map +1 -0
- package/dist/cmd/coder/skill/save.d.ts +2 -0
- package/dist/cmd/coder/skill/save.d.ts.map +1 -0
- package/dist/cmd/coder/skill/save.js +77 -0
- package/dist/cmd/coder/skill/save.js.map +1 -0
- package/dist/cmd/coder/start.d.ts.map +1 -1
- package/dist/cmd/coder/start.js +87 -131
- package/dist/cmd/coder/start.js.map +1 -1
- package/dist/cmd/coder/tui-init.d.ts.map +1 -1
- package/dist/cmd/coder/tui-init.js +7 -2
- package/dist/cmd/coder/tui-init.js.map +1 -1
- package/dist/cmd/coder/update.d.ts +2 -0
- package/dist/cmd/coder/update.d.ts.map +1 -0
- package/dist/cmd/coder/update.js +126 -0
- package/dist/cmd/coder/update.js.map +1 -0
- package/dist/cmd/coder/users.d.ts +2 -0
- package/dist/cmd/coder/users.d.ts.map +1 -0
- package/dist/cmd/coder/users.js +97 -0
- package/dist/cmd/coder/users.js.map +1 -0
- package/dist/cmd/coder/workspace/create.d.ts +2 -0
- package/dist/cmd/coder/workspace/create.d.ts.map +1 -0
- package/dist/cmd/coder/workspace/create.js +97 -0
- package/dist/cmd/coder/workspace/create.js.map +1 -0
- package/dist/cmd/coder/workspace/delete.d.ts +2 -0
- package/dist/cmd/coder/workspace/delete.d.ts.map +1 -0
- package/dist/cmd/coder/workspace/delete.js +64 -0
- package/dist/cmd/coder/workspace/delete.js.map +1 -0
- package/dist/cmd/coder/workspace/get.d.ts +2 -0
- package/dist/cmd/coder/workspace/get.d.ts.map +1 -0
- package/dist/cmd/coder/workspace/get.js +109 -0
- package/dist/cmd/coder/workspace/get.js.map +1 -0
- package/dist/cmd/coder/workspace/index.d.ts +2 -0
- package/dist/cmd/coder/workspace/index.d.ts.map +1 -0
- package/dist/cmd/coder/workspace/index.js +38 -0
- package/dist/cmd/coder/workspace/index.js.map +1 -0
- package/dist/cmd/coder/workspace/list.d.ts +2 -0
- package/dist/cmd/coder/workspace/list.d.ts.map +1 -0
- package/dist/cmd/coder/workspace/list.js +93 -0
- package/dist/cmd/coder/workspace/list.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/types.d.ts +1 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -10
- package/dist/types.js.map +1 -1
- package/package.json +7 -6
- package/src/cli.ts +4 -2
- package/src/cmd/ai/prompt/agent.md +6 -6
- package/src/cmd/build/vite/route-discovery.ts +8 -0
- package/src/cmd/cloud/sandbox/fs/rm.ts +8 -3
- package/src/cmd/cloud/sandbox/fs/rmdir.ts +8 -3
- package/src/cmd/coder/archive.ts +59 -0
- package/src/cmd/coder/create.ts +268 -0
- package/src/cmd/coder/delete.ts +67 -0
- package/src/cmd/coder/events.ts +106 -0
- package/src/cmd/coder/extension-path.ts +71 -0
- package/src/cmd/coder/{inspect.ts → get.ts} +45 -69
- package/src/cmd/coder/index.ts +52 -7
- package/src/cmd/coder/list.ts +29 -89
- package/src/cmd/coder/loop.ts +85 -0
- package/src/cmd/coder/participants.ts +100 -0
- package/src/cmd/coder/replay.ts +58 -0
- package/src/cmd/coder/resolve-repo.ts +119 -0
- package/src/cmd/coder/skill/buckets.ts +191 -0
- package/src/cmd/coder/skill/delete.ts +67 -0
- package/src/cmd/coder/skill/index.ts +35 -0
- package/src/cmd/coder/skill/list.ts +97 -0
- package/src/cmd/coder/skill/save.ts +84 -0
- package/src/cmd/coder/start.ts +101 -174
- package/src/cmd/coder/tui-init.ts +7 -3
- package/src/cmd/coder/update.ts +128 -0
- package/src/cmd/coder/users.ts +101 -0
- package/src/cmd/coder/workspace/create.ts +104 -0
- package/src/cmd/coder/workspace/delete.ts +70 -0
- package/src/cmd/coder/workspace/get.ts +112 -0
- package/src/cmd/coder/workspace/index.ts +38 -0
- package/src/cmd/coder/workspace/list.ts +101 -0
- package/src/config.ts +4 -3
- package/src/types.ts +0 -10
- package/dist/cmd/coder/config/index.d.ts +0 -2
- package/dist/cmd/coder/config/index.d.ts.map +0 -1
- package/dist/cmd/coder/config/index.js +0 -20
- package/dist/cmd/coder/config/index.js.map +0 -1
- package/dist/cmd/coder/config/set.d.ts +0 -2
- package/dist/cmd/coder/config/set.d.ts.map +0 -1
- package/dist/cmd/coder/config/set.js +0 -100
- package/dist/cmd/coder/config/set.js.map +0 -1
- package/dist/cmd/coder/hub-url.d.ts +0 -47
- package/dist/cmd/coder/hub-url.d.ts.map +0 -1
- package/dist/cmd/coder/hub-url.js +0 -148
- package/dist/cmd/coder/hub-url.js.map +0 -1
- package/dist/cmd/coder/inspect.d.ts +0 -2
- package/dist/cmd/coder/inspect.d.ts.map +0 -1
- package/dist/cmd/coder/inspect.js.map +0 -1
- package/dist/coder-config.d.ts +0 -14
- package/dist/coder-config.d.ts.map +0 -1
- package/dist/coder-config.js +0 -119
- package/dist/coder-config.js.map +0 -1
- package/src/cmd/coder/config/index.ts +0 -20
- package/src/cmd/coder/config/set.ts +0 -112
- package/src/cmd/coder/hub-url.ts +0 -205
- package/src/coder-config.ts +0 -141
package/src/cmd/coder/start.ts
CHANGED
|
@@ -1,95 +1,75 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
1
2
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
3
|
+
import { CoderClient, type CoderSessionListItem } from '@agentuity/core/coder';
|
|
4
|
+
import { ValidationOutputError } from '@agentuity/core';
|
|
5
|
+
import { toCoderHubWsUrl } from '../../coder-hub-url';
|
|
4
6
|
import { createSubcommand } from '../../types';
|
|
5
7
|
import * as tui from '../../tui';
|
|
6
8
|
import { getCommand } from '../../command-prefix';
|
|
7
9
|
import { ErrorCode } from '../../errors';
|
|
8
|
-
import {
|
|
9
|
-
clearStoredHubApiKeyOnUnauthorized,
|
|
10
|
-
formatHubUnauthorizedMessage,
|
|
11
|
-
formatMissingHubUrlMessage,
|
|
12
|
-
getHubResponseErrorMessage,
|
|
13
|
-
hubFetchHeaders,
|
|
14
|
-
isHubUnauthorizedStatus,
|
|
15
|
-
resolveHubApiKey,
|
|
16
|
-
resolveHubUrl,
|
|
17
|
-
toHubWsUrl,
|
|
18
|
-
} from './hub-url';
|
|
10
|
+
import { resolveExtensionPath, resolveExtensionRuntimeModulePath } from './extension-path';
|
|
19
11
|
import { probeHubInitAccess } from './tui-init';
|
|
20
12
|
|
|
21
|
-
/**
|
|
22
|
-
* Resolve the Coder extension path.
|
|
23
|
-
*
|
|
24
|
-
* Priority:
|
|
25
|
-
* 1. --extension flag (explicit override)
|
|
26
|
-
* 2. AGENTUITY_CODER_EXTENSION env var
|
|
27
|
-
* 3. Installed @agentuity/coder package (node_modules)
|
|
28
|
-
* 4. Local dev path relative to CLI package (SDK monorepo)
|
|
29
|
-
*/
|
|
30
|
-
function resolveExtensionPath(flagPath?: string): string | null {
|
|
31
|
-
// 1. Explicit flag
|
|
32
|
-
if (flagPath) {
|
|
33
|
-
const resolved = resolve(flagPath);
|
|
34
|
-
if (existsSync(resolved)) return resolved;
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// 2. Env var
|
|
39
|
-
const envPath = process.env.AGENTUITY_CODER_EXTENSION;
|
|
40
|
-
if (envPath) {
|
|
41
|
-
const resolved = resolve(envPath);
|
|
42
|
-
if (existsSync(resolved)) return resolved;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 3. Installed npm package in cwd
|
|
46
|
-
const cwdNodeModules = resolve(process.cwd(), 'node_modules', '@agentuity', 'coder');
|
|
47
|
-
if (existsSync(cwdNodeModules)) return cwdNodeModules;
|
|
48
|
-
|
|
49
|
-
// 4. SDK monorepo sibling (for development)
|
|
50
|
-
// This file is at packages/cli/src/cmd/coder/start.ts — 5 levels up to SDK root
|
|
51
|
-
try {
|
|
52
|
-
const cliDir = dirname(new URL(import.meta.url).pathname);
|
|
53
|
-
const sdkRoot = resolve(cliDir, '..', '..', '..', '..', '..');
|
|
54
|
-
const coderPath = join(sdkRoot, 'packages', 'coder');
|
|
55
|
-
if (existsSync(join(coderPath, 'src', 'index.ts'))) return coderPath;
|
|
56
|
-
} catch {
|
|
57
|
-
// Not in SDK monorepo
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
13
|
/**
|
|
64
14
|
* Find the `pi` binary.
|
|
65
15
|
*
|
|
66
16
|
* Priority:
|
|
67
17
|
* 1. --pi flag (explicit override)
|
|
68
18
|
* 2. AGENTUITY_CODER_PI_PATH env var
|
|
69
|
-
* 3.
|
|
19
|
+
* 3. Bundled pi from coder-tui's node_modules/.bin/pi
|
|
20
|
+
* 4. `pi` on PATH (fallback)
|
|
70
21
|
*/
|
|
71
|
-
function resolvePiBinary(flagPath?: string): string {
|
|
22
|
+
async function resolvePiBinary(flagPath?: string, extensionDir?: string): Promise<string> {
|
|
72
23
|
if (flagPath) return flagPath;
|
|
73
24
|
const envPath = process.env.AGENTUITY_CODER_PI_PATH;
|
|
74
25
|
if (envPath) return envPath;
|
|
26
|
+
|
|
27
|
+
// Look for pi bundled with the coder-tui extension
|
|
28
|
+
if (extensionDir) {
|
|
29
|
+
// Prefer require.resolve via package.json — handles hoisted deps, Bun's .bun cache, etc.
|
|
30
|
+
try {
|
|
31
|
+
const pkgJson = require.resolve('@mariozechner/pi-coding-agent/package.json', {
|
|
32
|
+
paths: [extensionDir],
|
|
33
|
+
});
|
|
34
|
+
const piCli = resolve(dirname(pkgJson), 'dist', 'cli.js');
|
|
35
|
+
if (await Bun.file(piCli).exists()) return piCli;
|
|
36
|
+
} catch {
|
|
37
|
+
// Fallback: direct .bin symlink check
|
|
38
|
+
const bundledPi = resolve(extensionDir, 'node_modules', '.bin', 'pi');
|
|
39
|
+
if (await Bun.file(bundledPi).exists()) return bundledPi;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
75
43
|
return 'pi';
|
|
76
44
|
}
|
|
77
45
|
|
|
46
|
+
function logValidationIssues(
|
|
47
|
+
ctx: { logger: { trace: (...args: unknown[]) => void } },
|
|
48
|
+
err: unknown
|
|
49
|
+
): void {
|
|
50
|
+
if (err instanceof ValidationOutputError) {
|
|
51
|
+
ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
|
|
52
|
+
ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
78
56
|
export const startSubcommand = createSubcommand({
|
|
79
57
|
name: 'start',
|
|
58
|
+
aliases: ['run'],
|
|
80
59
|
description: 'Start a Pi coding session connected to the Coder Hub',
|
|
81
60
|
tags: ['fast', 'requires-auth'],
|
|
61
|
+
requires: { auth: true, org: true },
|
|
82
62
|
examples: [
|
|
83
63
|
{
|
|
84
64
|
command: getCommand('coder start'),
|
|
85
65
|
description: 'Start Pi with auto-detected Hub and extension',
|
|
86
66
|
},
|
|
87
67
|
{
|
|
88
|
-
command: getCommand('coder start --
|
|
68
|
+
command: getCommand('coder start --url ws://127.0.0.1:3500/api/ws'),
|
|
89
69
|
description: 'Start with explicit Hub URL',
|
|
90
70
|
},
|
|
91
71
|
{
|
|
92
|
-
command: getCommand('coder start --extension ~/repos/agentuity/sdk/packages/coder'),
|
|
72
|
+
command: getCommand('coder start --extension ~/repos/agentuity/sdk/packages/coder-tui'),
|
|
93
73
|
description: 'Start with explicit extension path',
|
|
94
74
|
},
|
|
95
75
|
{
|
|
@@ -97,7 +77,7 @@ export const startSubcommand = createSubcommand({
|
|
|
97
77
|
description: 'Start as a specific agent role',
|
|
98
78
|
},
|
|
99
79
|
{
|
|
100
|
-
command: getCommand('coder start --remote
|
|
80
|
+
command: getCommand('coder start --remote codesess_abc123456789'),
|
|
101
81
|
description: 'Connect to an existing sandbox session remotely',
|
|
102
82
|
},
|
|
103
83
|
{
|
|
@@ -117,7 +97,7 @@ export const startSubcommand = createSubcommand({
|
|
|
117
97
|
],
|
|
118
98
|
schema: {
|
|
119
99
|
options: z.object({
|
|
120
|
-
|
|
100
|
+
url: z.string().optional().describe('Coder API URL override'),
|
|
121
101
|
extension: z.string().optional().describe('Coder extension path override'),
|
|
122
102
|
pi: z.string().optional().describe('Path to pi binary'),
|
|
123
103
|
agent: z.string().optional().describe('Agent role (e.g. scout, builder)'),
|
|
@@ -135,51 +115,25 @@ export const startSubcommand = createSubcommand({
|
|
|
135
115
|
.optional()
|
|
136
116
|
.describe('Git repo URL to clone in the sandbox (used with --sandbox)'),
|
|
137
117
|
}),
|
|
118
|
+
aliases: {
|
|
119
|
+
remote: ['session'],
|
|
120
|
+
},
|
|
138
121
|
},
|
|
139
122
|
async handler(ctx) {
|
|
140
|
-
const { opts, options
|
|
123
|
+
const { opts, options } = ctx;
|
|
124
|
+
const client = new CoderClient({
|
|
125
|
+
apiKey: ctx.auth.apiKey,
|
|
126
|
+
url: opts?.url,
|
|
127
|
+
orgId: ctx.orgId,
|
|
128
|
+
});
|
|
141
129
|
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
if (!hubHttpUrl) {
|
|
145
|
-
tui.fatal(formatMissingHubUrlMessage(), ErrorCode.NETWORK_ERROR);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
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
|
-
};
|
|
130
|
+
const hubHttpUrl = await client.getUrl();
|
|
131
|
+
const hubWsUrl = toCoderHubWsUrl(hubHttpUrl);
|
|
166
132
|
|
|
167
133
|
const initProbe = await probeHubInitAccess(hubHttpUrl, {
|
|
168
|
-
apiKey:
|
|
134
|
+
apiKey: ctx.auth.apiKey,
|
|
169
135
|
});
|
|
170
136
|
if (!initProbe.ok) {
|
|
171
|
-
if (initProbe.code === 'unauthorized') {
|
|
172
|
-
const clearedStoredKey =
|
|
173
|
-
resolvedHubApiKey.source === 'stored'
|
|
174
|
-
? await clearStoredHubApiKeyOnUnauthorized(401, resolvedHubApiKey, config)
|
|
175
|
-
: false;
|
|
176
|
-
tui.fatal(
|
|
177
|
-
formatHubUnauthorizedMessage(hubHttpUrl, initProbe.message, { clearedStoredKey }),
|
|
178
|
-
ErrorCode.NETWORK_ERROR
|
|
179
|
-
);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
137
|
tui.fatal(
|
|
184
138
|
`Could not bootstrap the Coder Hub at ${hubHttpUrl}: ${initProbe.message}`,
|
|
185
139
|
ErrorCode.NETWORK_ERROR
|
|
@@ -188,17 +142,27 @@ export const startSubcommand = createSubcommand({
|
|
|
188
142
|
}
|
|
189
143
|
|
|
190
144
|
// Resolve extension path
|
|
191
|
-
const extensionPath = resolveExtensionPath(opts?.extension);
|
|
145
|
+
const extensionPath = await resolveExtensionPath(opts?.extension);
|
|
192
146
|
if (!extensionPath) {
|
|
193
147
|
tui.fatal(
|
|
194
|
-
'Could not find the Agentuity Coder extension.\n\
|
|
148
|
+
'Could not find the Agentuity Coder extension.\n\nThis CLI install should include it automatically. Try:\n - Reinstall or update @agentuity/cli\n - Install it locally: npm install @agentuity/coder-tui\n - Set AGENTUITY_CODER_EXTENSION environment variable\n - Pass --extension flag',
|
|
195
149
|
ErrorCode.CONFIG_INVALID
|
|
196
150
|
);
|
|
197
151
|
return;
|
|
198
152
|
}
|
|
199
153
|
|
|
154
|
+
const loadRemoteTui = async () => {
|
|
155
|
+
const modulePath = await resolveExtensionRuntimeModulePath(extensionPath);
|
|
156
|
+
if (!modulePath) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Coder extension at ${extensionPath} is missing the remote TUI entrypoint`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return import(modulePath);
|
|
162
|
+
};
|
|
163
|
+
|
|
200
164
|
// Resolve pi binary
|
|
201
|
-
const piBinary = resolvePiBinary(opts?.pi);
|
|
165
|
+
const piBinary = await resolvePiBinary(opts?.pi, extensionPath);
|
|
202
166
|
|
|
203
167
|
// ── Remote mode: resolve session ID ──
|
|
204
168
|
let remoteSessionId: string | undefined;
|
|
@@ -210,31 +174,9 @@ export const startSubcommand = createSubcommand({
|
|
|
210
174
|
} else {
|
|
211
175
|
// No session ID — fetch connectable sessions and show picker
|
|
212
176
|
try {
|
|
213
|
-
type SessionInfo = {
|
|
214
|
-
id: string;
|
|
215
|
-
label: string;
|
|
216
|
-
status: string;
|
|
217
|
-
task: string | null;
|
|
218
|
-
createdAt: string;
|
|
219
|
-
};
|
|
220
|
-
|
|
221
177
|
const sessions = await tui.spinner({
|
|
222
178
|
message: 'Fetching connectable sessions…',
|
|
223
|
-
callback: async () =>
|
|
224
|
-
const resp = await fetch(`${hubHttpUrl}/api/hub/sessions/connectable`, {
|
|
225
|
-
headers: hubFetchHeaders(undefined, resolvedHubApiKey.apiKey),
|
|
226
|
-
signal: AbortSignal.timeout(10_000),
|
|
227
|
-
});
|
|
228
|
-
if (isHubUnauthorizedStatus(resp.status)) {
|
|
229
|
-
await handleUnauthorizedResponse(resp);
|
|
230
|
-
throw new Error('Hub authentication failed');
|
|
231
|
-
}
|
|
232
|
-
if (!resp.ok) {
|
|
233
|
-
throw new Error(`${resp.status} ${await getHubResponseErrorMessage(resp)}`);
|
|
234
|
-
}
|
|
235
|
-
const data = (await resp.json()) as { sessions: SessionInfo[] };
|
|
236
|
-
return data.sessions;
|
|
237
|
-
},
|
|
179
|
+
callback: async () => (await client.listConnectableSessions()).sessions,
|
|
238
180
|
});
|
|
239
181
|
|
|
240
182
|
if (sessions.length === 0) {
|
|
@@ -248,22 +190,20 @@ export const startSubcommand = createSubcommand({
|
|
|
248
190
|
const prompt = tui.createPrompt();
|
|
249
191
|
remoteSessionId = await prompt.select<string>({
|
|
250
192
|
message: 'Select a sandbox session to connect to',
|
|
251
|
-
options: sessions.map((s) => {
|
|
193
|
+
options: sessions.map((s: CoderSessionListItem) => {
|
|
252
194
|
const age = timeSince(new Date(s.createdAt));
|
|
253
|
-
const
|
|
254
|
-
const label = taskPreview
|
|
255
|
-
? `${s.label} ${tui.muted(`(${s.status}, ${age})`)} — ${taskPreview}`
|
|
256
|
-
: `${s.label} ${tui.muted(`(${s.status}, ${age})`)}`;
|
|
195
|
+
const label = `${s.label} ${tui.muted(`(${s.status}, ${age})`)}`;
|
|
257
196
|
return {
|
|
258
|
-
value: s.
|
|
197
|
+
value: s.sessionId,
|
|
259
198
|
label,
|
|
260
|
-
hint: s.
|
|
199
|
+
hint: s.sessionId,
|
|
261
200
|
};
|
|
262
201
|
}),
|
|
263
202
|
});
|
|
264
203
|
} catch (err) {
|
|
204
|
+
logValidationIssues(ctx, err);
|
|
265
205
|
const msg = err instanceof Error ? err.message : String(err);
|
|
266
|
-
if (msg === 'User cancelled'
|
|
206
|
+
if (msg === 'User cancelled') return;
|
|
267
207
|
tui.fatal(`Failed to fetch connectable sessions: ${msg}`, ErrorCode.NETWORK_ERROR);
|
|
268
208
|
return;
|
|
269
209
|
}
|
|
@@ -284,10 +224,12 @@ export const startSubcommand = createSubcommand({
|
|
|
284
224
|
}
|
|
285
225
|
|
|
286
226
|
try {
|
|
287
|
-
const { runRemoteTui } = await
|
|
227
|
+
const { runRemoteTui } = await loadRemoteTui();
|
|
288
228
|
await runRemoteTui({
|
|
289
229
|
hubWsUrl,
|
|
290
230
|
sessionId: remoteSessionId,
|
|
231
|
+
apiKey: ctx.auth.apiKey,
|
|
232
|
+
orgId: ctx.orgId,
|
|
291
233
|
});
|
|
292
234
|
} catch (err) {
|
|
293
235
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -308,7 +250,10 @@ export const startSubcommand = createSubcommand({
|
|
|
308
250
|
}
|
|
309
251
|
|
|
310
252
|
// Build request body
|
|
311
|
-
const body:
|
|
253
|
+
const body: {
|
|
254
|
+
task: string;
|
|
255
|
+
repo?: { url: string };
|
|
256
|
+
} = { task };
|
|
312
257
|
if (opts?.repo) {
|
|
313
258
|
body.repo = { url: opts.repo };
|
|
314
259
|
}
|
|
@@ -319,29 +264,10 @@ export const startSubcommand = createSubcommand({
|
|
|
319
264
|
|
|
320
265
|
let sessionId: string;
|
|
321
266
|
try {
|
|
322
|
-
const
|
|
323
|
-
method: 'POST',
|
|
324
|
-
headers: hubFetchHeaders(
|
|
325
|
-
{ 'Content-Type': 'application/json' },
|
|
326
|
-
resolvedHubApiKey.apiKey
|
|
327
|
-
),
|
|
328
|
-
body: JSON.stringify(body),
|
|
329
|
-
signal: AbortSignal.timeout(10_000),
|
|
330
|
-
});
|
|
331
|
-
if (isHubUnauthorizedStatus(resp.status)) {
|
|
332
|
-
await handleUnauthorizedResponse(resp);
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
if (!resp.ok) {
|
|
336
|
-
tui.fatal(
|
|
337
|
-
`Failed to create sandbox session: ${resp.status} ${await getHubResponseErrorMessage(resp)}`,
|
|
338
|
-
ErrorCode.NETWORK_ERROR
|
|
339
|
-
);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
const sessionInfo = (await resp.json()) as { sessionId: string };
|
|
267
|
+
const sessionInfo = await client.createSession(body);
|
|
343
268
|
sessionId = sessionInfo.sessionId;
|
|
344
269
|
} catch (err) {
|
|
270
|
+
logValidationIssues(ctx, err);
|
|
345
271
|
const msg = err instanceof Error ? err.message : String(err);
|
|
346
272
|
tui.fatal(`Failed to create sandbox session: ${msg}`, ErrorCode.NETWORK_ERROR);
|
|
347
273
|
return;
|
|
@@ -361,22 +287,10 @@ export const startSubcommand = createSubcommand({
|
|
|
361
287
|
while (Date.now() - pollStart < POLL_TIMEOUT) {
|
|
362
288
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
363
289
|
try {
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (isHubUnauthorizedStatus(pollResp.status)) {
|
|
369
|
-
await handleUnauthorizedResponse(pollResp);
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
if (pollResp.ok) {
|
|
373
|
-
const data = (await pollResp.json()) as {
|
|
374
|
-
participants?: Array<{ role: string }>;
|
|
375
|
-
};
|
|
376
|
-
if (data.participants?.some((p) => p.role === 'lead')) {
|
|
377
|
-
driverConnected = true;
|
|
378
|
-
break;
|
|
379
|
-
}
|
|
290
|
+
const data = await client.listParticipants(sessionId);
|
|
291
|
+
if (data.participants?.some((p) => p.role === 'lead')) {
|
|
292
|
+
driverConnected = true;
|
|
293
|
+
break;
|
|
380
294
|
}
|
|
381
295
|
} catch {
|
|
382
296
|
// Network blip — keep polling
|
|
@@ -395,10 +309,12 @@ export const startSubcommand = createSubcommand({
|
|
|
395
309
|
tui.newline();
|
|
396
310
|
|
|
397
311
|
try {
|
|
398
|
-
const { runRemoteTui } = await
|
|
312
|
+
const { runRemoteTui } = await loadRemoteTui();
|
|
399
313
|
await runRemoteTui({
|
|
400
314
|
hubWsUrl,
|
|
401
315
|
sessionId,
|
|
316
|
+
apiKey: ctx.auth.apiKey,
|
|
317
|
+
orgId: ctx.orgId,
|
|
402
318
|
});
|
|
403
319
|
} catch (err) {
|
|
404
320
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -412,7 +328,7 @@ export const startSubcommand = createSubcommand({
|
|
|
412
328
|
...(process.env as Record<string, string>),
|
|
413
329
|
AGENTUITY_CODER_HUB_URL: hubWsUrl,
|
|
414
330
|
};
|
|
415
|
-
|
|
331
|
+
env.AGENTUITY_CODER_API_KEY = ctx.auth.apiKey;
|
|
416
332
|
|
|
417
333
|
if (opts?.agent) {
|
|
418
334
|
env.AGENTUITY_CODER_AGENT = opts.agent;
|
|
@@ -440,7 +356,18 @@ export const startSubcommand = createSubcommand({
|
|
|
440
356
|
stderr: 'inherit',
|
|
441
357
|
});
|
|
442
358
|
|
|
359
|
+
// Forward signals to the child process so Ctrl+C exits cleanly
|
|
360
|
+
const onSigInt = () => proc.kill(2);
|
|
361
|
+
const onSigTerm = () => proc.kill(15);
|
|
362
|
+
process.on('SIGINT', onSigInt);
|
|
363
|
+
process.on('SIGTERM', onSigTerm);
|
|
364
|
+
|
|
443
365
|
const exitCode = await proc.exited;
|
|
366
|
+
|
|
367
|
+
// Clean up only our signal handlers (preserve other modules' listeners)
|
|
368
|
+
process.removeListener('SIGINT', onSigInt);
|
|
369
|
+
process.removeListener('SIGTERM', onSigTerm);
|
|
370
|
+
|
|
444
371
|
process.exit(exitCode);
|
|
445
372
|
} catch (err) {
|
|
446
373
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { hubFetchHeaders } from './hub-url';
|
|
2
|
-
|
|
3
1
|
export type HubInitProbeResult =
|
|
4
2
|
| { ok: true }
|
|
5
3
|
| {
|
|
@@ -27,10 +25,16 @@ export async function probeHubInitAccess(
|
|
|
27
25
|
}
|
|
28
26
|
): Promise<HubInitProbeResult> {
|
|
29
27
|
const fetchImpl = options?.fetchImpl ?? fetch;
|
|
28
|
+
const headers: Record<string, string> = {
|
|
29
|
+
accept: 'application/json',
|
|
30
|
+
};
|
|
31
|
+
if (options?.apiKey) {
|
|
32
|
+
headers['x-agentuity-auth-api-key'] = options.apiKey;
|
|
33
|
+
}
|
|
30
34
|
|
|
31
35
|
try {
|
|
32
36
|
const response = await fetchImpl(`${hubHttpUrl}/api/hub/init`, {
|
|
33
|
-
headers
|
|
37
|
+
headers,
|
|
34
38
|
signal: AbortSignal.timeout(5_000),
|
|
35
39
|
});
|
|
36
40
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { CoderClient, normalizeVisibility } from '@agentuity/core/coder';
|
|
3
|
+
import { ValidationOutputError } from '@agentuity/core';
|
|
4
|
+
import { createSubcommand } from '../../types';
|
|
5
|
+
import * as tui from '../../tui';
|
|
6
|
+
import { getCommand } from '../../command-prefix';
|
|
7
|
+
import { ErrorCode } from '../../errors';
|
|
8
|
+
|
|
9
|
+
export const updateSubcommand = createSubcommand({
|
|
10
|
+
name: 'update',
|
|
11
|
+
description: 'Update a Coder session',
|
|
12
|
+
tags: ['mutating', 'requires-auth'],
|
|
13
|
+
requires: { auth: true, org: true },
|
|
14
|
+
aliases: ['set', 'edit'],
|
|
15
|
+
examples: [
|
|
16
|
+
{
|
|
17
|
+
command: getCommand('coder update codesess_abc123 --label "New Label"'),
|
|
18
|
+
description: 'Update the session label',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
command: getCommand('coder update codesess_abc123 --visibility org'),
|
|
22
|
+
description: 'Make a session visible to the org',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
command: getCommand('coder update codesess_abc123 --tags "urgent,frontend"'),
|
|
26
|
+
description: 'Update tags on a session',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
command: getCommand(
|
|
30
|
+
'coder update codesess_abc123 --workflow-mode loop --loop-goal "Build it" --loop-max-iterations 20'
|
|
31
|
+
),
|
|
32
|
+
description: 'Switch a session to loop mode',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
schema: {
|
|
36
|
+
args: z.object({
|
|
37
|
+
sessionId: z.string().describe('Session ID to update'),
|
|
38
|
+
}),
|
|
39
|
+
options: z.object({
|
|
40
|
+
url: z.string().optional().describe('Coder API URL override'),
|
|
41
|
+
label: z.string().optional().describe('Updated session label'),
|
|
42
|
+
agent: z.string().optional().describe('Updated default agent role'),
|
|
43
|
+
visibility: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('Updated visibility: private, org, or collaborate'),
|
|
47
|
+
workflowMode: z.string().optional().describe('Updated workflow mode: standard or loop'),
|
|
48
|
+
loopGoal: z.string().optional().describe('Goal for loop mode'),
|
|
49
|
+
loopMaxIterations: z.number().optional().describe('Maximum loop iterations'),
|
|
50
|
+
loopAutoContinue: z.boolean().optional().describe('Auto-continue loop'),
|
|
51
|
+
loopAllowDetached: z.boolean().optional().describe('Allow detached loop execution'),
|
|
52
|
+
tags: z.string().optional().describe('Comma-separated tags (replaces existing)'),
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
async handler(ctx) {
|
|
56
|
+
const { args, opts, options } = ctx;
|
|
57
|
+
const client = new CoderClient({
|
|
58
|
+
apiKey: ctx.auth.apiKey,
|
|
59
|
+
url: opts?.url,
|
|
60
|
+
orgId: ctx.orgId,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const body: Record<string, unknown> = {};
|
|
64
|
+
|
|
65
|
+
if (opts?.label) body.label = opts.label;
|
|
66
|
+
if (opts?.agent) body.agent = opts.agent;
|
|
67
|
+
if (opts?.visibility) body.visibility = normalizeVisibility(opts.visibility);
|
|
68
|
+
if (opts?.workflowMode) body.workflowMode = opts.workflowMode;
|
|
69
|
+
if (opts?.tags) {
|
|
70
|
+
body.tags = opts.tags
|
|
71
|
+
.split(',')
|
|
72
|
+
.map((t) => t.trim())
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
opts?.loopGoal ||
|
|
78
|
+
opts?.loopMaxIterations ||
|
|
79
|
+
opts?.loopAutoContinue !== undefined ||
|
|
80
|
+
opts?.loopAllowDetached !== undefined
|
|
81
|
+
) {
|
|
82
|
+
const loop: Record<string, unknown> = {};
|
|
83
|
+
if (opts.loopGoal) loop.goal = opts.loopGoal;
|
|
84
|
+
if (opts.loopMaxIterations) loop.maxIterations = opts.loopMaxIterations;
|
|
85
|
+
if (opts.loopAutoContinue !== undefined) loop.autoContinue = opts.loopAutoContinue;
|
|
86
|
+
if (opts.loopAllowDetached !== undefined) loop.allowDetached = opts.loopAllowDetached;
|
|
87
|
+
body.loop = loop;
|
|
88
|
+
if (!body.workflowMode) body.workflowMode = 'loop';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (Object.keys(body).length === 0) {
|
|
92
|
+
tui.fatal(
|
|
93
|
+
'No update fields provided. Use --label, --visibility, --tags, --agent, --workflow-mode, or loop options.',
|
|
94
|
+
ErrorCode.VALIDATION_FAILED
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const updated = await client.updateSession(args.sessionId, body as any);
|
|
100
|
+
|
|
101
|
+
if (options.json) {
|
|
102
|
+
return updated;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
tui.success(`Session ${args.sessionId} updated.`);
|
|
106
|
+
|
|
107
|
+
const fields: string[] = [];
|
|
108
|
+
if (opts?.label) fields.push(`Label: ${opts.label}`);
|
|
109
|
+
if (opts?.visibility) fields.push(`Visibility: ${body.visibility}`);
|
|
110
|
+
if (opts?.tags) fields.push(`Tags: ${(body.tags as string[]).join(', ')}`);
|
|
111
|
+
if (opts?.agent) fields.push(`Agent: ${opts.agent}`);
|
|
112
|
+
if (opts?.workflowMode || body.loop) fields.push(`Workflow: ${body.workflowMode}`);
|
|
113
|
+
|
|
114
|
+
for (const f of fields) {
|
|
115
|
+
tui.output(` ${f}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return updated;
|
|
119
|
+
} catch (err) {
|
|
120
|
+
if (err instanceof ValidationOutputError) {
|
|
121
|
+
ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
|
|
122
|
+
ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
|
|
123
|
+
}
|
|
124
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
125
|
+
tui.fatal(`Failed to update session ${args.sessionId}: ${msg}`, ErrorCode.NETWORK_ERROR);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { CoderClient, type CoderUser } from '@agentuity/core/coder';
|
|
3
|
+
import { ValidationOutputError } from '@agentuity/core';
|
|
4
|
+
import { createSubcommand } from '../../types';
|
|
5
|
+
import * as tui from '../../tui';
|
|
6
|
+
import { getCommand } from '../../command-prefix';
|
|
7
|
+
import { ErrorCode } from '../../errors';
|
|
8
|
+
|
|
9
|
+
function formatRelativeTime(isoDate: string): string {
|
|
10
|
+
const parsed = new Date(isoDate).getTime();
|
|
11
|
+
if (Number.isNaN(parsed)) return 'unknown';
|
|
12
|
+
const diffMs = Math.max(0, Date.now() - parsed);
|
|
13
|
+
const seconds = Math.floor(diffMs / 1000);
|
|
14
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
15
|
+
const minutes = Math.floor(seconds / 60);
|
|
16
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
17
|
+
const hours = Math.floor(minutes / 60);
|
|
18
|
+
if (hours < 24) return `${hours}h ago`;
|
|
19
|
+
const days = Math.floor(hours / 24);
|
|
20
|
+
return `${days}d ago`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const usersSubcommand = createSubcommand({
|
|
24
|
+
name: 'users',
|
|
25
|
+
aliases: ['user'],
|
|
26
|
+
description: 'List known Coder Hub users',
|
|
27
|
+
tags: ['read-only', 'fast', 'requires-auth'],
|
|
28
|
+
idempotent: true,
|
|
29
|
+
requires: { auth: true, org: true },
|
|
30
|
+
examples: [
|
|
31
|
+
{
|
|
32
|
+
command: getCommand('coder users'),
|
|
33
|
+
description: 'List known users',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
command: getCommand('coder users --search jenny'),
|
|
37
|
+
description: 'Filter users by display name',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
command: getCommand('coder users --json'),
|
|
41
|
+
description: 'Return users as JSON',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
schema: {
|
|
45
|
+
options: z.object({
|
|
46
|
+
search: z.string().optional().describe('Filter users by display name'),
|
|
47
|
+
url: z.string().optional().describe('Coder API URL override'),
|
|
48
|
+
}),
|
|
49
|
+
},
|
|
50
|
+
async handler(ctx) {
|
|
51
|
+
const { options, opts } = ctx;
|
|
52
|
+
const client = new CoderClient({
|
|
53
|
+
apiKey: ctx.auth.apiKey,
|
|
54
|
+
url: opts?.url,
|
|
55
|
+
orgId: ctx.orgId,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
let users: CoderUser[] = [];
|
|
59
|
+
try {
|
|
60
|
+
const response = await client.listUsers({ search: opts?.search });
|
|
61
|
+
users = response.users;
|
|
62
|
+
} catch (err) {
|
|
63
|
+
if (err instanceof ValidationOutputError) {
|
|
64
|
+
ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
|
|
65
|
+
ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
|
|
66
|
+
}
|
|
67
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
68
|
+
tui.fatal(`Failed to list Coder users: ${msg}`, ErrorCode.NETWORK_ERROR);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (options.json) {
|
|
72
|
+
return users;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (users.length === 0) {
|
|
76
|
+
tui.info('No Coder users found.');
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
tui.table(
|
|
81
|
+
users.map((u) => ({
|
|
82
|
+
User: u.displayName,
|
|
83
|
+
Email: u.email,
|
|
84
|
+
Provider: u.provider,
|
|
85
|
+
'Last Login': formatRelativeTime(u.lastLoginAt),
|
|
86
|
+
'Last Seen': formatRelativeTime(u.lastSeenAt),
|
|
87
|
+
Joined: formatRelativeTime(u.createdAt),
|
|
88
|
+
})),
|
|
89
|
+
[
|
|
90
|
+
{ name: 'User', alignment: 'left' },
|
|
91
|
+
{ name: 'Email', alignment: 'left' },
|
|
92
|
+
{ name: 'Provider', alignment: 'left' },
|
|
93
|
+
{ name: 'Last Login', alignment: 'right' },
|
|
94
|
+
{ name: 'Last Seen', alignment: 'right' },
|
|
95
|
+
{ name: 'Joined', alignment: 'right' },
|
|
96
|
+
]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return users;
|
|
100
|
+
},
|
|
101
|
+
});
|