deepline 0.1.12 → 0.1.20

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 (82) hide show
  1. package/README.md +14 -6
  2. package/dist/cli/index.js +1346 -717
  3. package/dist/cli/index.mjs +1342 -713
  4. package/dist/index.d.mts +199 -23
  5. package/dist/index.d.ts +199 -23
  6. package/dist/index.js +221 -14
  7. package/dist/index.mjs +221 -14
  8. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +214 -77
  9. package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +85 -60
  10. package/dist/repo/apps/play-runner-workers/src/entry.ts +385 -66
  11. package/dist/repo/sdk/src/client.ts +237 -0
  12. package/dist/repo/sdk/src/config.ts +125 -8
  13. package/dist/repo/sdk/src/http.ts +29 -5
  14. package/dist/repo/sdk/src/play.ts +19 -36
  15. package/dist/repo/sdk/src/plays/bundle-play-file.ts +22 -8
  16. package/dist/repo/sdk/src/plays/local-file-discovery.ts +207 -160
  17. package/dist/repo/sdk/src/types.ts +25 -0
  18. package/dist/repo/sdk/src/version.ts +2 -2
  19. package/dist/repo/shared_libs/play-runtime/tool-result.ts +237 -145
  20. package/dist/repo/shared_libs/plays/bundling/index.ts +206 -229
  21. package/dist/repo/shared_libs/plays/dataset.ts +28 -0
  22. package/dist/repo/shared_libs/plays/row-identity.ts +59 -4
  23. package/package.json +5 -4
  24. package/dist/cli/index.js.map +0 -1
  25. package/dist/cli/index.mjs.map +0 -1
  26. package/dist/index.js.map +0 -1
  27. package/dist/index.mjs.map +0 -1
  28. package/dist/repo/apps/play-runner-workers/src/runtime/README.md +0 -21
  29. package/dist/repo/apps/play-runner-workers/src/runtime/batching.ts +0 -177
  30. package/dist/repo/apps/play-runner-workers/src/runtime/execution-plan.ts +0 -52
  31. package/dist/repo/apps/play-runner-workers/src/runtime/tool-batch.ts +0 -100
  32. package/dist/repo/sdk/src/cli/commands/auth.ts +0 -500
  33. package/dist/repo/sdk/src/cli/commands/billing.ts +0 -188
  34. package/dist/repo/sdk/src/cli/commands/csv.ts +0 -123
  35. package/dist/repo/sdk/src/cli/commands/db.ts +0 -119
  36. package/dist/repo/sdk/src/cli/commands/feedback.ts +0 -40
  37. package/dist/repo/sdk/src/cli/commands/org.ts +0 -117
  38. package/dist/repo/sdk/src/cli/commands/play.ts +0 -3441
  39. package/dist/repo/sdk/src/cli/commands/tools.ts +0 -687
  40. package/dist/repo/sdk/src/cli/dataset-stats.ts +0 -415
  41. package/dist/repo/sdk/src/cli/index.ts +0 -148
  42. package/dist/repo/sdk/src/cli/progress.ts +0 -149
  43. package/dist/repo/sdk/src/cli/skills-sync.ts +0 -141
  44. package/dist/repo/sdk/src/cli/trace.ts +0 -61
  45. package/dist/repo/sdk/src/cli/utils.ts +0 -145
  46. package/dist/repo/sdk/src/compat.ts +0 -77
  47. package/dist/repo/shared_libs/observability/node-tracing.ts +0 -129
  48. package/dist/repo/shared_libs/observability/tracing.ts +0 -98
  49. package/dist/repo/shared_libs/play-runtime/context.ts +0 -4242
  50. package/dist/repo/shared_libs/play-runtime/ctx-contract.ts +0 -250
  51. package/dist/repo/shared_libs/play-runtime/ctx-types.ts +0 -725
  52. package/dist/repo/shared_libs/play-runtime/dataset-id.ts +0 -10
  53. package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +0 -304
  54. package/dist/repo/shared_libs/play-runtime/db-session.ts +0 -462
  55. package/dist/repo/shared_libs/play-runtime/live-events.ts +0 -214
  56. package/dist/repo/shared_libs/play-runtime/live-state-contract.ts +0 -50
  57. package/dist/repo/shared_libs/play-runtime/map-execution-frame.ts +0 -114
  58. package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +0 -158
  59. package/dist/repo/shared_libs/play-runtime/progress-emitter.ts +0 -172
  60. package/dist/repo/shared_libs/play-runtime/protocol.ts +0 -121
  61. package/dist/repo/shared_libs/play-runtime/public-play-contract.ts +0 -42
  62. package/dist/repo/shared_libs/play-runtime/result-normalization.ts +0 -33
  63. package/dist/repo/shared_libs/play-runtime/runtime-api.ts +0 -1873
  64. package/dist/repo/shared_libs/play-runtime/runtime-constraints.ts +0 -2
  65. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +0 -201
  66. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-pg.ts +0 -48
  67. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver.ts +0 -84
  68. package/dist/repo/shared_libs/play-runtime/static-pipeline-types.ts +0 -147
  69. package/dist/repo/shared_libs/play-runtime/suspension.ts +0 -68
  70. package/dist/repo/shared_libs/play-runtime/tracing.ts +0 -31
  71. package/dist/repo/shared_libs/play-runtime/waterfall-replay.ts +0 -75
  72. package/dist/repo/shared_libs/play-runtime/worker-api-types.ts +0 -140
  73. package/dist/repo/shared_libs/plays/artifact-transport.ts +0 -14
  74. package/dist/repo/shared_libs/plays/artifact-types.ts +0 -49
  75. package/dist/repo/shared_libs/plays/compiler-manifest.ts +0 -186
  76. package/dist/repo/shared_libs/plays/definition.ts +0 -264
  77. package/dist/repo/shared_libs/plays/file-refs.ts +0 -11
  78. package/dist/repo/shared_libs/plays/rate-limit-scheduler.ts +0 -206
  79. package/dist/repo/shared_libs/plays/resolve-static-pipeline.ts +0 -164
  80. package/dist/repo/shared_libs/plays/runtime-validation.ts +0 -395
  81. package/dist/repo/shared_libs/temporal/constants.ts +0 -39
  82. package/dist/repo/shared_libs/temporal/preview-config.ts +0 -153
