@trillboards/cli 0.1.0 → 0.1.1

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.
Files changed (59) hide show
  1. package/README.md +414 -43
  2. package/dist/ads-client.d.ts +146 -0
  3. package/dist/ads-client.js +310 -0
  4. package/dist/agent-hook.d.ts +64 -0
  5. package/dist/agent-hook.js +96 -0
  6. package/dist/capability.d.ts +31 -0
  7. package/dist/capability.js +67 -0
  8. package/dist/claude-settings.d.ts +64 -0
  9. package/dist/claude-settings.js +126 -0
  10. package/dist/commands/ads/disable.d.ts +10 -0
  11. package/dist/commands/ads/disable.js +25 -0
  12. package/dist/commands/ads/enable.d.ts +11 -0
  13. package/dist/commands/ads/enable.js +32 -0
  14. package/dist/commands/ads/hook.d.ts +16 -0
  15. package/dist/commands/ads/hook.js +90 -0
  16. package/dist/commands/ads/login.d.ts +21 -0
  17. package/dist/commands/ads/login.js +108 -0
  18. package/dist/commands/ads/start.d.ts +20 -0
  19. package/dist/commands/ads/start.js +175 -0
  20. package/dist/commands/code.d.ts +12 -0
  21. package/dist/commands/code.js +84 -0
  22. package/dist/commands/devices/reconcile.d.ts +10 -0
  23. package/dist/commands/devices/reconcile.js +94 -0
  24. package/dist/commands/generated/generated-device-heartbeat-get.d.ts +14 -0
  25. package/dist/commands/generated/generated-device-heartbeat-get.js +32 -0
  26. package/dist/commands/generated/generated-federated-get-latest-model-descriptor.d.ts +19 -0
  27. package/dist/commands/generated/generated-federated-get-latest-model-descriptor.js +47 -0
  28. package/dist/commands/generated/generated-federated-get-model-weights.d.ts +19 -0
  29. package/dist/commands/generated/generated-federated-get-model-weights.js +47 -0
  30. package/dist/commands/generated/generated-federated-gradient-upload.d.ts +13 -0
  31. package/dist/commands/generated/generated-federated-gradient-upload.js +32 -0
  32. package/dist/commands/generated/generated-federated-performance-upload.d.ts +13 -0
  33. package/dist/commands/generated/generated-federated-performance-upload.js +32 -0
  34. package/dist/commands/generated/generated-federated-taste-upload.d.ts +13 -0
  35. package/dist/commands/generated/generated-federated-taste-upload.js +32 -0
  36. package/dist/commands/generated/generated-post-device-heartbeat.d.ts +15 -0
  37. package/dist/commands/generated/generated-post-device-heartbeat.js +35 -0
  38. package/dist/commands/listen.js +9 -1
  39. package/dist/commands/logs/tail.js +10 -1
  40. package/dist/config.d.ts +21 -0
  41. package/dist/config.js +34 -9
  42. package/dist/dead-time.d.ts +51 -0
  43. package/dist/dead-time.js +119 -0
  44. package/dist/format-helpers.d.ts +6 -0
  45. package/dist/format-helpers.js +10 -0
  46. package/dist/http.d.ts +8 -1
  47. package/dist/http.js +6 -1
  48. package/dist/machine-id.d.ts +12 -0
  49. package/dist/machine-id.js +22 -0
  50. package/dist/pty-wrapper.d.ts +46 -0
  51. package/dist/pty-wrapper.js +245 -0
  52. package/dist/render-runner.d.ts +48 -0
  53. package/dist/render-runner.js +151 -0
  54. package/dist/render.d.ts +82 -0
  55. package/dist/render.js +124 -0
  56. package/dist/user-auth.d.ts +57 -0
  57. package/dist/user-auth.js +110 -0
  58. package/oclif.manifest.json +1140 -255
  59. package/package.json +9 -2
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Agent-ads user-JWT authentication.
3
+ *
4
+ * The agent-ads surface authenticates as the DEVELOPER (a Trillboards user),
5
+ * NOT the partner. The virtual-screen register route only accepts a user JWT
6
+ * (`Authorization: Bearer <JWT>` via middleware/auth.js verifyToken) — the
7
+ * partner `api_key` does not work there. So we maintain a separate token pair:
8
+ * - user_token (access JWT, 1h TTL)
9
+ * - user_refresh_token (refresh token, 90d TTL)
10
+ *
11
+ * Tokens are stored in ~/.trillboards/config.json (same file/perms as api_key)
12
+ * and are NEVER logged.
13
+ */
14
+ /** deviceType sent to /v1/user/login + /v1/user/refresh-token (the route requires it). */
15
+ export declare const AGENT_CLI_DEVICE_TYPE = "agent_cli";
16
+ export interface LoginResult {
17
+ token: string;
18
+ refreshToken: string;
19
+ /** Seconds until the access token expires (server returns 3600). */
20
+ expiresIn: number;
21
+ }
22
+ /**
23
+ * POST /v1/user/login {email, password, deviceType} → {token, refreshToken, expiresIn}.
24
+ *
25
+ * Throws TrillboardsApiError on non-2xx (401 invalid creds, 404 no user, etc.).
26
+ */
27
+ export declare function loginUser(baseUrl: string, email: string, password: string): Promise<LoginResult>;
28
+ export interface RefreshResult {
29
+ /** New access token. NOTE: refresh returns `accessToken`, login returns `token`. */
30
+ token: string;
31
+ refreshToken: string;
32
+ expiresIn: number;
33
+ }
34
+ /**
35
+ * POST /v1/user/refresh-token {refreshToken, deviceType} → {accessToken, refreshToken, expiresIn}.
36
+ *
37
+ * Tokens are rotated: a NEW refresh token comes back too and must be persisted.
38
+ */
39
+ export declare function refreshUserToken(baseUrl: string, refreshToken: string): Promise<RefreshResult>;
40
+ /** Persist a login/refresh result to config (token, refresh token, computed expiry). */
41
+ export declare function persistTokens(result: {
42
+ token: string;
43
+ refreshToken: string;
44
+ expiresIn: number;
45
+ }): void;
46
+ /**
47
+ * Resolve the active user JWT, refreshing it first if it is missing an expiry,
48
+ * expired, or within the refresh skew window. Returns the valid access token, or
49
+ * `null` if there is no usable credential (caller should tell the user to log in).
50
+ *
51
+ * Precedence:
52
+ * 1. TRILLBOARDS_USER_TOKEN env (headless paste — used verbatim, never refreshed)
53
+ * 2. config.user_token (refreshed via config.user_refresh_token when stale)
54
+ */
55
+ export declare function refreshUserTokenIfNeeded(baseUrl: string): Promise<string | null>;
56
+ /** Expose the access-token TTL for tests / callers that want to set expiry. */
57
+ export declare const ACCESS_TOKEN_TTL: number;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Agent-ads user-JWT authentication.
3
+ *
4
+ * The agent-ads surface authenticates as the DEVELOPER (a Trillboards user),
5
+ * NOT the partner. The virtual-screen register route only accepts a user JWT
6
+ * (`Authorization: Bearer <JWT>` via middleware/auth.js verifyToken) — the
7
+ * partner `api_key` does not work there. So we maintain a separate token pair:
8
+ * - user_token (access JWT, 1h TTL)
9
+ * - user_refresh_token (refresh token, 90d TTL)
10
+ *
11
+ * Tokens are stored in ~/.trillboards/config.json (same file/perms as api_key)
12
+ * and are NEVER logged.
13
+ */
14
+ import { readConfig, updateConfig } from './config.js';
15
+ import { TrillboardsApiError, apiRequest } from './http.js';
16
+ /** deviceType sent to /v1/user/login + /v1/user/refresh-token (the route requires it). */
17
+ export const AGENT_CLI_DEVICE_TYPE = 'agent_cli';
18
+ /** Access-token TTL in ms (server issues 1h tokens; expiresIn=3600). */
19
+ const ACCESS_TOKEN_TTL_MS = 3600 * 1000;
20
+ /** Refresh proactively when within this window of expiry (5 minutes). */
21
+ const REFRESH_SKEW_MS = 5 * 60 * 1000;
22
+ /**
23
+ * POST /v1/user/login {email, password, deviceType} → {token, refreshToken, expiresIn}.
24
+ *
25
+ * Throws TrillboardsApiError on non-2xx (401 invalid creds, 404 no user, etc.).
26
+ */
27
+ export async function loginUser(baseUrl, email, password) {
28
+ const res = (await apiRequest('POST', '/v1/user/login', {
29
+ baseUrl,
30
+ body: { email, password, deviceType: AGENT_CLI_DEVICE_TYPE },
31
+ }));
32
+ const token = typeof res.token === 'string' ? res.token : '';
33
+ const refreshToken = typeof res.refreshToken === 'string' ? res.refreshToken : '';
34
+ if (!token || !refreshToken) {
35
+ throw new TrillboardsApiError('Login succeeded but no token was returned', 'LOGIN_NO_TOKEN', 502);
36
+ }
37
+ const expiresIn = typeof res.expiresIn === 'number' && res.expiresIn > 0 ? res.expiresIn : 3600;
38
+ return { token, refreshToken, expiresIn };
39
+ }
40
+ /**
41
+ * POST /v1/user/refresh-token {refreshToken, deviceType} → {accessToken, refreshToken, expiresIn}.
42
+ *
43
+ * Tokens are rotated: a NEW refresh token comes back too and must be persisted.
44
+ */
45
+ export async function refreshUserToken(baseUrl, refreshToken) {
46
+ const res = (await apiRequest('POST', '/v1/user/refresh-token', {
47
+ baseUrl,
48
+ body: { refreshToken, deviceType: AGENT_CLI_DEVICE_TYPE },
49
+ }));
50
+ // Refresh response uses `accessToken` (asymmetric vs login's `token`).
51
+ const token = typeof res.accessToken === 'string' ? res.accessToken : '';
52
+ const newRefresh = typeof res.refreshToken === 'string' ? res.refreshToken : '';
53
+ if (!token) {
54
+ throw new TrillboardsApiError('Refresh succeeded but no access token was returned', 'REFRESH_NO_TOKEN', 502);
55
+ }
56
+ const expiresIn = typeof res.expiresIn === 'number' && res.expiresIn > 0 ? res.expiresIn : 3600;
57
+ // Keep the existing refresh token if the server somehow omitted a rotated one.
58
+ return { token, refreshToken: newRefresh || refreshToken, expiresIn };
59
+ }
60
+ /** Persist a login/refresh result to config (token, refresh token, computed expiry). */
61
+ export function persistTokens(result) {
62
+ updateConfig({
63
+ user_token: result.token,
64
+ user_refresh_token: result.refreshToken,
65
+ user_token_expires_at: Date.now() + result.expiresIn * 1000,
66
+ });
67
+ }
68
+ /**
69
+ * Resolve the active user JWT, refreshing it first if it is missing an expiry,
70
+ * expired, or within the refresh skew window. Returns the valid access token, or
71
+ * `null` if there is no usable credential (caller should tell the user to log in).
72
+ *
73
+ * Precedence:
74
+ * 1. TRILLBOARDS_USER_TOKEN env (headless paste — used verbatim, never refreshed)
75
+ * 2. config.user_token (refreshed via config.user_refresh_token when stale)
76
+ */
77
+ export async function refreshUserTokenIfNeeded(baseUrl) {
78
+ const envToken = process.env.TRILLBOARDS_USER_TOKEN;
79
+ if (envToken && envToken.trim()) {
80
+ // Caller supplied a raw JWT; we have no refresh token for it, so use as-is.
81
+ return envToken.trim();
82
+ }
83
+ const cfg = readConfig();
84
+ const token = cfg.user_token;
85
+ const refreshToken = cfg.user_refresh_token;
86
+ if (!token)
87
+ return null;
88
+ const expiresAt = cfg.user_token_expires_at;
89
+ const nearExpiry = typeof expiresAt !== 'number' || Number.isNaN(expiresAt) || Date.now() >= expiresAt - REFRESH_SKEW_MS;
90
+ if (!nearExpiry) {
91
+ return token;
92
+ }
93
+ // Token is stale/near-expiry. Refresh if we can; otherwise return what we have
94
+ // and let the downstream 401 surface a clear re-login prompt.
95
+ if (!refreshToken)
96
+ return token;
97
+ try {
98
+ const refreshed = await refreshUserToken(baseUrl, refreshToken);
99
+ persistTokens(refreshed);
100
+ return refreshed.token;
101
+ }
102
+ catch {
103
+ // Refresh failed (expired/rotated/revoked refresh token). Fall back to the
104
+ // current access token; if it is also dead the API call will 401 and the
105
+ // command will instruct the user to run `trillboards ads login`.
106
+ return token;
107
+ }
108
+ }
109
+ /** Expose the access-token TTL for tests / callers that want to set expiry. */
110
+ export const ACCESS_TOKEN_TTL = ACCESS_TOKEN_TTL_MS;