@trekagent/claude 0.1.0 → 0.2.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 +21 -3
- package/bin/cli.mjs +184 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,10 @@ 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 — the token is then created and delivered straight back to the installer over a localhost
|
|
10
|
+
loopback listener. No copy-paste required.
|
|
11
|
+
|
|
8
12
|
## Usage
|
|
9
13
|
|
|
10
14
|
```bash
|
|
@@ -24,14 +28,28 @@ npx @trekagent/claude init --uninstall
|
|
|
24
28
|
| --- | --- | --- |
|
|
25
29
|
| `--project` | (default) | Project scope — write `./.claude/settings.local.json`. |
|
|
26
30
|
| `--user` | | User scope — write `~/.claude/settings.local.json`. |
|
|
27
|
-
| `--token <trk_...>` |
|
|
31
|
+
| `--token <trk_...>` | browser login | Trek API token (skips the browser flow). |
|
|
28
32
|
| `--api-url <url>` | `https://api.trekagent.io` | Trek API base URL. |
|
|
33
|
+
| `--cockpit-url <url>` | `https://console.trekagent.io` | Cockpit base URL used for browser login. |
|
|
29
34
|
| `--project-id <uuid>` | `$TREK_PROJECT_ID` | Bind a default Trek project. |
|
|
30
35
|
| `--marketplace <owner>/<repo>` | `trekagent/trek-claude-plugin` or `$TREK_MARKETPLACE` | GitHub repo hosting the plugin marketplace. |
|
|
36
|
+
| `--login` | | Force a fresh browser login, ignoring any saved token. |
|
|
37
|
+
| `--no-browser` | | Skip the browser flow and paste a token manually. |
|
|
31
38
|
| `--uninstall` | | Remove the plugin + Trek env for the chosen scope. |
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
### How the token is resolved
|
|
41
|
+
|
|
42
|
+
`init` finds a token in this order:
|
|
43
|
+
|
|
44
|
+
1. `--token <trk_...>` flag.
|
|
45
|
+
2. `$TREK_TOKEN` environment variable.
|
|
46
|
+
3. A `trk_` token already wired into project `./.claude/settings.local.json` or user
|
|
47
|
+
`~/.claude/settings.local.json` (skipped when `--login` is passed).
|
|
48
|
+
4. **Browser login** (interactive terminals, unless `--no-browser`): opens the cockpit
|
|
49
|
+
`cli-auth` page, you sign in / sign up, and the token is delivered back automatically over a
|
|
50
|
+
loopback listener bound to `127.0.0.1`.
|
|
51
|
+
5. Manual paste prompt — the fallback for `--no-browser`, non-interactive shells, or if the
|
|
52
|
+
browser flow times out (3 min). Points you at **Settings → API tokens** in the cockpit.
|
|
35
53
|
|
|
36
54
|
## What `init` does
|
|
37
55
|
|
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,147 @@ 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 a trk_ token, or null if it could
|
|
150
|
+
// not complete (timeout / browser failure / user error) so the caller can fall back.
|
|
151
|
+
function browserLogin(flags) {
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
const expectedState = randomUUID();
|
|
154
|
+
let settled = false;
|
|
155
|
+
let timer = null;
|
|
156
|
+
|
|
157
|
+
const finish = (result) => {
|
|
158
|
+
if (settled) return;
|
|
159
|
+
settled = true;
|
|
160
|
+
if (timer) clearTimeout(timer);
|
|
161
|
+
try { server.close(); } catch {}
|
|
162
|
+
resolve(result);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const server = createServer((req, res) => {
|
|
166
|
+
let url;
|
|
167
|
+
try { url = new URL(req.url, 'http://127.0.0.1'); }
|
|
168
|
+
catch { res.writeHead(204).end(); return; }
|
|
169
|
+
|
|
170
|
+
if (url.pathname !== '/callback') {
|
|
171
|
+
// favicon.ico and any other path: ignore.
|
|
172
|
+
res.writeHead(204).end();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const token = url.searchParams.get('token');
|
|
177
|
+
const state = url.searchParams.get('state');
|
|
178
|
+
const error = url.searchParams.get('error');
|
|
179
|
+
|
|
180
|
+
// A mismatched state must NOT resolve — keep waiting.
|
|
181
|
+
if (state !== expectedState) {
|
|
182
|
+
res.writeHead(400, { 'content-type': 'text/html; charset=utf-8' });
|
|
183
|
+
res.end(errorHtml('Invalid state — this login request did not originate from this terminal.'));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (error) {
|
|
188
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
189
|
+
res.end(errorHtml(`Login failed: ${error}`));
|
|
190
|
+
warn(`browser login failed: ${error}`);
|
|
191
|
+
finish(null);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (looksLikeToken(token)) {
|
|
196
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
197
|
+
res.end(SUCCESS_HTML);
|
|
198
|
+
finish(token);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Hit /callback with no usable token — show an error but keep waiting.
|
|
203
|
+
res.writeHead(400, { 'content-type': 'text/html; charset=utf-8' });
|
|
204
|
+
res.end(errorHtml('No token received.'));
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
server.on('error', (err) => {
|
|
208
|
+
warn(`could not start local login listener: ${err.message}`);
|
|
209
|
+
finish(null);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
server.listen(0, '127.0.0.1', () => {
|
|
213
|
+
const port = server.address().port;
|
|
214
|
+
const label = `Claude Code @ ${hostname()}`;
|
|
215
|
+
const base = cockpitBase(flags);
|
|
216
|
+
const authUrl = `${base}/cli-auth?port=${port}&state=${encodeURIComponent(expectedState)}&name=${encodeURIComponent(label)}`;
|
|
217
|
+
|
|
218
|
+
log();
|
|
219
|
+
log(`Opening your browser to sign in to Trek…`);
|
|
220
|
+
const opened = openBrowser(authUrl);
|
|
221
|
+
if (!opened) {
|
|
222
|
+
log(`Could not open a browser automatically. Open this URL to continue:`);
|
|
223
|
+
log(` ${c.cyan(authUrl)}`);
|
|
224
|
+
} else {
|
|
225
|
+
log(c.dim(` If it doesn't open, visit: ${authUrl}`));
|
|
226
|
+
}
|
|
227
|
+
log(`Waiting for you to authorize in the browser… ${c.dim('(Ctrl+C to cancel)')}`);
|
|
228
|
+
|
|
229
|
+
timer = setTimeout(() => {
|
|
230
|
+
warn('timed out waiting for browser login.');
|
|
231
|
+
finish(null);
|
|
232
|
+
}, LOGIN_TIMEOUT_MS);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Manual paste fallback (also used for --no-browser and non-TTY).
|
|
238
|
+
async function pasteToken() {
|
|
92
239
|
log();
|
|
93
240
|
log(`Trek needs an API token (${c.cyan('trk_...')}).`);
|
|
94
241
|
log(`Mint one in the cockpit: ${c.cyan(COCKPIT_SETTINGS_URL)} (Settings → API tokens).`);
|
|
@@ -99,6 +246,26 @@ async function resolveToken(flags) {
|
|
|
99
246
|
return tok;
|
|
100
247
|
}
|
|
101
248
|
|
|
249
|
+
async function resolveToken(flags) {
|
|
250
|
+
// 1. explicit flag
|
|
251
|
+
if (typeof flags.token === 'string' && flags.token) return flags.token;
|
|
252
|
+
// 2. environment
|
|
253
|
+
if (process.env.TREK_TOKEN) { skip('using TREK_TOKEN from environment'); return process.env.TREK_TOKEN; }
|
|
254
|
+
// 3. reuse an existing token from settings (unless --login forces re-auth)
|
|
255
|
+
if (!flags.login) {
|
|
256
|
+
const existing = detectExistingToken();
|
|
257
|
+
if (existing) { skip(`reusing TREK_TOKEN from ${rel(existing.path)}`); return existing.token; }
|
|
258
|
+
}
|
|
259
|
+
// 4. browser login (interactive, unless --no-browser)
|
|
260
|
+
if (process.stdin.isTTY && !flags['no-browser']) {
|
|
261
|
+
const tok = await browserLogin(flags);
|
|
262
|
+
if (looksLikeToken(tok)) { ok('signed in via browser'); return tok; }
|
|
263
|
+
warn('falling back to manual token entry.');
|
|
264
|
+
}
|
|
265
|
+
// 5. manual paste fallback (also the --no-browser / non-TTY path)
|
|
266
|
+
return pasteToken();
|
|
267
|
+
}
|
|
268
|
+
|
|
102
269
|
// --- settings.local.json: merge env block ----------------------------------
|
|
103
270
|
function writeTokenEnv(claudeDir, apiUrl, token, projectId) {
|
|
104
271
|
const path = join(claudeDir, 'settings.local.json');
|
|
@@ -211,20 +378,26 @@ ${c.bold('Usage')}
|
|
|
211
378
|
${c.bold('Options')}
|
|
212
379
|
--project Project scope: write ./.claude/settings.local.json (default)
|
|
213
380
|
--user User scope: write ~/.claude/settings.local.json
|
|
214
|
-
--token <trk_...> Trek API token (
|
|
381
|
+
--token <trk_...> Trek API token (skips browser login)
|
|
215
382
|
--api-url <url> Trek API base URL (default ${DEFAULT_API_URL})
|
|
383
|
+
--cockpit-url <url> Cockpit base URL for browser login (default ${DEFAULT_COCKPIT_URL})
|
|
216
384
|
--project-id <uuid> Bind a default Trek project (sets TREK_PROJECT_ID)
|
|
217
385
|
--marketplace <o/r> GitHub owner/repo of the plugin marketplace (else $TREK_MARKETPLACE)
|
|
386
|
+
--login Force a fresh browser login (ignore any saved token)
|
|
387
|
+
--no-browser Skip browser login; paste a token manually
|
|
218
388
|
--uninstall Remove the plugin + Trek env for the chosen scope
|
|
219
389
|
-h, --help Show this help
|
|
220
390
|
|
|
221
391
|
${c.bold('What init does')}
|
|
222
392
|
1. claude plugin marketplace add trekagent/trek-claude-plugin
|
|
223
393
|
2. claude plugin install ${PLUGIN}@${MARKETPLACE}
|
|
224
|
-
3.
|
|
394
|
+
3. resolves a token (flag → env → saved → browser login → manual paste)
|
|
395
|
+
4. writes TREK_TOKEN / TREK_API_URL into .claude/settings.local.json (gitignored)
|
|
225
396
|
|
|
226
|
-
|
|
227
|
-
|
|
397
|
+
By default, if no token is already available init opens your browser to sign in /
|
|
398
|
+
create an account; the token is created and delivered back automatically over a
|
|
399
|
+
localhost listener. The plugin ships the skill, presence hooks, and the remote MCP
|
|
400
|
+
server; the token in settings.local.json is what makes them authenticate. Re-running is safe.
|
|
228
401
|
`;
|
|
229
402
|
|
|
230
403
|
async function main() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trekagent/claude",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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",
|