@@ -1,500 +0,0 @@
1
- import { writeFileSync, existsSync, mkdirSync } from 'node:fs';
2
- import { hostname } from 'node:os';
3
- import { dirname } from 'node:path';
4
- import { Command } from 'commander';
5
- import { hostEnvFilePath, loadCliEnv, autoDetectBaseUrl, parseEnvFile } from '../../config.js';
6
- import { argsWantJson, openInBrowser } from '../utils.js';
7
-
8
- const EXIT_OK = 0;
9
- const EXIT_AUTH = 1;
10
- const EXIT_SERVER = 2;
11
-
12
- function envFilePath(baseUrl: string): string {
13
- return hostEnvFilePath(baseUrl);
14
- }
15
-
16
- function saveEnvValues(values: Record<string, string>, baseUrl: string): void {
17
- const filePath = envFilePath(baseUrl);
18
- const dir = dirname(filePath);
19
- if (!existsSync(dir)) {
20
- mkdirSync(dir, { recursive: true });
21
- }
22
-
23
- // Merge with existing values
24
- const existing = existsSync(filePath) ? parseEnvFile(filePath) : {};
25
- const merged = { ...existing, ...values };
26
-
27
- const lines = Object.entries(merged)
28
- .filter(([, v]) => v !== '')
29
- .map(([k, v]) => `${k}=${v}`);
30
- writeFileSync(filePath, lines.join('\n') + '\n', 'utf-8');
31
- }
32
-
33
- async function httpJson(
34
- method: string,
35
- url: string,
36
- apiKey: string | null,
37
- body?: unknown,
38
- ): Promise<{ status: number; data: Record<string, unknown> }> {
39
- const headers: Record<string, string> = { 'Content-Type': 'application/json' };
40
- if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
41
-
42
- let response: Response | null = null;
43
- let lastError: unknown = null;
44
- for (const candidateUrl of buildCandidateUrls(url)) {
45
- try {
46
- response = await fetch(candidateUrl, {
47
- method,
48
- headers,
49
- body: body !== undefined ? JSON.stringify(body) : undefined,
50
- });
51
- break;
52
- } catch (error) {
53
- lastError = error;
54
- }
55
- }
56
-
57
- if (!response) {
58
- throw lastError instanceof Error ? lastError : new Error(String(lastError));
59
- }
60
-
61
- let data: Record<string, unknown>;
62
- try {
63
- data = (await response.json()) as Record<string, unknown>;
64
- } catch {
65
- data = {};
66
- }
67
- return { status: response.status, data };
68
- }
69
-
70
- function buildCandidateUrls(url: string): string[] {
71
- try {
72
- const parsed = new URL(url);
73
- const candidates = [url];
74
- const configuredInternalOrigin =
75
- process.env.DEEPLINE_INTERNAL_APP_URL ||
76
- process.env.WORKTREE_INTERNAL_APP_URL ||
77
- (process.env.PORT ? `http://127.0.0.1:${process.env.PORT}` : '');
78
- if (parsed.hostname === 'localhost') {
79
- const loopback = new URL(url);
80
- loopback.hostname = '127.0.0.1';
81
- candidates.push(loopback.toString());
82
- }
83
- if (parsed.hostname.endsWith('.localhost') && configuredInternalOrigin) {
84
- const internal = new URL(url);
85
- const origin = new URL(configuredInternalOrigin);
86
- internal.protocol = origin.protocol;
87
- internal.hostname = origin.hostname;
88
- internal.port = origin.port;
89
- candidates.push(internal.toString());
90
- }
91
- return [...new Set(candidates)];
92
- } catch {
93
- return [url];
94
- }
95
- }
96
-
97
- function sleep(ms: number): Promise<void> {
98
- return new Promise((resolve) => setTimeout(resolve, ms));
99
- }
100
-
101
- function printDeeplineLogo(): void {
102
- if (process.stdout.isTTY && (process.stdout.columns ?? 80) >= 70) {
103
- console.log(' ██████╗ ███████╗ ███████╗ ██████╗ ██╗ ██╗ ███╗ ██╗ ███████╗');
104
- console.log(' ██╔══██╗ ██╔════╝ ██╔════╝ ██╔══██╗ ██║ ██║ ████╗ ██║ ██╔════╝');
105
- console.log(' ██║ ██║ █████╗ █████╗ ██████╔╝ ██║ ██║ ██╔██╗ ██║ █████╗');
106
- console.log(' ██║ ██║ ██╔══╝ ██╔══╝ ██╔═══╝ ██║ ██║ ██║╚██╗██║ ██╔══╝');
107
- console.log(' ██████╔╝ ███████╗ ███████╗ ██║ ███████╗ ██║ ██║ ╚████║ ███████╗');
108
- console.log(' ╚═════╝ ╚══════╝ ╚══════╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝ ╚══════╝');
109
- console.log('');
110
- return;
111
- }
112
- console.log('DEEPLINE');
113
- }
114
-
115
- function printClaimSuccessBanner(statusData: Record<string, unknown>): void {
116
- console.log('');
117
- printDeeplineLogo();
118
- console.log('✓ All set! Your CLI is connected.');
119
- if (statusData.org_name) {
120
- console.log(` • Signed in with organization: ${statusData.org_name}`);
121
- }
122
- }
123
-
124
- export async function handleRegister(args: string[]): Promise<number> {
125
- const baseUrl = autoDetectBaseUrl().replace(/\/$/, '');
126
-
127
- let orgName = '';
128
- let agentName = '';
129
- let noWait = false;
130
-
131
- for (let i = 0; i < args.length; i++) {
132
- if (args[i] === '--org-name' && args[i + 1]) orgName = args[++i]!;
133
- else if (args[i] === '--agent-name' && args[i + 1]) agentName = args[++i]!;
134
- else if (args[i] === '--no-wait') noWait = true;
135
- }
136
-
137
- if (!agentName) {
138
- try {
139
- agentName = hostname() || 'Deepline CLI (TS)';
140
- } catch {
141
- agentName = 'Deepline CLI (TS)';
142
- }
143
- }
144
-
145
- const payload: Record<string, string> = {};
146
- if (orgName) payload.org_name = orgName;
147
- if (agentName) payload.agent_name = agentName;
148
-
149
- const { status, data } = await httpJson('POST', `${baseUrl}/api/v2/auth/cli/register`, null, payload);
150
-
151
- if (status >= 400) {
152
- console.error(`Auth register failed (status ${status}).`);
153
- if (data.error) console.error(String(data.error));
154
- return EXIT_SERVER;
155
- }
156
-
157
- const claimUrl = String(data.claim_url || '');
158
- const claimToken = String(data.claim_token || '');
159
-
160
- if (claimToken) {
161
- saveEnvValues({
162
- DEEPLINE_ORIGIN_URL: baseUrl,
163
- DEEPLINE_CLAIM_TOKEN: claimToken,
164
- }, baseUrl);
165
- }
166
-
167
- if (claimUrl) {
168
- console.log(' Opening approval page in your browser.');
169
- console.log(` If it didn't open, cmd+click: ${claimUrl}`);
170
- openInBrowser(claimUrl);
171
- }
172
-
173
- if (data.cli_message) {
174
- console.log(String(data.cli_message));
175
- }
176
-
177
- if (noWait) return EXIT_OK;
178
-
179
- if (!claimToken) {
180
- console.error('Missing claim token from register response.');
181
- return EXIT_SERVER;
182
- }
183
-
184
- // Poll for claim approval
185
- while (true) {
186
- const { status: s, data: statusData } = await httpJson(
187
- 'POST',
188
- `${baseUrl}/api/v2/auth/cli/status`,
189
- null,
190
- { claim_token: claimToken, reveal: true },
191
- );
192
-
193
- if (s === 401 || s === 403) {
194
- console.log('Status: unauthorized');
195
- return EXIT_AUTH;
196
- }
197
- if (s >= 500 || s === 0 || s === 400) {
198
- await sleep(2000);
199
- continue;
200
- }
201
- if (s >= 400) {
202
- console.error(`Auth status error (status ${s}).`);
203
- return EXIT_SERVER;
204
- }
205
-
206
- const state = String(statusData.status || '').toLowerCase();
207
- if (state === 'claimed') {
208
- const apiKey = String(statusData.api_key || '');
209
- if (apiKey) {
210
- saveEnvValues({
211
- DEEPLINE_ORIGIN_URL: baseUrl,
212
- DEEPLINE_API_KEY: apiKey,
213
- DEEPLINE_CLAIM_TOKEN: '',
214
- }, baseUrl);
215
- printClaimSuccessBanner(statusData);
216
- return EXIT_OK;
217
- }
218
- }
219
- if (state === 'expired') {
220
- console.log('That approval link expired. Please run: deepline auth register');
221
- return EXIT_AUTH;
222
- }
223
- await sleep(2000);
224
- }
225
- }
226
-
227
- export async function handleWait(args: string[]): Promise<number> {
228
- const baseUrl = autoDetectBaseUrl().replace(/\/$/, '');
229
- let timeoutSeconds = 300;
230
-
231
- for (let i = 0; i < args.length; i++) {
232
- if (args[i] === '--timeout' && args[i + 1]) {
233
- const parsed = Number.parseInt(args[++i]!, 10);
234
- if (Number.isFinite(parsed) && parsed > 0) {
235
- timeoutSeconds = parsed;
236
- }
237
- }
238
- }
239
-
240
- const env = loadCliEnv(baseUrl);
241
- if (env.DEEPLINE_API_KEY?.trim()) {
242
- console.log('Already connected.');
243
- return EXIT_OK;
244
- }
245
-
246
- const claimToken = env.DEEPLINE_CLAIM_TOKEN?.trim() || '';
247
- if (!claimToken) {
248
- console.error('No pending approval. Run: deepline auth register --no-wait');
249
- return EXIT_AUTH;
250
- }
251
-
252
- const deadline = Date.now() + timeoutSeconds * 1000;
253
- while (Date.now() <= deadline) {
254
- const { status, data } = await httpJson(
255
- 'POST',
256
- `${baseUrl}/api/v2/auth/cli/status`,
257
- null,
258
- { claim_token: claimToken, reveal: true },
259
- );
260
-
261
- if (status === 401 || status === 403) {
262
- console.error('Claim is invalid. Run: deepline auth register');
263
- return EXIT_AUTH;
264
- }
265
- if (status >= 500 || status === 0 || status === 400) {
266
- await sleep(2000);
267
- continue;
268
- }
269
- if (status >= 400) {
270
- console.error(`Auth status error (status ${status}).`);
271
- return EXIT_SERVER;
272
- }
273
-
274
- const state = String(data.status || '').toLowerCase();
275
- if (state === 'claimed') {
276
- const apiKey = String(data.api_key || '');
277
- if (apiKey) {
278
- saveEnvValues({
279
- DEEPLINE_ORIGIN_URL: baseUrl,
280
- DEEPLINE_API_KEY: apiKey,
281
- DEEPLINE_CLAIM_TOKEN: '',
282
- }, baseUrl);
283
- printClaimSuccessBanner(data);
284
- return EXIT_OK;
285
- }
286
- }
287
- if (state === 'expired') {
288
- console.error('That approval link expired. Run: deepline auth register');
289
- return EXIT_AUTH;
290
- }
291
-
292
- await sleep(2000);
293
- }
294
-
295
- console.error('Still pending. Approve the browser link, then run: deepline auth wait');
296
- return EXIT_AUTH;
297
- }
298
-
299
- export async function handleStatus(args: string[]): Promise<number> {
300
- const baseUrl = autoDetectBaseUrl().replace(/\/$/, '');
301
- const reveal = args.includes('--reveal');
302
- const jsonOutput = argsWantJson(args);
303
- let hostStatusPayload: Record<string, unknown> | null = null;
304
-
305
- // Health check
306
- try {
307
- const { status: hStatus, data: hData } = await httpJson('GET', `${baseUrl}/api/v2/health`, null);
308
- if (hStatus === 200) {
309
- hostStatusPayload = {
310
- host: baseUrl,
311
- hostStatus: hData.status || 'ok',
312
- hostVersion: hData.version || '(unknown)',
313
- };
314
- if (!jsonOutput) {
315
- console.log(`Host: ${baseUrl}`);
316
- console.log(`Host status: ${hData.status || 'ok'}`);
317
- console.log(`Host version: ${hData.version || '(unknown)'}`);
318
- }
319
- }
320
- } catch {
321
- hostStatusPayload = {
322
- host: baseUrl,
323
- hostStatus: 'unreachable',
324
- hostVersion: null,
325
- };
326
- if (!jsonOutput) {
327
- console.log(`Host: ${baseUrl} (unreachable)`);
328
- }
329
- }
330
-
331
- const env = loadCliEnv(baseUrl);
332
- const apiKey =
333
- process.env.DEEPLINE_API_KEY?.trim() ||
334
- env.DEEPLINE_API_KEY ||
335
- '';
336
-
337
- if (!apiKey) {
338
- if (env.DEEPLINE_CLAIM_TOKEN?.trim()) {
339
- if (jsonOutput) {
340
- process.stdout.write(`${JSON.stringify({
341
- ...(hostStatusPayload ?? { host: baseUrl }),
342
- status: 'pending',
343
- connected: false,
344
- next: 'deepline auth wait',
345
- })}\n`);
346
- return EXIT_OK;
347
- }
348
- console.log('Status: pending');
349
- console.log('Run: deepline auth wait');
350
- return EXIT_OK;
351
- }
352
- if (jsonOutput) {
353
- process.stdout.write(`${JSON.stringify({
354
- ...(hostStatusPayload ?? { host: baseUrl }),
355
- status: 'not connected',
356
- connected: false,
357
- next: 'deepline auth register',
358
- })}\n`);
359
- return EXIT_OK;
360
- }
361
- console.log('Status: not connected');
362
- console.log('Run: deepline auth register');
363
- return EXIT_OK;
364
- }
365
-
366
- const { status, data } = await httpJson('POST', `${baseUrl}/api/v2/auth/cli/status`, apiKey, {
367
- api_key: apiKey,
368
- reveal,
369
- });
370
-
371
- if (status === 401 || status === 403) {
372
- if (jsonOutput) {
373
- process.stdout.write(`${JSON.stringify({
374
- ...(hostStatusPayload ?? { host: baseUrl }),
375
- status: 'unauthorized',
376
- connected: false,
377
- next: 'deepline auth register',
378
- })}\n`);
379
- return EXIT_AUTH;
380
- }
381
- console.log('Status: unauthorized');
382
- console.log('Run: deepline auth register');
383
- return EXIT_AUTH;
384
- }
385
- if (status >= 400) {
386
- console.error(`Auth status error (status ${status}).`);
387
- return EXIT_SERVER;
388
- }
389
-
390
- const payload = {
391
- ...(hostStatusPayload ?? { host: baseUrl }),
392
- status: data.status || '(unknown)',
393
- connected: true,
394
- rateLimitTier: data.rate_limit_tier || '(unknown)',
395
- workspace: {
396
- id: data.org_id ?? null,
397
- name: data.org_name ?? null,
398
- slug: data.org_slug ?? null,
399
- },
400
- user: {
401
- id: data.user_id ?? null,
402
- },
403
- examples: Array.isArray(data.examples) ? data.examples : [],
404
- };
405
-
406
- if (jsonOutput) {
407
- process.stdout.write(`${JSON.stringify(payload)}\n`);
408
- } else {
409
- console.log(`Status: ${payload.status}`);
410
- console.log(`Rate limit tier: ${payload.rateLimitTier}`);
411
- if (payload.workspace.name) console.log(`Workspace: ${payload.workspace.name}`);
412
- if (payload.workspace.slug) console.log(`Workspace slug: ${payload.workspace.slug}`);
413
- if (payload.workspace.id != null) console.log(`Org ID: ${payload.workspace.id}`);
414
- if (payload.user.id != null) console.log(`User ID: ${payload.user.id}`);
415
- if (payload.examples.length > 0) {
416
- console.log('Examples:');
417
- for (const example of payload.examples.slice(0, 3)) {
418
- console.log(` ${String(example)}`);
419
- }
420
- }
421
- }
422
-
423
- if (reveal) {
424
- const apiKeyResp = String(data.api_key || apiKey);
425
- if (apiKeyResp) {
426
- saveEnvValues({
427
- DEEPLINE_ORIGIN_URL: baseUrl,
428
- DEEPLINE_API_KEY: apiKeyResp,
429
- DEEPLINE_CLAIM_TOKEN: '',
430
- }, baseUrl);
431
- console.log(`Saved API key to ${envFilePath(baseUrl)}`);
432
- }
433
- }
434
-
435
- return EXIT_OK;
436
- }
437
-
438
- export function registerAuthCommands(program: Command): void {
439
- const auth = program
440
- .command('auth')
441
- .description('Register this device and show CLI auth status.')
442
- .addHelpText(
443
- 'after',
444
- `
445
- Common commands:
446
- deepline auth register
447
- deepline auth status
448
- deepline auth status --json
449
- `,
450
- );
451
-
452
- auth
453
- .command('register')
454
- .description('Register this device and open the approval page in your browser.')
455
- .option('--org-name <name>', 'Workspace name to prefill')
456
- .option('--agent-name <name>', 'Agent name to register')
457
- .option('--no-wait', 'Return immediately after opening the approval page')
458
- .action(async (options) => {
459
- process.exitCode = await handleRegister([
460
- ...(options.orgName ? ['--org-name', options.orgName] : []),
461
- ...(options.agentName ? ['--agent-name', options.agentName] : []),
462
- ...(options.noWait || options.wait === false ? ['--no-wait'] : []),
463
- ]);
464
- });
465
-
466
- auth
467
- .command('wait')
468
- .description('Wait for a pending browser approval and save the API key.')
469
- .option('--timeout <seconds>', 'Maximum seconds to wait', '300')
470
- .action(async (options) => {
471
- process.exitCode = await handleWait([
472
- ...(options.timeout ? ['--timeout', options.timeout] : []),
473
- ]);
474
- });
475
-
476
- auth
477
- .command('status')
478
- .description('Show the current CLI auth and workspace status.')
479
- .addHelpText(
480
- 'after',
481
- `
482
- Notes:
483
- Shows the host, server health, connection state, active workspace, and user id.
484
- Use --reveal only when you intentionally want the server to return and persist
485
- the API key into this host's CLI auth file.
486
-
487
- Examples:
488
- deepline auth status
489
- deepline auth status --json
490
- `,
491
- )
492
- .option('--reveal', 'Persist the revealed API key back to the host auth file')
493
- .option('--json', 'Emit JSON output. Also automatic when stdout is piped')
494
- .action(async (options) => {
495
- process.exitCode = await handleStatus([
496
- ...(options.reveal ? ['--reveal'] : []),
497
- ...(options.json ? ['--json'] : []),
498
- ]);
499
- });
500
- }
@@ -1,188 +0,0 @@
1
- import { Command } from 'commander';
2
- import { getAuthedHttpClient, openInBrowser, printJson, shouldEmitJson, writeCsvRowsFile } from '../utils.js';
3
-
4
- type UsageEntry = {
5
- operation?: string | null;
6
- provider?: string | null;
7
- credits?: number | string | null;
8
- billing_mode?: string | null;
9
- status?: string | null;
10
- created_at?: string | null;
11
- batch_count?: number | null;
12
- request_id?: string | null;
13
- };
14
-
15
- function humanize(value: string | null | undefined): string {
16
- return String(value || '')
17
- .split('_')
18
- .filter(Boolean)
19
- .map((token) => token[0]?.toUpperCase() + token.slice(1))
20
- .join(' ') || 'Unknown';
21
- }
22
-
23
- function printRecentUsage(entries: UsageEntry[]): void {
24
- if (entries.length === 0) {
25
- process.stdout.write('Recent activity: none yet\n');
26
- return;
27
- }
28
- process.stdout.write('Recent activity:\n');
29
- for (const entry of entries) {
30
- const op = `${humanize(entry.provider)} ${humanize(entry.operation)}`.trim();
31
- const charge = entry.billing_mode === 'no_bill' ? 'free' : `${entry.credits ?? 0} cr`;
32
- const status = entry.status || 'completed';
33
- process.stdout.write(` ${op} | ${charge} | ${status} | ${entry.created_at || 'unknown'}\n`);
34
- }
35
- }
36
-
37
- async function handleBalance(options: { json?: boolean }): Promise<void> {
38
- const { http } = getAuthedHttpClient();
39
- const payload = await http.get<Record<string, unknown>>('/api/v2/billing/balance');
40
- if (shouldEmitJson(options.json)) return printJson(payload);
41
- const status = String(payload.balance_status || '');
42
- if (status === 'no_billing') {
43
- process.stdout.write('Balance: 0 credits\n');
44
- process.stdout.write("Billing: No billing account or payment method set up for this workspace.\n");
45
- return;
46
- }
47
- process.stdout.write(`Balance: ${payload.balance ?? '(unknown)'} credits\n`);
48
- }
49
-
50
- async function handleUsage(options: { limit?: string; offset?: string; json?: boolean }): Promise<void> {
51
- const { http } = getAuthedHttpClient();
52
- const params = new URLSearchParams();
53
- if (options.limit) params.set('recent_limit', options.limit);
54
- if (options.offset) params.set('recent_offset', options.offset);
55
- const suffix = params.size > 0 ? `?${params.toString()}` : '';
56
- const payload = await http.get<Record<string, unknown>>(`/api/v2/billing/usage${suffix}`);
57
- if (shouldEmitJson(options.json)) return printJson(payload);
58
- const usage = (payload.usage ?? {}) as Record<string, unknown>;
59
- const quota = (payload.quota ?? {}) as Record<string, unknown>;
60
- const recent = (payload.recent ?? {}) as Record<string, unknown>;
61
- process.stdout.write(`Balance: ${payload.balance ?? '(unknown)'}\n`);
62
- process.stdout.write(`Last 30 days spent: ${usage.month_spent_credits ?? '(unknown)'}\n`);
63
- process.stdout.write(
64
- `Monthly limit: ${quota.enabled ? quota.monthly_credits_limit ?? '(unknown)' : 'off'}\n`,
65
- );
66
- printRecentUsage(Array.isArray(recent.entries) ? (recent.entries as UsageEntry[]) : []);
67
- }
68
-
69
- async function handleLimit(options: { json?: boolean }): Promise<void> {
70
- const { http } = getAuthedHttpClient();
71
- const payload = await http.get<Record<string, unknown>>('/api/v2/billing/limit');
72
- if (shouldEmitJson(options.json)) return printJson(payload);
73
- if (payload.enabled) {
74
- process.stdout.write(`Monthly limit: ${payload.monthly_credits_limit ?? '(unknown)'}\n`);
75
- process.stdout.write(`Remaining before cap: ${payload.remaining_credits ?? '(unknown)'}\n`);
76
- return;
77
- }
78
- process.stdout.write('Monthly limit: off\n');
79
- }
80
-
81
- async function handleSetLimit(credits: string, options: { json?: boolean }): Promise<void> {
82
- const { http } = getAuthedHttpClient();
83
- const payload = await http.request('/api/v2/billing/limit', {
84
- method: 'PUT',
85
- body: { monthly_credits_limit: Number.parseInt(credits, 10) },
86
- });
87
- if (shouldEmitJson(options.json)) return printJson(payload);
88
- process.stdout.write(`Monthly billing limit set to ${credits} credits.\n`);
89
- }
90
-
91
- async function handleLimitOff(options: { json?: boolean }): Promise<void> {
92
- const { http } = getAuthedHttpClient();
93
- const payload = await http.request('/api/v2/billing/limit', { method: 'DELETE' });
94
- if (shouldEmitJson(options.json)) return printJson(payload);
95
- process.stdout.write('Monthly billing limit is now off.\n');
96
- }
97
-
98
- async function handleHistory(
99
- options: { time: '1d' | '1w' | '1m' | '1y'; json?: boolean },
100
- ): Promise<void> {
101
- const { http } = getAuthedHttpClient();
102
- const windows: Record<string, number> = { '1d': 86400, '1w': 604800, '1m': 2592000, '1y': 31536000 };
103
- const sinceAt = Math.max(0, Math.floor(Date.now() / 1000) - windows[options.time]) * 1000;
104
- const payload = await http.get<Record<string, unknown>>(`/api/v2/billing/ledger?since_at=${sinceAt}&limit=5000`);
105
- const entries = Array.isArray(payload.entries) ? payload.entries as Array<Record<string, unknown>> : [];
106
- const rows = entries.map((entry) => {
107
- const metadata = (entry.metadata ?? {}) as Record<string, unknown>;
108
- return {
109
- created_at: entry.created_at ?? '',
110
- delta_credits: entry.delta ?? '',
111
- reason: entry.reason ?? '',
112
- provider: metadata.provider ?? '',
113
- operation: metadata.operation ?? '',
114
- };
115
- });
116
- const outputPath = await writeCsvRowsFile(`billing-history-${options.time}`, rows);
117
- if (shouldEmitJson(options.json)) {
118
- return printJson({ output_path: outputPath, row_count: rows.length, time_window: options.time });
119
- }
120
- process.stdout.write(`Billing history written to ${outputPath}\n`);
121
- process.stdout.write(`${rows.length} row(s) exported.\n`);
122
- }
123
-
124
- async function handleCheckout(options: {
125
- tier?: string;
126
- credits?: string;
127
- discountCode?: string;
128
- noOpen?: boolean;
129
- json?: boolean;
130
- }): Promise<void> {
131
- const { http } = getAuthedHttpClient();
132
- const payload = await http.post<Record<string, unknown>>('/api/v2/billing/checkout', {
133
- ...(options.tier ? { tierId: options.tier } : {}),
134
- ...(options.credits ? { credits: Number.parseInt(options.credits, 10) } : {}),
135
- ...(options.discountCode ? { discountCode: options.discountCode } : {}),
136
- });
137
- if (shouldEmitJson(options.json)) return printJson(payload);
138
- const url = String(payload.url || payload.checkout_url || '');
139
- if (!options.noOpen && url) openInBrowser(url);
140
- process.stdout.write(`${url || 'Checkout session created.'}\n`);
141
- }
142
-
143
- async function handleRedeemCode(code: string, options: { noOpen?: boolean; json?: boolean }): Promise<void> {
144
- const { http } = getAuthedHttpClient();
145
- const payload = await http.post<Record<string, unknown>>('/api/v2/billing/checkout/verify', { code });
146
- if (shouldEmitJson(options.json)) return printJson(payload);
147
- const url = String(payload.url || '');
148
- if (!options.noOpen && url) openInBrowser(url);
149
- process.stdout.write(`${url || 'Code redeemed.'}\n`);
150
- }
151
-
152
- export function registerBillingCommands(program: Command): void {
153
- const billing = program.command('billing').description('Inspect balance, usage, limits, and checkout flows.');
154
-
155
- billing.command('balance').description('Show current billing balance.').option('--json', 'Emit JSON output').action(handleBalance);
156
- billing
157
- .command('usage')
158
- .description('Show current usage plus recent calls.')
159
- .option('--limit <n>', 'Recent-call page size')
160
- .option('--offset <n>', 'Recent-call offset')
161
- .option('--json', 'Emit JSON output')
162
- .action(handleUsage);
163
- billing.command('limit').description('Show configured monthly limit state.').option('--json', 'Emit JSON output').action(handleLimit);
164
- billing.command('set-limit').description('Set monthly credit cap.').argument('<credits>', 'Monthly credits limit').option('--json', 'Emit JSON output').action(handleSetLimit);
165
- billing.command('off').description('Disable monthly credit cap.').option('--json', 'Emit JSON output').action(handleLimitOff);
166
- billing
167
- .command('history')
168
- .description('Export billing ledger history to CSV.')
169
- .requiredOption('--time <window>', 'Rolling time window: 1d, 1w, 1m, or 1y')
170
- .option('--json', 'Emit JSON output')
171
- .action(handleHistory);
172
- billing
173
- .command('checkout')
174
- .description('Create a checkout session and optionally open it in your browser.')
175
- .option('--tier <tierId>', 'Named pricing tier')
176
- .option('--credits <credits>', 'Custom credit amount')
177
- .option('--discount-code <code>', 'Apply a discount code')
178
- .option('--no-open', 'Print the checkout URL without opening a browser')
179
- .option('--json', 'Emit JSON output')
180
- .action(handleCheckout);
181
- billing
182
- .command('redeem-code')
183
- .description('Redeem a billing code.')
184
- .requiredOption('--code <code>', 'Code to redeem')
185
- .option('--no-open', 'Do not open a browser')
186
- .option('--json', 'Emit JSON output')
187
- .action(({ code, ...options }) => handleRedeemCode(code, options));
188
- }