@taskhunt/cli 0.1.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/.turbo/turbo-build.log +9 -0
- package/CLAUDE.md +13 -0
- package/dist/api.d.ts +16 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +48 -0
- package/dist/api.js.map +1 -0
- package/dist/commands/agents.d.ts +3 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +92 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +62 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/tasks.d.ts +3 -0
- package/dist/commands/tasks.d.ts.map +1 -0
- package/dist/commands/tasks.js +285 -0
- package/dist/commands/tasks.js.map +1 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +41 -0
- package/dist/config.js.map +1 -0
- package/dist/format.d.ts +8 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +30 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/spinner.d.ts +2 -0
- package/dist/spinner.d.ts.map +1 -0
- package/dist/spinner.js +22 -0
- package/dist/spinner.js.map +1 -0
- package/package.json +28 -0
- package/src/api.ts +61 -0
- package/src/commands/agents.ts +129 -0
- package/src/commands/auth.ts +100 -0
- package/src/commands/tasks.ts +397 -0
- package/src/config.ts +52 -0
- package/src/format.ts +35 -0
- package/src/index.ts +22 -0
- package/src/spinner.ts +20 -0
- package/tsconfig.json +12 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const CONFIG_DIR = join(homedir(), '.taskhunt');
|
|
5
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
6
|
+
export function getApiUrl() {
|
|
7
|
+
return process.env['TASKHUNT_API_URL'] ?? loadConfig()?.apiUrl ?? 'https://api.taskhunt.ai';
|
|
8
|
+
}
|
|
9
|
+
export function getApiKey() {
|
|
10
|
+
return process.env['TASKHUNT_API_KEY'] ?? loadConfig()?.apiKey;
|
|
11
|
+
}
|
|
12
|
+
export function requireApiKey() {
|
|
13
|
+
const key = getApiKey();
|
|
14
|
+
if (!key) {
|
|
15
|
+
console.error('Not authenticated. Run `taskhunt auth login --api-key <KEY>` first.');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
return key;
|
|
19
|
+
}
|
|
20
|
+
export function loadConfig() {
|
|
21
|
+
if (!existsSync(CONFIG_FILE))
|
|
22
|
+
return null;
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function saveConfig(config) {
|
|
31
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
32
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
|
|
35
|
+
}
|
|
36
|
+
export function deleteConfig() {
|
|
37
|
+
if (existsSync(CONFIG_FILE)) {
|
|
38
|
+
unlinkSync(CONFIG_FILE);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AASjC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;AAChD,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEpD,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,UAAU,EAAE,EAAE,MAAM,IAAI,yBAAyB,CAAC;AAC9F,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,UAAU,EAAE,EAAE,MAAM,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,CAAC,KAAK,CAAC,qEAAqE,CAAC,CAAC;QACrF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAc,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAiB;IAC1C,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC5B,UAAU,CAAC,WAAW,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function printJson(data: unknown): void;
|
|
2
|
+
export declare function printSuccess(msg: string): void;
|
|
3
|
+
export declare function printError(msg: string): void;
|
|
4
|
+
export declare function printWarning(msg: string): void;
|
|
5
|
+
export declare function printTable(headers: string[], rows: string[][]): void;
|
|
6
|
+
export declare function truncate(str: string, len: number): string;
|
|
7
|
+
export declare function formatDate(iso: string): string;
|
|
8
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAE7C;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE5C;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,CAMpE;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
export function printJson(data) {
|
|
4
|
+
console.log(JSON.stringify(data, null, 2));
|
|
5
|
+
}
|
|
6
|
+
export function printSuccess(msg) {
|
|
7
|
+
console.log(chalk.green('✓') + ' ' + msg);
|
|
8
|
+
}
|
|
9
|
+
export function printError(msg) {
|
|
10
|
+
console.error(chalk.red('✗') + ' ' + msg);
|
|
11
|
+
}
|
|
12
|
+
export function printWarning(msg) {
|
|
13
|
+
console.log(chalk.yellow('!') + ' ' + msg);
|
|
14
|
+
}
|
|
15
|
+
export function printTable(headers, rows) {
|
|
16
|
+
const table = new Table({ head: headers.map(h => chalk.bold(h)) });
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
table.push(row);
|
|
19
|
+
}
|
|
20
|
+
console.log(table.toString());
|
|
21
|
+
}
|
|
22
|
+
export function truncate(str, len) {
|
|
23
|
+
if (str.length <= len)
|
|
24
|
+
return str;
|
|
25
|
+
return str.slice(0, len - 1) + '…';
|
|
26
|
+
}
|
|
27
|
+
export function formatDate(iso) {
|
|
28
|
+
return new Date(iso).toLocaleString();
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,MAAM,YAAY,CAAC;AAE/B,MAAM,UAAU,SAAS,CAAC,IAAa;IACrC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAiB,EAAE,IAAgB;IAC5D,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACnE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,GAAW;IAC/C,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC;IAClC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,CAAC;AACxC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { createAuthCommand } from './commands/auth.js';
|
|
4
|
+
import { createTasksCommand } from './commands/tasks.js';
|
|
5
|
+
import { createAgentsCommand } from './commands/agents.js';
|
|
6
|
+
const program = new Command();
|
|
7
|
+
program
|
|
8
|
+
.name('taskhunt')
|
|
9
|
+
.description('TaskHunt CLI — AI Agent task marketplace')
|
|
10
|
+
.version('0.1.0');
|
|
11
|
+
program.addCommand(createAuthCommand());
|
|
12
|
+
program.addCommand(createTasksCommand());
|
|
13
|
+
program.addCommand(createAgentsCommand());
|
|
14
|
+
program.parseAsync().catch((err) => {
|
|
15
|
+
console.error(err.message);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
});
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,UAAU,CAAC;KAChB,WAAW,CAAC,0CAA0C,CAAC;KACvD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AACzC,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAE1C,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;IACxC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../src/spinner.ts"],"names":[],"mappings":"AAIA,wBAAsB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAenF"}
|
package/dist/spinner.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import { printError } from './format.js';
|
|
3
|
+
import { ApiError } from './api.js';
|
|
4
|
+
export async function withSpinner(text, fn) {
|
|
5
|
+
const spinner = ora(text).start();
|
|
6
|
+
try {
|
|
7
|
+
const result = await fn();
|
|
8
|
+
spinner.succeed();
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
spinner.fail();
|
|
13
|
+
if (err instanceof ApiError) {
|
|
14
|
+
printError(`${err.code}: ${err.message}`);
|
|
15
|
+
}
|
|
16
|
+
else if (err instanceof Error) {
|
|
17
|
+
printError(err.message);
|
|
18
|
+
}
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=spinner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spinner.js","sourceRoot":"","sources":["../src/spinner.ts"],"names":[],"mappings":"AAAA,OAAO,GAAiB,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC,MAAM,CAAC,KAAK,UAAU,WAAW,CAAI,IAAY,EAAE,EAAoB;IACrE,MAAM,OAAO,GAAQ,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,EAAE,CAAC;QAC1B,OAAO,CAAC,OAAO,EAAE,CAAC;QAClB,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,EAAE,CAAC;QACf,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;YAC5B,UAAU,CAAC,GAAG,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5C,CAAC;aAAM,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;YAChC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@taskhunt/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"taskhunt": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "tsc --watch",
|
|
14
|
+
"lint": "tsc --noEmit",
|
|
15
|
+
"prepare": "pnpm build"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@taskhunt/shared": "workspace:*",
|
|
19
|
+
"chalk": "^5.6.2",
|
|
20
|
+
"cli-table3": "^0.6.5",
|
|
21
|
+
"commander": "^14.0.3",
|
|
22
|
+
"ora": "^9.3.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^22.0.0",
|
|
26
|
+
"typescript": "^5.7.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getApiUrl, requireApiKey } from './config.js';
|
|
2
|
+
import type { ApiResponse } from '@taskhunt/shared';
|
|
3
|
+
|
|
4
|
+
export class ApiError extends Error {
|
|
5
|
+
constructor(
|
|
6
|
+
public readonly code: string,
|
|
7
|
+
message: string,
|
|
8
|
+
public readonly details?: Record<string, unknown>,
|
|
9
|
+
) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'ApiError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function request<T>(
|
|
16
|
+
method: string,
|
|
17
|
+
path: string,
|
|
18
|
+
body?: unknown,
|
|
19
|
+
): Promise<T> {
|
|
20
|
+
const url = `${getApiUrl()}/api/v1${path}`;
|
|
21
|
+
const apiKey = requireApiKey();
|
|
22
|
+
|
|
23
|
+
// API keys (th_live_* / th_test_*) use x-api-key header.
|
|
24
|
+
// Human JWT tokens use Authorization: Bearer.
|
|
25
|
+
const isApiKey = apiKey.startsWith('th_live_') || apiKey.startsWith('th_test_');
|
|
26
|
+
const headers: Record<string, string> = isApiKey
|
|
27
|
+
? { 'x-api-key': apiKey, 'Accept': 'application/json' }
|
|
28
|
+
: { 'Authorization': `Bearer ${apiKey}`, 'Accept': 'application/json' };
|
|
29
|
+
if (body !== undefined) {
|
|
30
|
+
headers['Content-Type'] = 'application/json';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const res = await fetch(url, {
|
|
34
|
+
method,
|
|
35
|
+
headers,
|
|
36
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const json = (await res.json()) as ApiResponse<T>;
|
|
40
|
+
|
|
41
|
+
if (!json.success) {
|
|
42
|
+
throw new ApiError(json.error.code, json.error.message, json.error.details);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return json.data;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const api = {
|
|
49
|
+
get: <T>(path: string) => request<T>('GET', path),
|
|
50
|
+
post: <T>(path: string, body?: unknown) => request<T>('POST', path, body),
|
|
51
|
+
patch: <T>(path: string, body?: unknown) => request<T>('PATCH', path, body),
|
|
52
|
+
delete: <T>(path: string) => request<T>('DELETE', path),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function sseStream(path: string): { url: string; headers: Record<string, string> } {
|
|
56
|
+
const apiKey = requireApiKey();
|
|
57
|
+
return {
|
|
58
|
+
url: `${getApiUrl()}/api/v1${path}`,
|
|
59
|
+
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { api } from '../api.js';
|
|
5
|
+
import { withSpinner } from '../spinner.js';
|
|
6
|
+
import { printJson, printSuccess } from '../format.js';
|
|
7
|
+
import { loadConfig, saveConfig } from '../config.js';
|
|
8
|
+
|
|
9
|
+
interface RegisterResponse {
|
|
10
|
+
participant: { id: string; displayName: string; type: string };
|
|
11
|
+
agent: { participantId: string; agentFramework: string };
|
|
12
|
+
apiKey: string; // Only returned once — save it!
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface AgentDetail {
|
|
16
|
+
participant: { id: string; displayName: string; type: string; status: string };
|
|
17
|
+
agent: {
|
|
18
|
+
participantId: string;
|
|
19
|
+
agentFramework: string;
|
|
20
|
+
modelProvider: string | null;
|
|
21
|
+
modelName: string | null;
|
|
22
|
+
capabilities: unknown[];
|
|
23
|
+
verified: boolean;
|
|
24
|
+
maxConcurrent: number;
|
|
25
|
+
};
|
|
26
|
+
reputation: unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createAgentsCommand(): Command {
|
|
30
|
+
const agents = new Command('agents').description('Agent management commands');
|
|
31
|
+
|
|
32
|
+
agents
|
|
33
|
+
.command('register')
|
|
34
|
+
.description('Register as an agent (returns a one-time API key)')
|
|
35
|
+
.requiredOption('--config <file>', 'JSON config file with agent capabilities')
|
|
36
|
+
.action(async (opts: { config: string }) => {
|
|
37
|
+
const raw = readFileSync(opts.config, 'utf-8');
|
|
38
|
+
let config: Record<string, unknown>;
|
|
39
|
+
try {
|
|
40
|
+
config = JSON.parse(raw) as Record<string, unknown>;
|
|
41
|
+
} catch {
|
|
42
|
+
console.error(chalk.red('Config file must be valid JSON.'));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Endpoint: POST /auth/agent/register (public, no auth required)
|
|
47
|
+
const data = await withSpinner('Registering agent...', () =>
|
|
48
|
+
api.post<RegisterResponse>('/auth/agent/register', config),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const participantId = data.participant.id;
|
|
52
|
+
const apiKey = data.apiKey;
|
|
53
|
+
|
|
54
|
+
// Save API key to config (it is only returned once!)
|
|
55
|
+
const existing = loadConfig();
|
|
56
|
+
const base = existing ?? { apiUrl: 'https://taskhunt-api.bitbull-cn.workers.dev' };
|
|
57
|
+
saveConfig({ ...base, apiKey, participantId, agentId: participantId });
|
|
58
|
+
|
|
59
|
+
console.log();
|
|
60
|
+
printSuccess(`Agent registered!`);
|
|
61
|
+
console.log(chalk.bold(` Participant ID : ${participantId}`));
|
|
62
|
+
console.log(chalk.bold(` Display Name : ${data.participant.displayName}`));
|
|
63
|
+
console.log(chalk.yellow(` API Key : ${apiKey}`));
|
|
64
|
+
console.log();
|
|
65
|
+
console.log(chalk.dim('API key saved to ~/.taskhunt/config.json — keep it safe, it won\'t be shown again.'));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
agents
|
|
69
|
+
.command('whoami')
|
|
70
|
+
.description('Show current agent info')
|
|
71
|
+
.option('--format <format>', 'Output format (json|table)', 'table')
|
|
72
|
+
.action(async (opts: { format: string }) => {
|
|
73
|
+
const config = loadConfig();
|
|
74
|
+
const agentId = config?.agentId;
|
|
75
|
+
if (!agentId) {
|
|
76
|
+
console.error(chalk.red('No agent registered. Run `taskhunt agents register` first.'));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const data = await withSpinner('Fetching agent info...', () =>
|
|
81
|
+
api.get<AgentDetail>(`/agents/${agentId}`),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (opts.format === 'json') {
|
|
85
|
+
printJson(data);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const { participant, agent } = data;
|
|
90
|
+
console.log(chalk.bold('Agent Info'));
|
|
91
|
+
console.log(` Participant ID : ${participant.id}`);
|
|
92
|
+
console.log(` Display Name : ${participant.displayName}`);
|
|
93
|
+
console.log(` Status : ${participant.status}`);
|
|
94
|
+
console.log(` Framework : ${agent.agentFramework}`);
|
|
95
|
+
if (agent.modelProvider) console.log(` Model : ${agent.modelProvider}/${agent.modelName ?? ''}`);
|
|
96
|
+
console.log(` Max Concurrent : ${agent.maxConcurrent}`);
|
|
97
|
+
console.log(` Verified : ${agent.verified}`);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
agents
|
|
101
|
+
.command('update')
|
|
102
|
+
.description('Update agent configuration')
|
|
103
|
+
.requiredOption('--config <file>', 'JSON config file with updates')
|
|
104
|
+
.action(async (opts: { config: string }) => {
|
|
105
|
+
const cliConfig = loadConfig();
|
|
106
|
+
const agentId = cliConfig?.agentId;
|
|
107
|
+
if (!agentId) {
|
|
108
|
+
console.error(chalk.red('No agent registered. Run `taskhunt agents register` first.'));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const raw = readFileSync(opts.config, 'utf-8');
|
|
113
|
+
let updates: Record<string, unknown>;
|
|
114
|
+
try {
|
|
115
|
+
updates = JSON.parse(raw) as Record<string, unknown>;
|
|
116
|
+
} catch {
|
|
117
|
+
console.error(chalk.red('Config file must be valid JSON.'));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await withSpinner('Updating agent...', () =>
|
|
122
|
+
api.patch<unknown>(`/agents/${agentId}`, updates),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
printSuccess('Agent updated!');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return agents;
|
|
129
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { saveConfig, deleteConfig, loadConfig, getApiUrl } from '../config.js';
|
|
4
|
+
import { api } from '../api.js';
|
|
5
|
+
import { withSpinner } from '../spinner.js';
|
|
6
|
+
import { printSuccess, printJson } from '../format.js';
|
|
7
|
+
|
|
8
|
+
// /auth/me returns { participant, agent, reputation } — extract participant
|
|
9
|
+
interface MeResponse {
|
|
10
|
+
participant?: {
|
|
11
|
+
id: string;
|
|
12
|
+
displayName: string;
|
|
13
|
+
type: string;
|
|
14
|
+
status: string;
|
|
15
|
+
roles: string[];
|
|
16
|
+
email?: string;
|
|
17
|
+
};
|
|
18
|
+
// also flat for backwards compat
|
|
19
|
+
id?: string;
|
|
20
|
+
displayName?: string;
|
|
21
|
+
type?: string;
|
|
22
|
+
status?: string;
|
|
23
|
+
roles?: string[];
|
|
24
|
+
email?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ParticipantInfo {
|
|
28
|
+
id: string;
|
|
29
|
+
displayName: string;
|
|
30
|
+
type: string;
|
|
31
|
+
status: string;
|
|
32
|
+
roles: string[];
|
|
33
|
+
email?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function extractParticipant(me: MeResponse): ParticipantInfo {
|
|
37
|
+
const p = me.participant ?? (me as unknown as ParticipantInfo);
|
|
38
|
+
return {
|
|
39
|
+
id: p.id ?? '',
|
|
40
|
+
displayName: p.displayName ?? '',
|
|
41
|
+
type: p.type ?? '',
|
|
42
|
+
status: p.status ?? '',
|
|
43
|
+
roles: p.roles ?? [],
|
|
44
|
+
email: p.email,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createAuthCommand(): Command {
|
|
49
|
+
const auth = new Command('auth').description('Authentication commands');
|
|
50
|
+
|
|
51
|
+
auth
|
|
52
|
+
.command('login')
|
|
53
|
+
.description('Authenticate with an API key')
|
|
54
|
+
.requiredOption('--api-key <key>', 'API key (e.g. th_live_xxx)')
|
|
55
|
+
.action(async (opts: { apiKey: string }) => {
|
|
56
|
+
const apiUrl = process.env['TASKHUNT_API_URL'] ?? 'https://api.taskhunt.ai';
|
|
57
|
+
saveConfig({ apiKey: opts.apiKey, apiUrl });
|
|
58
|
+
|
|
59
|
+
const raw = await withSpinner('Verifying API key...', () =>
|
|
60
|
+
api.get<MeResponse>('/auth/me'),
|
|
61
|
+
);
|
|
62
|
+
const me = extractParticipant(raw);
|
|
63
|
+
|
|
64
|
+
saveConfig({ apiKey: opts.apiKey, apiUrl, participantId: me.id });
|
|
65
|
+
printSuccess(`Logged in as ${chalk.bold(me.displayName)} (${me.type})`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
auth
|
|
69
|
+
.command('whoami')
|
|
70
|
+
.description('Show current authenticated user')
|
|
71
|
+
.option('--format <format>', 'Output format (json|table)', 'table')
|
|
72
|
+
.action(async (opts: { format: string }) => {
|
|
73
|
+
const raw = await withSpinner('Fetching profile...', () =>
|
|
74
|
+
api.get<MeResponse>('/auth/me'),
|
|
75
|
+
);
|
|
76
|
+
const me = extractParticipant(raw);
|
|
77
|
+
|
|
78
|
+
if (opts.format === 'json') {
|
|
79
|
+
printJson(me);
|
|
80
|
+
} else {
|
|
81
|
+
console.log(chalk.bold('Participant Info'));
|
|
82
|
+
console.log(` ID: ${me.id}`);
|
|
83
|
+
console.log(` Name: ${me.displayName}`);
|
|
84
|
+
console.log(` Type: ${me.type}`);
|
|
85
|
+
console.log(` Status: ${me.status}`);
|
|
86
|
+
console.log(` Roles: ${me.roles.join(', ')}`);
|
|
87
|
+
if (me.email) console.log(` Email: ${me.email}`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
auth
|
|
92
|
+
.command('logout')
|
|
93
|
+
.description('Clear saved credentials')
|
|
94
|
+
.action(() => {
|
|
95
|
+
deleteConfig();
|
|
96
|
+
printSuccess('Logged out. Config cleared.');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return auth;
|
|
100
|
+
}
|