@agentuity/cli 2.0.6 → 2.0.8
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/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/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/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} +37 -33
- 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 +54 -4
- 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 +25 -34
- 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 +88 -117
- 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 +9 -3
- 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/cmd/dev/sync.js +5 -5
- package/dist/cmd/dev/sync.js.map +1 -1
- 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 +14 -3
- 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 +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/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/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} +44 -45
- package/src/cmd/coder/index.ts +54 -4
- package/src/cmd/coder/list.ts +28 -65
- 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 +104 -141
- package/src/cmd/coder/tui-init.ts +13 -4
- 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/cmd/dev/sync.ts +5 -5
- package/src/coder-hub-url.ts +32 -0
- package/src/config.ts +17 -3
- package/src/internal-logger.ts +83 -2
- package/src/keychain.ts +68 -39
- package/dist/cmd/coder/hub-url.d.ts +0 -36
- package/dist/cmd/coder/hub-url.d.ts.map +0 -1
- package/dist/cmd/coder/hub-url.js +0 -106
- 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/src/cmd/coder/hub-url.ts +0 -111
package/src/cmd/coder/start.ts
CHANGED
|
@@ -1,85 +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 {
|
|
10
|
+
import { resolveExtensionPath, resolveExtensionRuntimeModulePath } from './extension-path';
|
|
9
11
|
import { probeHubInitAccess } from './tui-init';
|
|
10
12
|
|
|
11
|
-
/**
|
|
12
|
-
* Resolve the Coder extension path.
|
|
13
|
-
*
|
|
14
|
-
* Priority:
|
|
15
|
-
* 1. --extension flag (explicit override)
|
|
16
|
-
* 2. AGENTUITY_CODER_EXTENSION env var
|
|
17
|
-
* 3. Installed @agentuity/coder package (node_modules)
|
|
18
|
-
* 4. Local dev path relative to CLI package (SDK monorepo)
|
|
19
|
-
*/
|
|
20
|
-
function resolveExtensionPath(flagPath?: string): string | null {
|
|
21
|
-
// 1. Explicit flag
|
|
22
|
-
if (flagPath) {
|
|
23
|
-
const resolved = resolve(flagPath);
|
|
24
|
-
if (existsSync(resolved)) return resolved;
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// 2. Env var
|
|
29
|
-
const envPath = process.env.AGENTUITY_CODER_EXTENSION;
|
|
30
|
-
if (envPath) {
|
|
31
|
-
const resolved = resolve(envPath);
|
|
32
|
-
if (existsSync(resolved)) return resolved;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// 3. Installed npm package in cwd
|
|
36
|
-
const cwdNodeModules = resolve(process.cwd(), 'node_modules', '@agentuity', 'coder');
|
|
37
|
-
if (existsSync(cwdNodeModules)) return cwdNodeModules;
|
|
38
|
-
|
|
39
|
-
// 4. SDK monorepo sibling (for development)
|
|
40
|
-
// This file is at packages/cli/src/cmd/coder/start.ts — 5 levels up to SDK root
|
|
41
|
-
try {
|
|
42
|
-
const cliDir = dirname(new URL(import.meta.url).pathname);
|
|
43
|
-
const sdkRoot = resolve(cliDir, '..', '..', '..', '..', '..');
|
|
44
|
-
const coderPath = join(sdkRoot, 'packages', 'coder');
|
|
45
|
-
if (existsSync(join(coderPath, 'src', 'index.ts'))) return coderPath;
|
|
46
|
-
} catch {
|
|
47
|
-
// Not in SDK monorepo
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
13
|
/**
|
|
54
14
|
* Find the `pi` binary.
|
|
55
15
|
*
|
|
56
16
|
* Priority:
|
|
57
17
|
* 1. --pi flag (explicit override)
|
|
58
18
|
* 2. AGENTUITY_CODER_PI_PATH env var
|
|
59
|
-
* 3.
|
|
19
|
+
* 3. Bundled pi from coder-tui's node_modules/.bin/pi
|
|
20
|
+
* 4. `pi` on PATH (fallback)
|
|
60
21
|
*/
|
|
61
|
-
function resolvePiBinary(flagPath?: string): string {
|
|
22
|
+
async function resolvePiBinary(flagPath?: string, extensionDir?: string): Promise<string> {
|
|
62
23
|
if (flagPath) return flagPath;
|
|
63
24
|
const envPath = process.env.AGENTUITY_CODER_PI_PATH;
|
|
64
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
|
+
|
|
65
43
|
return 'pi';
|
|
66
44
|
}
|
|
67
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
|
+
|
|
68
56
|
export const startSubcommand = createSubcommand({
|
|
69
57
|
name: 'start',
|
|
58
|
+
aliases: ['run'],
|
|
70
59
|
description: 'Start a Pi coding session connected to the Coder Hub',
|
|
71
60
|
tags: ['fast', 'requires-auth'],
|
|
61
|
+
requires: { auth: true, org: true },
|
|
72
62
|
examples: [
|
|
73
63
|
{
|
|
74
64
|
command: getCommand('coder start'),
|
|
75
65
|
description: 'Start Pi with auto-detected Hub and extension',
|
|
76
66
|
},
|
|
77
67
|
{
|
|
78
|
-
command: getCommand('coder start --
|
|
68
|
+
command: getCommand('coder start --url ws://127.0.0.1:3500/api/ws'),
|
|
79
69
|
description: 'Start with explicit Hub URL',
|
|
80
70
|
},
|
|
81
71
|
{
|
|
82
|
-
command: getCommand('coder start --extension ~/repos/agentuity/sdk/packages/coder'),
|
|
72
|
+
command: getCommand('coder start --extension ~/repos/agentuity/sdk/packages/coder-tui'),
|
|
83
73
|
description: 'Start with explicit extension path',
|
|
84
74
|
},
|
|
85
75
|
{
|
|
@@ -87,7 +77,7 @@ export const startSubcommand = createSubcommand({
|
|
|
87
77
|
description: 'Start as a specific agent role',
|
|
88
78
|
},
|
|
89
79
|
{
|
|
90
|
-
command: getCommand('coder start --remote
|
|
80
|
+
command: getCommand('coder start --remote codesess_abc123456789'),
|
|
91
81
|
description: 'Connect to an existing sandbox session remotely',
|
|
92
82
|
},
|
|
93
83
|
{
|
|
@@ -107,7 +97,7 @@ export const startSubcommand = createSubcommand({
|
|
|
107
97
|
],
|
|
108
98
|
schema: {
|
|
109
99
|
options: z.object({
|
|
110
|
-
|
|
100
|
+
url: z.string().optional().describe('Coder API URL override'),
|
|
111
101
|
extension: z.string().optional().describe('Coder extension path override'),
|
|
112
102
|
pi: z.string().optional().describe('Path to pi binary'),
|
|
113
103
|
agent: z.string().optional().describe('Agent role (e.g. scout, builder)'),
|
|
@@ -125,31 +115,25 @@ export const startSubcommand = createSubcommand({
|
|
|
125
115
|
.optional()
|
|
126
116
|
.describe('Git repo URL to clone in the sandbox (used with --sandbox)'),
|
|
127
117
|
}),
|
|
118
|
+
aliases: {
|
|
119
|
+
remote: ['session'],
|
|
120
|
+
},
|
|
128
121
|
},
|
|
129
122
|
async handler(ctx) {
|
|
130
123
|
const { opts, options } = ctx;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const initProbe = await probeHubInitAccess(hubHttpUrl);
|
|
124
|
+
const client = new CoderClient({
|
|
125
|
+
apiKey: ctx.auth.apiKey,
|
|
126
|
+
url: opts?.url,
|
|
127
|
+
orgId: ctx.orgId,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const hubHttpUrl = await client.getUrl();
|
|
131
|
+
const hubWsUrl = toCoderHubWsUrl(hubHttpUrl);
|
|
132
|
+
|
|
133
|
+
const initProbe = await probeHubInitAccess(hubHttpUrl, {
|
|
134
|
+
apiKey: ctx.auth.apiKey,
|
|
135
|
+
});
|
|
144
136
|
if (!initProbe.ok) {
|
|
145
|
-
if (initProbe.code === 'unauthorized') {
|
|
146
|
-
tui.fatal(
|
|
147
|
-
`Coder Hub at ${hubHttpUrl} requires authentication.\n\nSet AGENTUITY_CODER_API_KEY in your shell and retry.\n\nServer said: ${initProbe.message}`,
|
|
148
|
-
ErrorCode.NETWORK_ERROR
|
|
149
|
-
);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
137
|
tui.fatal(
|
|
154
138
|
`Could not bootstrap the Coder Hub at ${hubHttpUrl}: ${initProbe.message}`,
|
|
155
139
|
ErrorCode.NETWORK_ERROR
|
|
@@ -158,17 +142,27 @@ export const startSubcommand = createSubcommand({
|
|
|
158
142
|
}
|
|
159
143
|
|
|
160
144
|
// Resolve extension path
|
|
161
|
-
const extensionPath = resolveExtensionPath(opts?.extension);
|
|
145
|
+
const extensionPath = await resolveExtensionPath(opts?.extension);
|
|
162
146
|
if (!extensionPath) {
|
|
163
147
|
tui.fatal(
|
|
164
|
-
'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',
|
|
165
149
|
ErrorCode.CONFIG_INVALID
|
|
166
150
|
);
|
|
167
151
|
return;
|
|
168
152
|
}
|
|
169
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
|
+
|
|
170
164
|
// Resolve pi binary
|
|
171
|
-
const piBinary = resolvePiBinary(opts?.pi);
|
|
165
|
+
const piBinary = await resolvePiBinary(opts?.pi, extensionPath);
|
|
172
166
|
|
|
173
167
|
// ── Remote mode: resolve session ID ──
|
|
174
168
|
let remoteSessionId: string | undefined;
|
|
@@ -180,32 +174,14 @@ export const startSubcommand = createSubcommand({
|
|
|
180
174
|
} else {
|
|
181
175
|
// No session ID — fetch connectable sessions and show picker
|
|
182
176
|
try {
|
|
183
|
-
type SessionInfo = {
|
|
184
|
-
id: string;
|
|
185
|
-
label: string;
|
|
186
|
-
status: string;
|
|
187
|
-
task: string | null;
|
|
188
|
-
createdAt: string;
|
|
189
|
-
};
|
|
190
|
-
|
|
191
177
|
const sessions = await tui.spinner({
|
|
192
178
|
message: 'Fetching connectable sessions…',
|
|
193
|
-
callback: async () =>
|
|
194
|
-
const resp = await fetch(`${hubHttpUrl}/api/hub/sessions/connectable`, {
|
|
195
|
-
headers: hubFetchHeaders(),
|
|
196
|
-
signal: AbortSignal.timeout(10_000),
|
|
197
|
-
});
|
|
198
|
-
if (!resp.ok) {
|
|
199
|
-
throw new Error(`${resp.status} ${resp.statusText}`);
|
|
200
|
-
}
|
|
201
|
-
const data = (await resp.json()) as { sessions: SessionInfo[] };
|
|
202
|
-
return data.sessions;
|
|
203
|
-
},
|
|
179
|
+
callback: async () => (await client.listConnectableSessions()).sessions,
|
|
204
180
|
});
|
|
205
181
|
|
|
206
182
|
if (sessions.length === 0) {
|
|
207
183
|
tui.fatal(
|
|
208
|
-
|
|
184
|
+
`No connectable sandbox sessions found.\n\nCreate one with:\n ${getCommand('coder start --sandbox "your task"')}`,
|
|
209
185
|
ErrorCode.CONFIG_INVALID
|
|
210
186
|
);
|
|
211
187
|
return;
|
|
@@ -214,20 +190,18 @@ export const startSubcommand = createSubcommand({
|
|
|
214
190
|
const prompt = tui.createPrompt();
|
|
215
191
|
remoteSessionId = await prompt.select<string>({
|
|
216
192
|
message: 'Select a sandbox session to connect to',
|
|
217
|
-
options: sessions.map((s) => {
|
|
193
|
+
options: sessions.map((s: CoderSessionListItem) => {
|
|
218
194
|
const age = timeSince(new Date(s.createdAt));
|
|
219
|
-
const
|
|
220
|
-
const label = taskPreview
|
|
221
|
-
? `${s.label} ${tui.muted(`(${s.status}, ${age})`)} — ${taskPreview}`
|
|
222
|
-
: `${s.label} ${tui.muted(`(${s.status}, ${age})`)}`;
|
|
195
|
+
const label = `${s.label} ${tui.muted(`(${s.status}, ${age})`)}`;
|
|
223
196
|
return {
|
|
224
|
-
value: s.
|
|
197
|
+
value: s.sessionId,
|
|
225
198
|
label,
|
|
226
|
-
hint: s.
|
|
199
|
+
hint: s.sessionId,
|
|
227
200
|
};
|
|
228
201
|
}),
|
|
229
202
|
});
|
|
230
203
|
} catch (err) {
|
|
204
|
+
logValidationIssues(ctx, err);
|
|
231
205
|
const msg = err instanceof Error ? err.message : String(err);
|
|
232
206
|
if (msg === 'User cancelled') return;
|
|
233
207
|
tui.fatal(`Failed to fetch connectable sessions: ${msg}`, ErrorCode.NETWORK_ERROR);
|
|
@@ -250,10 +224,12 @@ export const startSubcommand = createSubcommand({
|
|
|
250
224
|
}
|
|
251
225
|
|
|
252
226
|
try {
|
|
253
|
-
const { runRemoteTui } = await
|
|
227
|
+
const { runRemoteTui } = await loadRemoteTui();
|
|
254
228
|
await runRemoteTui({
|
|
255
229
|
hubWsUrl,
|
|
256
230
|
sessionId: remoteSessionId,
|
|
231
|
+
apiKey: ctx.auth.apiKey,
|
|
232
|
+
orgId: ctx.orgId,
|
|
257
233
|
});
|
|
258
234
|
} catch (err) {
|
|
259
235
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -273,14 +249,11 @@ export const startSubcommand = createSubcommand({
|
|
|
273
249
|
return;
|
|
274
250
|
}
|
|
275
251
|
|
|
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
252
|
// Build request body
|
|
283
|
-
const body:
|
|
253
|
+
const body: {
|
|
254
|
+
task: string;
|
|
255
|
+
repo?: { url: string };
|
|
256
|
+
} = { task };
|
|
284
257
|
if (opts?.repo) {
|
|
285
258
|
body.repo = { url: opts.repo };
|
|
286
259
|
}
|
|
@@ -291,23 +264,10 @@ export const startSubcommand = createSubcommand({
|
|
|
291
264
|
|
|
292
265
|
let sessionId: string;
|
|
293
266
|
try {
|
|
294
|
-
const
|
|
295
|
-
method: 'POST',
|
|
296
|
-
headers: hubFetchHeaders({ 'Content-Type': 'application/json' }),
|
|
297
|
-
body: JSON.stringify(body),
|
|
298
|
-
signal: AbortSignal.timeout(10_000),
|
|
299
|
-
});
|
|
300
|
-
if (!resp.ok) {
|
|
301
|
-
const errText = await resp.text();
|
|
302
|
-
tui.fatal(
|
|
303
|
-
`Failed to create sandbox session: ${resp.status} ${errText}`,
|
|
304
|
-
ErrorCode.NETWORK_ERROR
|
|
305
|
-
);
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
const sessionInfo = (await resp.json()) as { sessionId: string };
|
|
267
|
+
const sessionInfo = await client.createSession(body);
|
|
309
268
|
sessionId = sessionInfo.sessionId;
|
|
310
269
|
} catch (err) {
|
|
270
|
+
logValidationIssues(ctx, err);
|
|
311
271
|
const msg = err instanceof Error ? err.message : String(err);
|
|
312
272
|
tui.fatal(`Failed to create sandbox session: ${msg}`, ErrorCode.NETWORK_ERROR);
|
|
313
273
|
return;
|
|
@@ -327,18 +287,10 @@ export const startSubcommand = createSubcommand({
|
|
|
327
287
|
while (Date.now() - pollStart < POLL_TIMEOUT) {
|
|
328
288
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
329
289
|
try {
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (pollResp.ok) {
|
|
335
|
-
const data = (await pollResp.json()) as {
|
|
336
|
-
participants?: Array<{ role: string }>;
|
|
337
|
-
};
|
|
338
|
-
if (data.participants?.some((p) => p.role === 'lead')) {
|
|
339
|
-
driverConnected = true;
|
|
340
|
-
break;
|
|
341
|
-
}
|
|
290
|
+
const data = await client.listParticipants(sessionId);
|
|
291
|
+
if (data.participants?.some((p) => p.role === 'lead')) {
|
|
292
|
+
driverConnected = true;
|
|
293
|
+
break;
|
|
342
294
|
}
|
|
343
295
|
} catch {
|
|
344
296
|
// Network blip — keep polling
|
|
@@ -357,10 +309,12 @@ export const startSubcommand = createSubcommand({
|
|
|
357
309
|
tui.newline();
|
|
358
310
|
|
|
359
311
|
try {
|
|
360
|
-
const { runRemoteTui } = await
|
|
312
|
+
const { runRemoteTui } = await loadRemoteTui();
|
|
361
313
|
await runRemoteTui({
|
|
362
314
|
hubWsUrl,
|
|
363
315
|
sessionId,
|
|
316
|
+
apiKey: ctx.auth.apiKey,
|
|
317
|
+
orgId: ctx.orgId,
|
|
364
318
|
});
|
|
365
319
|
} catch (err) {
|
|
366
320
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -374,9 +328,7 @@ export const startSubcommand = createSubcommand({
|
|
|
374
328
|
...(process.env as Record<string, string>),
|
|
375
329
|
AGENTUITY_CODER_HUB_URL: hubWsUrl,
|
|
376
330
|
};
|
|
377
|
-
|
|
378
|
-
const cliApiKey = process.env.AGENTUITY_CODER_API_KEY;
|
|
379
|
-
if (cliApiKey) env.AGENTUITY_CODER_API_KEY = cliApiKey;
|
|
331
|
+
env.AGENTUITY_CODER_API_KEY = ctx.auth.apiKey;
|
|
380
332
|
|
|
381
333
|
if (opts?.agent) {
|
|
382
334
|
env.AGENTUITY_CODER_AGENT = opts.agent;
|
|
@@ -404,7 +356,18 @@ export const startSubcommand = createSubcommand({
|
|
|
404
356
|
stderr: 'inherit',
|
|
405
357
|
});
|
|
406
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
|
+
|
|
407
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
|
+
|
|
408
371
|
process.exit(exitCode);
|
|
409
372
|
} catch (err) {
|
|
410
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
|
| {
|
|
@@ -21,11 +19,22 @@ function normalizeErrorMessage(payload: unknown, fallback: string): string {
|
|
|
21
19
|
|
|
22
20
|
export async function probeHubInitAccess(
|
|
23
21
|
hubHttpUrl: string,
|
|
24
|
-
|
|
22
|
+
options?: {
|
|
23
|
+
apiKey?: string | null;
|
|
24
|
+
fetchImpl?: typeof fetch;
|
|
25
|
+
}
|
|
25
26
|
): Promise<HubInitProbeResult> {
|
|
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
|
+
}
|
|
34
|
+
|
|
26
35
|
try {
|
|
27
36
|
const response = await fetchImpl(`${hubHttpUrl}/api/hub/init`, {
|
|
28
|
-
headers
|
|
37
|
+
headers,
|
|
29
38
|
signal: AbortSignal.timeout(5_000),
|
|
30
39
|
});
|
|
31
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
|
+
});
|