@trekagent/claude 0.1.1 → 0.3.0
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 +24 -3
- package/bin/cli.mjs +198 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,11 @@ plugin** (skill + presence hooks + remote MCP server) from the Trek marketplace
|
|
|
5
5
|
API token — so an agent starts reporting presence and working the ready-task frontier the moment
|
|
6
6
|
you restart Claude Code.
|
|
7
7
|
|
|
8
|
+
If no token is already available, `init` **opens your browser** so you can sign in or create an
|
|
9
|
+
account and **pick a project** — the token (and the selected project) are then delivered straight
|
|
10
|
+
back to the installer over a localhost loopback listener and written as `TREK_TOKEN` /
|
|
11
|
+
`TREK_PROJECT_ID`. No copy-paste required.
|
|
12
|
+
|
|
8
13
|
## Usage
|
|
9
14
|
|
|
10
15
|
```bash
|
|
@@ -24,14 +29,30 @@ npx @trekagent/claude init --uninstall
|
|
|
24
29
|
| --- | --- | --- |
|
|
25
30
|
| `--project` | (default) | Project scope — write `./.claude/settings.local.json`. |
|
|
26
31
|
| `--user` | | User scope — write `~/.claude/settings.local.json`. |
|
|
27
|
-
| `--token <trk_...>` |
|
|
32
|
+
| `--token <trk_...>` | browser login | Trek API token (skips the browser flow). |
|
|
28
33
|
| `--api-url <url>` | `https://api.trekagent.io` | Trek API base URL. |
|
|
34
|
+
| `--cockpit-url <url>` | `https://console.trekagent.io` | Cockpit base URL used for browser login. |
|
|
29
35
|
| `--project-id <uuid>` | `$TREK_PROJECT_ID` | Bind a default Trek project. |
|
|
30
36
|
| `--marketplace <owner>/<repo>` | `trekagent/trek-claude-plugin` or `$TREK_MARKETPLACE` | GitHub repo hosting the plugin marketplace. |
|
|
37
|
+
| `--login` | | Force a fresh browser login, ignoring any saved token. |
|
|
38
|
+
| `--no-browser` | | Skip the browser flow and paste a token manually. |
|
|
31
39
|
| `--uninstall` | | Remove the plugin + Trek env for the chosen scope. |
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
### How the token is resolved
|
|
42
|
+
|
|
43
|
+
`init` finds a token in this order:
|
|
44
|
+
|
|
45
|
+
1. `--token <trk_...>` flag.
|
|
46
|
+
2. `$TREK_TOKEN` environment variable.
|
|
47
|
+
3. A `trk_` token already wired into project `./.claude/settings.local.json` or user
|
|
48
|
+
`~/.claude/settings.local.json` (skipped when `--login` is passed).
|
|
49
|
+
4. **Browser login** (interactive terminals, unless `--no-browser`): opens the cockpit
|
|
50
|
+
`cli-auth` page, you sign in / sign up and select a project, and the token plus the chosen
|
|
51
|
+
project are delivered back automatically over a loopback listener bound to `127.0.0.1`. The
|
|
52
|
+
selected project is written as `TREK_PROJECT_ID` (an explicit `--project-id` still applies to
|
|
53
|
+
the non-browser paths).
|
|
54
|
+
5. Manual paste prompt — the fallback for `--no-browser`, non-interactive shells, or if the
|
|
55
|
+
browser flow times out (3 min). Points you at **Settings → API tokens** in the cockpit.
|
|
35
56
|
|
|
36
57
|
## What `init` does
|
|
37
58
|
|
package/bin/cli.mjs
CHANGED
|
@@ -4,22 +4,31 @@
|
|
|
4
4
|
// Installs the Trek plugin (skill + presence hooks + remote MCP server) from the Trek
|
|
5
5
|
// marketplace, then wires your API token so the MCP server and hooks authenticate.
|
|
6
6
|
//
|
|
7
|
+
// By default, if no token is already available, init opens your browser to sign in and
|
|
8
|
+
// the token is created + delivered back automatically over a localhost loopback listener.
|
|
9
|
+
//
|
|
7
10
|
// npx @trekagent/claude init # project scope (writes ./.claude/settings.local.json)
|
|
8
11
|
// npx @trekagent/claude init --user # user scope (writes ~/.claude/settings.local.json)
|
|
9
12
|
// npx @trekagent/claude init --token trk_... --api-url https://api.trekagent.io
|
|
13
|
+
// npx @trekagent/claude init --login # force a fresh browser login
|
|
14
|
+
// npx @trekagent/claude init --no-browser # skip browser, paste a token manually
|
|
10
15
|
// npx @trekagent/claude init --marketplace owner/repo # override the marketplace source
|
|
11
16
|
// npx @trekagent/claude init --uninstall
|
|
12
17
|
//
|
|
13
18
|
// Pure Node, no deps. Idempotent: re-running never duplicates or clobbers your other settings.
|
|
14
19
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
15
20
|
import { dirname, join } from 'node:path';
|
|
16
|
-
import { homedir } from 'node:os';
|
|
21
|
+
import { homedir, hostname } from 'node:os';
|
|
17
22
|
import { createInterface } from 'node:readline';
|
|
18
|
-
import { spawnSync } from 'node:child_process';
|
|
23
|
+
import { spawnSync, spawn } from 'node:child_process';
|
|
24
|
+
import { createServer } from 'node:http';
|
|
25
|
+
import { randomUUID } from 'node:crypto';
|
|
19
26
|
|
|
20
27
|
// --- defaults --------------------------------------------------------------
|
|
21
28
|
const DEFAULT_API_URL = 'https://api.trekagent.io';
|
|
22
|
-
const
|
|
29
|
+
const DEFAULT_COCKPIT_URL = 'https://console.trekagent.io';
|
|
30
|
+
const COCKPIT_SETTINGS_URL = `${DEFAULT_COCKPIT_URL}/settings`;
|
|
31
|
+
const LOGIN_TIMEOUT_MS = 180000; // 3 min to authorize in the browser
|
|
23
32
|
const PLUGIN = 'trek';
|
|
24
33
|
const MARKETPLACE = 'trek'; // the `name` in the marketplace's marketplace.json
|
|
25
34
|
// GitHub org/repo hosting the plugin marketplace.
|
|
@@ -42,7 +51,7 @@ const warn = (s) => log(` ${c.yellow('!!')} ${s}`);
|
|
|
42
51
|
// --- arg parsing -----------------------------------------------------------
|
|
43
52
|
function parseArgs(argv) {
|
|
44
53
|
const args = { _: [], flags: {} };
|
|
45
|
-
const bools = new Set(['user', 'project', 'uninstall', 'help', 'force']);
|
|
54
|
+
const bools = new Set(['user', 'project', 'uninstall', 'help', 'force', 'no-browser', 'login']);
|
|
46
55
|
for (let i = 0; i < argv.length; i++) {
|
|
47
56
|
const a = argv[i];
|
|
48
57
|
if (a === '-h') { args.flags.help = true; continue; }
|
|
@@ -86,9 +95,152 @@ function claude(bin, args) {
|
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
// --- token -----------------------------------------------------------------
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
const looksLikeToken = (t) => typeof t === 'string' && t.startsWith('trk_');
|
|
99
|
+
|
|
100
|
+
// Derive the cockpit base URL: explicit --cockpit-url wins, otherwise fall back
|
|
101
|
+
// to the default cockpit (used both for the default api base and any custom one).
|
|
102
|
+
function cockpitBase(flags) {
|
|
103
|
+
if (typeof flags['cockpit-url'] === 'string' && flags['cockpit-url']) {
|
|
104
|
+
return flags['cockpit-url'].replace(/\/+$/, '');
|
|
105
|
+
}
|
|
106
|
+
return DEFAULT_COCKPIT_URL;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Reuse a token already wired into project or user settings.local.json.
|
|
110
|
+
function detectExistingToken() {
|
|
111
|
+
const candidates = [
|
|
112
|
+
join(CWD, '.claude', 'settings.local.json'),
|
|
113
|
+
join(homedir(), '.claude', 'settings.local.json'),
|
|
114
|
+
];
|
|
115
|
+
for (const path of candidates) {
|
|
116
|
+
const cur = readJson(path);
|
|
117
|
+
const tok = cur && cur.env && cur.env.TREK_TOKEN;
|
|
118
|
+
if (looksLikeToken(tok)) return { token: tok, path };
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Open a URL in the user's default browser, cross-platform. Tolerates failure.
|
|
124
|
+
function openBrowser(url) {
|
|
125
|
+
try {
|
|
126
|
+
let cmd, args;
|
|
127
|
+
if (process.platform === 'darwin') { cmd = 'open'; args = [url]; }
|
|
128
|
+
else if (process.platform === 'win32') { cmd = 'cmd'; args = ['/c', 'start', '', url]; }
|
|
129
|
+
else { cmd = 'xdg-open'; args = [url]; }
|
|
130
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
131
|
+
child.on('error', () => {});
|
|
132
|
+
child.unref();
|
|
133
|
+
return true;
|
|
134
|
+
} catch { return false; }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const SUCCESS_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>Trek CLI connected</title>
|
|
138
|
+
<style>body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;background:#0b0d10;color:#e8eaed;
|
|
139
|
+
display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
|
|
140
|
+
.card{text-align:center;max-width:30rem;padding:2rem}h1{font-size:1.4rem}p{color:#9aa0a6}</style></head>
|
|
141
|
+
<body><div class="card"><h1>Trek CLI connected ✓</h1>
|
|
142
|
+
<p>You can close this tab and return to your terminal.</p></div></body></html>`;
|
|
143
|
+
const errorHtml = (msg) => `<!doctype html><html><head><meta charset="utf-8"><title>Trek CLI</title>
|
|
144
|
+
<style>body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;background:#0b0d10;color:#e8eaed;
|
|
145
|
+
display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
|
|
146
|
+
.card{text-align:center;max-width:30rem;padding:2rem}h1{font-size:1.4rem}p{color:#f28b82}</style></head>
|
|
147
|
+
<body><div class="card"><h1>Trek CLI</h1><p>${String(msg).replace(/[<>&]/g, '')}</p></div></body></html>`;
|
|
148
|
+
|
|
149
|
+
// Loopback browser-login flow. Resolves with { token, projectId } on success, or
|
|
150
|
+
// null if it could not complete (timeout / browser failure / user error) so the
|
|
151
|
+
// caller can fall back. The cockpit success callback contract is:
|
|
152
|
+
// success: http://127.0.0.1:<port>/callback?token=<trk_...>&project=<projectId>&state=<nonce>
|
|
153
|
+
// error: http://127.0.0.1:<port>/callback?error=<msg>&state=<nonce>
|
|
154
|
+
// Project selection is mandatory in the cockpit, so `project` is normally present.
|
|
155
|
+
function browserLogin(flags) {
|
|
156
|
+
return new Promise((resolve) => {
|
|
157
|
+
const expectedState = randomUUID();
|
|
158
|
+
let settled = false;
|
|
159
|
+
let timer = null;
|
|
160
|
+
|
|
161
|
+
const finish = (result) => {
|
|
162
|
+
if (settled) return;
|
|
163
|
+
settled = true;
|
|
164
|
+
if (timer) clearTimeout(timer);
|
|
165
|
+
try { server.close(); } catch {}
|
|
166
|
+
resolve(result);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const server = createServer((req, res) => {
|
|
170
|
+
let url;
|
|
171
|
+
try { url = new URL(req.url, 'http://127.0.0.1'); }
|
|
172
|
+
catch { res.writeHead(204).end(); return; }
|
|
173
|
+
|
|
174
|
+
if (url.pathname !== '/callback') {
|
|
175
|
+
// favicon.ico and any other path: ignore.
|
|
176
|
+
res.writeHead(204).end();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const token = url.searchParams.get('token');
|
|
181
|
+
const state = url.searchParams.get('state');
|
|
182
|
+
const error = url.searchParams.get('error');
|
|
183
|
+
const projectId = url.searchParams.get('project') || '';
|
|
184
|
+
|
|
185
|
+
// A mismatched state must NOT resolve — keep waiting.
|
|
186
|
+
if (state !== expectedState) {
|
|
187
|
+
res.writeHead(400, { 'content-type': 'text/html; charset=utf-8' });
|
|
188
|
+
res.end(errorHtml('Invalid state — this login request did not originate from this terminal.'));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (error) {
|
|
193
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
194
|
+
res.end(errorHtml(`Login failed: ${error}`));
|
|
195
|
+
warn(`browser login failed: ${error}`);
|
|
196
|
+
finish(null);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (looksLikeToken(token)) {
|
|
201
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
202
|
+
res.end(SUCCESS_HTML);
|
|
203
|
+
finish({ token, projectId });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Hit /callback with no usable token — show an error but keep waiting.
|
|
208
|
+
res.writeHead(400, { 'content-type': 'text/html; charset=utf-8' });
|
|
209
|
+
res.end(errorHtml('No token received.'));
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
server.on('error', (err) => {
|
|
213
|
+
warn(`could not start local login listener: ${err.message}`);
|
|
214
|
+
finish(null);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
server.listen(0, '127.0.0.1', () => {
|
|
218
|
+
const port = server.address().port;
|
|
219
|
+
const label = `Claude Code @ ${hostname()}`;
|
|
220
|
+
const base = cockpitBase(flags);
|
|
221
|
+
const authUrl = `${base}/cli-auth?port=${port}&state=${encodeURIComponent(expectedState)}&name=${encodeURIComponent(label)}`;
|
|
222
|
+
|
|
223
|
+
log();
|
|
224
|
+
log(`Opening your browser to sign in to Trek…`);
|
|
225
|
+
const opened = openBrowser(authUrl);
|
|
226
|
+
if (!opened) {
|
|
227
|
+
log(`Could not open a browser automatically. Open this URL to continue:`);
|
|
228
|
+
log(` ${c.cyan(authUrl)}`);
|
|
229
|
+
} else {
|
|
230
|
+
log(c.dim(` If it doesn't open, visit: ${authUrl}`));
|
|
231
|
+
}
|
|
232
|
+
log(`Waiting for you to authorize in the browser… ${c.dim('(Ctrl+C to cancel)')}`);
|
|
233
|
+
|
|
234
|
+
timer = setTimeout(() => {
|
|
235
|
+
warn('timed out waiting for browser login.');
|
|
236
|
+
finish(null);
|
|
237
|
+
}, LOGIN_TIMEOUT_MS);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Manual paste fallback (also used for --no-browser and non-TTY).
|
|
243
|
+
async function pasteToken() {
|
|
92
244
|
log();
|
|
93
245
|
log(`Trek needs an API token (${c.cyan('trk_...')}).`);
|
|
94
246
|
log(`Mint one in the cockpit: ${c.cyan(COCKPIT_SETTINGS_URL)} (Settings → API tokens).`);
|
|
@@ -99,6 +251,30 @@ async function resolveToken(flags) {
|
|
|
99
251
|
return tok;
|
|
100
252
|
}
|
|
101
253
|
|
|
254
|
+
// Resolve a token (and, where available, a project id) following this precedence.
|
|
255
|
+
// Always returns { token, projectId }; projectId is '' for every path except a
|
|
256
|
+
// successful browser login, where the cockpit's mandatory project selection is
|
|
257
|
+
// delivered back over the loopback callback.
|
|
258
|
+
async function resolveToken(flags) {
|
|
259
|
+
// 1. explicit flag
|
|
260
|
+
if (typeof flags.token === 'string' && flags.token) return { token: flags.token, projectId: '' };
|
|
261
|
+
// 2. environment
|
|
262
|
+
if (process.env.TREK_TOKEN) { skip('using TREK_TOKEN from environment'); return { token: process.env.TREK_TOKEN, projectId: '' }; }
|
|
263
|
+
// 3. reuse an existing token from settings (unless --login forces re-auth)
|
|
264
|
+
if (!flags.login) {
|
|
265
|
+
const existing = detectExistingToken();
|
|
266
|
+
if (existing) { skip(`reusing TREK_TOKEN from ${rel(existing.path)}`); return { token: existing.token, projectId: '' }; }
|
|
267
|
+
}
|
|
268
|
+
// 4. browser login (interactive, unless --no-browser)
|
|
269
|
+
if (process.stdin.isTTY && !flags['no-browser']) {
|
|
270
|
+
const result = await browserLogin(flags);
|
|
271
|
+
if (result && looksLikeToken(result.token)) { ok('signed in via browser'); return { token: result.token, projectId: result.projectId || '' }; }
|
|
272
|
+
warn('falling back to manual token entry.');
|
|
273
|
+
}
|
|
274
|
+
// 5. manual paste fallback (also the --no-browser / non-TTY path)
|
|
275
|
+
return { token: await pasteToken(), projectId: '' };
|
|
276
|
+
}
|
|
277
|
+
|
|
102
278
|
// --- settings.local.json: merge env block ----------------------------------
|
|
103
279
|
function writeTokenEnv(claudeDir, apiUrl, token, projectId) {
|
|
104
280
|
const path = join(claudeDir, 'settings.local.json');
|
|
@@ -149,7 +325,7 @@ function installPlugin(bin, marketplaceRepo) {
|
|
|
149
325
|
async function init(flags) {
|
|
150
326
|
const userScope = !!flags.user;
|
|
151
327
|
const apiUrl = (typeof flags['api-url'] === 'string' && flags['api-url']) || DEFAULT_API_URL;
|
|
152
|
-
const
|
|
328
|
+
const flagProjectId = typeof flags['project-id'] === 'string' ? flags['project-id'] : process.env.TREK_PROJECT_ID || '';
|
|
153
329
|
const marketplaceRepo = (typeof flags.marketplace === 'string' && flags.marketplace) || DEFAULT_MARKETPLACE_REPO;
|
|
154
330
|
|
|
155
331
|
log(c.bold(`\nTrek installer — ${userScope ? 'user' : 'project'} scope`));
|
|
@@ -165,7 +341,10 @@ async function init(flags) {
|
|
|
165
341
|
installPlugin(bin, marketplaceRepo);
|
|
166
342
|
}
|
|
167
343
|
|
|
168
|
-
const token = await resolveToken(flags);
|
|
344
|
+
const { token, projectId: browserProjectId } = await resolveToken(flags);
|
|
345
|
+
// The browser flow's mandatory project selection wins; otherwise fall back to
|
|
346
|
+
// the explicit --project-id / $TREK_PROJECT_ID resolution.
|
|
347
|
+
const projectId = browserProjectId || flagProjectId;
|
|
169
348
|
const claudeDir = userScope ? join(homedir(), '.claude') : join(CWD, '.claude');
|
|
170
349
|
writeTokenEnv(claudeDir, apiUrl, token, projectId);
|
|
171
350
|
if (!userScope) ensureGitignore(CWD, ['.claude/settings.local.json']);
|
|
@@ -211,20 +390,26 @@ ${c.bold('Usage')}
|
|
|
211
390
|
${c.bold('Options')}
|
|
212
391
|
--project Project scope: write ./.claude/settings.local.json (default)
|
|
213
392
|
--user User scope: write ~/.claude/settings.local.json
|
|
214
|
-
--token <trk_...> Trek API token (
|
|
393
|
+
--token <trk_...> Trek API token (skips browser login)
|
|
215
394
|
--api-url <url> Trek API base URL (default ${DEFAULT_API_URL})
|
|
395
|
+
--cockpit-url <url> Cockpit base URL for browser login (default ${DEFAULT_COCKPIT_URL})
|
|
216
396
|
--project-id <uuid> Bind a default Trek project (sets TREK_PROJECT_ID)
|
|
217
397
|
--marketplace <o/r> GitHub owner/repo of the plugin marketplace (else $TREK_MARKETPLACE)
|
|
398
|
+
--login Force a fresh browser login (ignore any saved token)
|
|
399
|
+
--no-browser Skip browser login; paste a token manually
|
|
218
400
|
--uninstall Remove the plugin + Trek env for the chosen scope
|
|
219
401
|
-h, --help Show this help
|
|
220
402
|
|
|
221
403
|
${c.bold('What init does')}
|
|
222
404
|
1. claude plugin marketplace add trekagent/trek-claude-plugin
|
|
223
405
|
2. claude plugin install ${PLUGIN}@${MARKETPLACE}
|
|
224
|
-
3.
|
|
406
|
+
3. resolves a token (flag → env → saved → browser login → manual paste)
|
|
407
|
+
4. writes TREK_TOKEN / TREK_API_URL into .claude/settings.local.json (gitignored)
|
|
225
408
|
|
|
226
|
-
|
|
227
|
-
|
|
409
|
+
By default, if no token is already available init opens your browser to sign in /
|
|
410
|
+
create an account; the token is created and delivered back automatically over a
|
|
411
|
+
localhost listener. The plugin ships the skill, presence hooks, and the remote MCP
|
|
412
|
+
server; the token in settings.local.json is what makes them authenticate. Re-running is safe.
|
|
228
413
|
`;
|
|
229
414
|
|
|
230
415
|
async function main() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trekagent/claude",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "One-command installer for Trek: installs the Trek Claude Code plugin (skill + hooks + MCP) from the Trek marketplace and wires your API token.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|