@bussolabs/closeyourit-cli 0.0.2

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 (99) hide show
  1. package/README.md +99 -0
  2. package/bin/run.js +2 -0
  3. package/dist/base.d.ts +34 -0
  4. package/dist/base.js +85 -0
  5. package/dist/commands/errors/list.d.ts +11 -0
  6. package/dist/commands/errors/list.js +36 -0
  7. package/dist/commands/errors/mute.d.ts +12 -0
  8. package/dist/commands/errors/mute.js +22 -0
  9. package/dist/commands/errors/reopen.d.ts +12 -0
  10. package/dist/commands/errors/reopen.js +22 -0
  11. package/dist/commands/errors/resolve.d.ts +12 -0
  12. package/dist/commands/errors/resolve.js +22 -0
  13. package/dist/commands/errors/show.d.ts +12 -0
  14. package/dist/commands/errors/show.js +23 -0
  15. package/dist/commands/errors/unmute.d.ts +12 -0
  16. package/dist/commands/errors/unmute.js +22 -0
  17. package/dist/commands/groups/create.d.ts +12 -0
  18. package/dist/commands/groups/create.js +31 -0
  19. package/dist/commands/groups/list.d.ts +9 -0
  20. package/dist/commands/groups/list.js +19 -0
  21. package/dist/commands/invitations/create.d.ts +12 -0
  22. package/dist/commands/invitations/create.js +38 -0
  23. package/dist/commands/invitations/list.d.ts +9 -0
  24. package/dist/commands/invitations/list.js +24 -0
  25. package/dist/commands/invitations/revoke.d.ts +9 -0
  26. package/dist/commands/invitations/revoke.js +20 -0
  27. package/dist/commands/login.d.ts +9 -0
  28. package/dist/commands/login.js +63 -0
  29. package/dist/commands/logout.d.ts +6 -0
  30. package/dist/commands/logout.js +17 -0
  31. package/dist/commands/members/list.d.ts +9 -0
  32. package/dist/commands/members/list.js +24 -0
  33. package/dist/commands/metrics/list.d.ts +11 -0
  34. package/dist/commands/metrics/list.js +36 -0
  35. package/dist/commands/metrics/show.d.ts +12 -0
  36. package/dist/commands/metrics/show.js +23 -0
  37. package/dist/commands/projects/create.d.ts +15 -0
  38. package/dist/commands/projects/create.js +49 -0
  39. package/dist/commands/projects/list.d.ts +9 -0
  40. package/dist/commands/projects/list.js +19 -0
  41. package/dist/commands/projects/set-environments.d.ts +11 -0
  42. package/dist/commands/projects/set-environments.js +42 -0
  43. package/dist/commands/projects/show.d.ts +9 -0
  44. package/dist/commands/projects/show.js +22 -0
  45. package/dist/commands/roles/create.d.ts +13 -0
  46. package/dist/commands/roles/create.js +36 -0
  47. package/dist/commands/roles/delete.d.ts +9 -0
  48. package/dist/commands/roles/delete.js +21 -0
  49. package/dist/commands/roles/list.d.ts +9 -0
  50. package/dist/commands/roles/list.js +23 -0
  51. package/dist/commands/roles/show.d.ts +9 -0
  52. package/dist/commands/roles/show.js +25 -0
  53. package/dist/commands/roles/update.d.ts +14 -0
  54. package/dist/commands/roles/update.js +44 -0
  55. package/dist/commands/teams/add-member.d.ts +10 -0
  56. package/dist/commands/teams/add-member.js +26 -0
  57. package/dist/commands/teams/create.d.ts +16 -0
  58. package/dist/commands/teams/create.js +50 -0
  59. package/dist/commands/teams/delete.d.ts +9 -0
  60. package/dist/commands/teams/delete.js +21 -0
  61. package/dist/commands/teams/list.d.ts +9 -0
  62. package/dist/commands/teams/list.js +27 -0
  63. package/dist/commands/teams/remove-member.d.ts +10 -0
  64. package/dist/commands/teams/remove-member.js +26 -0
  65. package/dist/commands/teams/show.d.ts +9 -0
  66. package/dist/commands/teams/show.js +27 -0
  67. package/dist/commands/teams/update.d.ts +17 -0
  68. package/dist/commands/teams/update.js +54 -0
  69. package/dist/commands/tickets/create.d.ts +18 -0
  70. package/dist/commands/tickets/create.js +51 -0
  71. package/dist/commands/tickets/list.d.ts +10 -0
  72. package/dist/commands/tickets/list.js +24 -0
  73. package/dist/commands/tickets/show.d.ts +12 -0
  74. package/dist/commands/tickets/show.js +23 -0
  75. package/dist/commands/tokens/create.d.ts +11 -0
  76. package/dist/commands/tokens/create.js +34 -0
  77. package/dist/commands/tokens/list.d.ts +9 -0
  78. package/dist/commands/tokens/list.js +25 -0
  79. package/dist/commands/tokens/revoke.d.ts +12 -0
  80. package/dist/commands/tokens/revoke.js +22 -0
  81. package/dist/commands/tokens/rotate.d.ts +12 -0
  82. package/dist/commands/tokens/rotate.js +28 -0
  83. package/dist/commands/whoami.d.ts +6 -0
  84. package/dist/commands/whoami.js +19 -0
  85. package/dist/errors/error-codes.d.ts +35 -0
  86. package/dist/errors/error-codes.js +40 -0
  87. package/dist/lib/api.d.ts +28 -0
  88. package/dist/lib/api.js +81 -0
  89. package/dist/lib/browser.d.ts +2 -0
  90. package/dist/lib/browser.js +32 -0
  91. package/dist/lib/config.d.ts +20 -0
  92. package/dist/lib/config.js +93 -0
  93. package/dist/lib/device-flow.d.ts +28 -0
  94. package/dist/lib/device-flow.js +62 -0
  95. package/dist/lib/output.d.ts +4 -0
  96. package/dist/lib/output.js +21 -0
  97. package/oclif.manifest.json +1954 -0
  98. package/opencli.json +229 -0
  99. package/package.json +34 -0
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_1 = require("../../base");
5
+ class TokensRevoke extends base_1.BaseCommand {
6
+ static description = 'Revoke an ingest token';
7
+ static examples = ['<%= config.bin %> tokens revoke <token-id> --project acme-api'];
8
+ static args = {
9
+ id: core_1.Args.string({ description: 'Token id', required: true }),
10
+ };
11
+ static flags = { ...base_1.projectFlag };
12
+ async run() {
13
+ const { args, flags } = await this.parse(TokensRevoke);
14
+ const projectId = await this.resolveProjectId(flags.project);
15
+ const res = await this.api.delete(`/cli/v1/projects/${projectId}/tokens/${args.id}`);
16
+ if (!this.jsonEnabled()) {
17
+ this.log(`Token ${args.id} revoked.`);
18
+ }
19
+ return res;
20
+ }
21
+ }
22
+ exports.default = TokensRevoke;
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class TokensRotate extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ };
11
+ run(): Promise<unknown>;
12
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_1 = require("../../base");
5
+ class TokensRotate extends base_1.BaseCommand {
6
+ static description = 'Rotate an ingest token (the new secret is shown only once)';
7
+ static examples = ['<%= config.bin %> tokens rotate <token-id> --project acme-api'];
8
+ static args = {
9
+ id: core_1.Args.string({ description: 'Token id', required: true }),
10
+ };
11
+ static flags = { ...base_1.projectFlag };
12
+ async run() {
13
+ const { args, flags } = await this.parse(TokensRotate);
14
+ const projectId = await this.resolveProjectId(flags.project);
15
+ const res = await this.api.put(`/cli/v1/projects/${projectId}/tokens/${args.id}/rotation`);
16
+ const token = res.data ?? {};
17
+ if (!this.jsonEnabled()) {
18
+ this.log('Token rotated. Store the new credentials now — they are shown only once:');
19
+ this.log('');
20
+ this.log(` secret: ${token.secret ?? '-'}`);
21
+ this.log(` dsn: ${token.dsn ?? '-'}`);
22
+ this.log('');
23
+ this.warn('The previous secret is now invalid and the new one cannot be retrieved again.');
24
+ }
25
+ return res;
26
+ }
27
+ }
28
+ exports.default = TokensRotate;
@@ -0,0 +1,6 @@
1
+ import { BaseCommand } from '../base';
2
+ export default class Whoami extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<unknown>;
6
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const base_1 = require("../base");
4
+ class Whoami extends base_1.BaseCommand {
5
+ static description = 'Show the authenticated account, organization, token and permissions';
6
+ static examples = ['<%= config.bin %> whoami', '<%= config.bin %> whoami --json'];
7
+ async run() {
8
+ const res = await this.api.get('/cli/v1/whoami');
9
+ const who = res.data;
10
+ if (!this.jsonEnabled()) {
11
+ this.log(`Account: ${who.account?.name ?? '-'} <${who.account?.email ?? '-'}>`);
12
+ this.log(`Organization: ${who.organization?.name ?? '-'} (${who.organization?.slug ?? '-'})`);
13
+ this.log(`Token: ${who.token?.prefix ?? '-'}`);
14
+ this.log(`Permissions: ${(who.permissions ?? []).join(', ') || '-'}`);
15
+ }
16
+ return who;
17
+ }
18
+ }
19
+ exports.default = Whoami;
@@ -0,0 +1,35 @@
1
+ export declare const ErrorCodes: {
2
+ readonly Auth: {
3
+ readonly notLoggedIn: "C401-AUT-001";
4
+ };
5
+ readonly Config: {
6
+ readonly notFound: "C000-CFG-001";
7
+ readonly corrupted: "C000-CFG-002";
8
+ };
9
+ readonly Network: {
10
+ readonly requestFailed: "C000-NET-001";
11
+ readonly parseFailed: "C000-NET-003";
12
+ };
13
+ readonly Project: {
14
+ readonly notFound: "C404-PRJ-001";
15
+ };
16
+ readonly Role: {
17
+ readonly notFound: "C404-ROL-001";
18
+ };
19
+ readonly Group: {
20
+ readonly notFound: "C404-GRP-001";
21
+ };
22
+ readonly Team: {
23
+ readonly notFound: "C404-TEM-001";
24
+ };
25
+ readonly Member: {
26
+ readonly notFound: "C404-MBR-001";
27
+ };
28
+ readonly Device: {
29
+ readonly expired: "C408-DEV-001";
30
+ readonly failed: "C400-DEV-002";
31
+ };
32
+ readonly System: {
33
+ readonly unexpected: "C500-SYS-001";
34
+ };
35
+ };
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ErrorCodes = void 0;
4
+ // Local CLI error codes — C{STATUS}-{DOMAIN}-{SEQ}.
5
+ // Backend codes (R*/G*) are propagated as-is from the response envelope; never remap them here.
6
+ exports.ErrorCodes = {
7
+ Auth: {
8
+ notLoggedIn: 'C401-AUT-001',
9
+ },
10
+ Config: {
11
+ notFound: 'C000-CFG-001',
12
+ corrupted: 'C000-CFG-002',
13
+ },
14
+ Network: {
15
+ requestFailed: 'C000-NET-001',
16
+ parseFailed: 'C000-NET-003',
17
+ },
18
+ Project: {
19
+ notFound: 'C404-PRJ-001',
20
+ },
21
+ Role: {
22
+ notFound: 'C404-ROL-001',
23
+ },
24
+ Group: {
25
+ notFound: 'C404-GRP-001',
26
+ },
27
+ Team: {
28
+ notFound: 'C404-TEM-001',
29
+ },
30
+ Member: {
31
+ notFound: 'C404-MBR-001',
32
+ },
33
+ Device: {
34
+ expired: 'C408-DEV-001',
35
+ failed: 'C400-DEV-002',
36
+ },
37
+ System: {
38
+ unexpected: 'C500-SYS-001',
39
+ },
40
+ };
@@ -0,0 +1,28 @@
1
+ import type { CliConfig } from './config';
2
+ /** Error carrying the HTTP status and the structured code from the backend envelope (R/G prefix) or a local C-prefix fallback. */
3
+ export declare class ApiRequestError extends Error {
4
+ readonly status: number;
5
+ readonly code: string;
6
+ readonly details?: unknown | undefined;
7
+ constructor(status: number, code: string, message: string, details?: unknown | undefined);
8
+ }
9
+ export interface RequestOptions {
10
+ body?: unknown;
11
+ /** Defaults to true; set false for unauthenticated endpoints. */
12
+ auth?: boolean;
13
+ }
14
+ export interface Envelope<T = unknown> {
15
+ data: T;
16
+ meta?: Record<string, unknown>;
17
+ }
18
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
19
+ export declare class CliApi {
20
+ private readonly config;
21
+ constructor(config: CliConfig);
22
+ request<T = unknown>(method: HttpMethod, path: string, opts?: RequestOptions): Promise<Envelope<T>>;
23
+ get<T = unknown>(path: string, opts?: RequestOptions): Promise<Envelope<T>>;
24
+ post<T = unknown>(path: string, body?: unknown, opts?: RequestOptions): Promise<Envelope<T>>;
25
+ put<T = unknown>(path: string, body?: unknown, opts?: RequestOptions): Promise<Envelope<T>>;
26
+ delete<T = unknown>(path: string, opts?: RequestOptions): Promise<Envelope<T>>;
27
+ }
28
+ export {};
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CliApi = exports.ApiRequestError = void 0;
4
+ const error_codes_1 = require("../errors/error-codes");
5
+ /** Error carrying the HTTP status and the structured code from the backend envelope (R/G prefix) or a local C-prefix fallback. */
6
+ class ApiRequestError extends Error {
7
+ status;
8
+ code;
9
+ details;
10
+ constructor(status, code, message, details) {
11
+ super(message);
12
+ this.status = status;
13
+ this.code = code;
14
+ this.details = details;
15
+ this.name = 'ApiRequestError';
16
+ }
17
+ }
18
+ exports.ApiRequestError = ApiRequestError;
19
+ class CliApi {
20
+ config;
21
+ constructor(config) {
22
+ this.config = config;
23
+ }
24
+ async request(method, path, opts = {}) {
25
+ const auth = opts.auth !== false;
26
+ const headers = {
27
+ Accept: 'application/json',
28
+ 'Content-Type': 'application/json',
29
+ };
30
+ if (auth) {
31
+ if (!this.config.token) {
32
+ throw new ApiRequestError(401, error_codes_1.ErrorCodes.Auth.notLoggedIn, 'Not logged in. Run "closeyourit login".');
33
+ }
34
+ headers.Authorization = `Bearer ${this.config.token}`;
35
+ }
36
+ const url = this.config.apiUrl.replace(/\/+$/, '') + path;
37
+ let res;
38
+ try {
39
+ res = await fetch(url, {
40
+ method,
41
+ headers,
42
+ body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
43
+ });
44
+ }
45
+ catch (err) {
46
+ throw new ApiRequestError(0, error_codes_1.ErrorCodes.Network.requestFailed, `Request to ${url} failed: ${err.message}`);
47
+ }
48
+ const text = await res.text();
49
+ let json = {};
50
+ if (text.length > 0) {
51
+ try {
52
+ json = JSON.parse(text);
53
+ }
54
+ catch {
55
+ if (!res.ok) {
56
+ throw new ApiRequestError(res.status, error_codes_1.ErrorCodes.Network.requestFailed, `Request failed (${res.status})`);
57
+ }
58
+ throw new ApiRequestError(res.status, error_codes_1.ErrorCodes.Network.parseFailed, 'Failed to parse server response');
59
+ }
60
+ }
61
+ if (!res.ok) {
62
+ const envelope = (json ?? {});
63
+ const error = envelope.error ?? {};
64
+ throw new ApiRequestError(res.status, error.code ?? `C${res.status}-API-000`, error.message ?? res.statusText ?? `Request failed (${res.status})`, error.details);
65
+ }
66
+ return (json ?? {});
67
+ }
68
+ get(path, opts) {
69
+ return this.request('GET', path, opts);
70
+ }
71
+ post(path, body, opts) {
72
+ return this.request('POST', path, { ...opts, body });
73
+ }
74
+ put(path, body, opts) {
75
+ return this.request('PUT', path, { ...opts, body });
76
+ }
77
+ delete(path, opts) {
78
+ return this.request('DELETE', path, opts);
79
+ }
80
+ }
81
+ exports.CliApi = CliApi;
@@ -0,0 +1,2 @@
1
+ /** Best-effort: open a URL in the default browser. Never throws. */
2
+ export declare function openUrl(url: string): void;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.openUrl = openUrl;
4
+ const node_child_process_1 = require("node:child_process");
5
+ /** Best-effort: open a URL in the default browser. Never throws. */
6
+ function openUrl(url) {
7
+ try {
8
+ let command;
9
+ let args;
10
+ switch (process.platform) {
11
+ case 'darwin':
12
+ command = 'open';
13
+ args = [url];
14
+ break;
15
+ case 'win32':
16
+ command = 'cmd';
17
+ args = ['/c', 'start', '', url];
18
+ break;
19
+ default:
20
+ command = 'xdg-open';
21
+ args = [url];
22
+ }
23
+ const child = (0, node_child_process_1.spawn)(command, args, { stdio: 'ignore', detached: true });
24
+ child.on('error', () => {
25
+ // Swallow: opening a browser is a convenience, not a requirement.
26
+ });
27
+ child.unref();
28
+ }
29
+ catch {
30
+ // Never throw from a convenience helper.
31
+ }
32
+ }
@@ -0,0 +1,20 @@
1
+ export interface CliConfig {
2
+ apiUrl: string;
3
+ token?: string;
4
+ account?: {
5
+ id: string;
6
+ name: string;
7
+ email: string;
8
+ };
9
+ organization?: {
10
+ id: string;
11
+ name: string;
12
+ slug: string;
13
+ };
14
+ }
15
+ export declare function configPath(): string;
16
+ export declare function defaultApiUrl(): string;
17
+ export declare function loadConfig(): CliConfig;
18
+ export declare function saveConfig(cfg: CliConfig): void;
19
+ /** Remove token + identity, keep apiUrl. */
20
+ export declare function clearToken(): void;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.configPath = configPath;
37
+ exports.defaultApiUrl = defaultApiUrl;
38
+ exports.loadConfig = loadConfig;
39
+ exports.saveConfig = saveConfig;
40
+ exports.clearToken = clearToken;
41
+ const fs = __importStar(require("node:fs"));
42
+ const os = __importStar(require("node:os"));
43
+ const path = __importStar(require("node:path"));
44
+ const DEFAULT_API_URL = 'https://www.closeyour.it';
45
+ /** Directory holding config.json. Overridable via CLOSEYOURIT_CONFIG_DIR (read at call time). */
46
+ function configDir() {
47
+ const override = process.env.CLOSEYOURIT_CONFIG_DIR;
48
+ if (override && override.trim() !== '')
49
+ return override;
50
+ return path.join(os.homedir(), '.config', 'closeyourit');
51
+ }
52
+ function configPath() {
53
+ return path.join(configDir(), 'config.json');
54
+ }
55
+ function defaultApiUrl() {
56
+ const fromEnv = process.env.CLOSEYOURIT_API_URL;
57
+ return fromEnv && fromEnv.trim() !== '' ? fromEnv : DEFAULT_API_URL;
58
+ }
59
+ function loadConfig() {
60
+ const file = configPath();
61
+ let parsed = {};
62
+ try {
63
+ if (fs.existsSync(file)) {
64
+ parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
65
+ }
66
+ }
67
+ catch {
68
+ // Corrupted/unreadable config falls back to defaults rather than crashing.
69
+ parsed = {};
70
+ }
71
+ return {
72
+ apiUrl: parsed.apiUrl && parsed.apiUrl.trim() !== '' ? parsed.apiUrl : defaultApiUrl(),
73
+ token: parsed.token,
74
+ account: parsed.account,
75
+ organization: parsed.organization,
76
+ };
77
+ }
78
+ function saveConfig(cfg) {
79
+ const dir = configDir();
80
+ fs.mkdirSync(dir, { recursive: true });
81
+ const file = configPath();
82
+ fs.writeFileSync(file, `${JSON.stringify(cfg, null, 2)}\n`, { mode: 0o600 });
83
+ // Ensure perms even if the file pre-existed with a looser mode.
84
+ fs.chmodSync(file, 0o600);
85
+ }
86
+ /** Remove token + identity, keep apiUrl. */
87
+ function clearToken() {
88
+ const cfg = loadConfig();
89
+ delete cfg.token;
90
+ delete cfg.account;
91
+ delete cfg.organization;
92
+ saveConfig(cfg);
93
+ }
@@ -0,0 +1,28 @@
1
+ export interface DeviceAuthorization {
2
+ device_code: string;
3
+ user_code: string;
4
+ verification_uri: string;
5
+ verification_uri_complete: string;
6
+ interval: number;
7
+ expires_in: number;
8
+ }
9
+ export interface DeviceTokenResult {
10
+ access_token: string;
11
+ token: {
12
+ id: string;
13
+ name: string;
14
+ prefix: string;
15
+ };
16
+ }
17
+ export interface PollOptions {
18
+ /** Seconds between polls (from the authorize response). */
19
+ interval: number;
20
+ /** Lifetime of the device_code in seconds. */
21
+ expiresIn: number;
22
+ /** Called on each authorization_pending / slow_down tick. */
23
+ onPending?: () => void;
24
+ /** Injectable for tests; defaults to setTimeout-based sleep. */
25
+ sleep?: (ms: number) => Promise<void>;
26
+ }
27
+ export declare function startDeviceFlow(apiUrl: string, clientName: string): Promise<DeviceAuthorization>;
28
+ export declare function pollDeviceToken(apiUrl: string, deviceCode: string, opts: PollOptions): Promise<DeviceTokenResult>;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ // OAuth 2.0 Device Authorization Grant against the CloseYourIt /cli backend.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.startDeviceFlow = startDeviceFlow;
5
+ exports.pollDeviceToken = pollDeviceToken;
6
+ function defaultSleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+ async function postJson(url, body) {
10
+ const res = await fetch(url, {
11
+ method: 'POST',
12
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
13
+ body: JSON.stringify(body),
14
+ });
15
+ const text = await res.text();
16
+ let json = {};
17
+ if (text.length > 0) {
18
+ try {
19
+ json = JSON.parse(text);
20
+ }
21
+ catch {
22
+ json = {};
23
+ }
24
+ }
25
+ return { ok: res.ok, status: res.status, json };
26
+ }
27
+ function base(apiUrl) {
28
+ return apiUrl.replace(/\/+$/, '');
29
+ }
30
+ async function startDeviceFlow(apiUrl, clientName) {
31
+ const { ok, status, json } = await postJson(`${base(apiUrl)}/cli/device/authorize`, { client_name: clientName });
32
+ if (!ok) {
33
+ throw new Error(json?.error?.message ?? `Device authorization failed (${status})`);
34
+ }
35
+ return json.data;
36
+ }
37
+ async function pollDeviceToken(apiUrl, deviceCode, opts) {
38
+ const sleep = opts.sleep ?? defaultSleep;
39
+ let interval = opts.interval > 0 ? opts.interval : 5;
40
+ const deadline = Date.now() + opts.expiresIn * 1000;
41
+ for (;;) {
42
+ if (Date.now() > deadline) {
43
+ throw new Error('Device authorization expired. Run "closeyourit login" again.');
44
+ }
45
+ await sleep(interval * 1000);
46
+ const { ok, json } = await postJson(`${base(apiUrl)}/cli/device/token`, { device_code: deviceCode });
47
+ if (ok) {
48
+ return json.data;
49
+ }
50
+ const oauthError = json?.error?.details?.oauth_error;
51
+ if (oauthError === 'authorization_pending') {
52
+ opts.onPending?.();
53
+ continue;
54
+ }
55
+ if (oauthError === 'slow_down') {
56
+ interval += 5;
57
+ opts.onPending?.();
58
+ continue;
59
+ }
60
+ throw new Error(json?.error?.message ?? 'Device authorization failed');
61
+ }
62
+ }
@@ -0,0 +1,4 @@
1
+ /** Minimal dependency-free table renderer for human output. */
2
+ export declare function renderTable(headers: string[], rows: string[][]): string;
3
+ /** Pretty-print the scalar fields of a record as `key: value` lines. */
4
+ export declare function renderRecord(obj: Record<string, unknown>): string;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderTable = renderTable;
4
+ exports.renderRecord = renderRecord;
5
+ /** Minimal dependency-free table renderer for human output. */
6
+ function renderTable(headers, rows) {
7
+ if (rows.length === 0) {
8
+ return `${headers.join(' ')}\n(no results)`;
9
+ }
10
+ const widths = headers.map((header, col) => Math.max(header.length, ...rows.map((row) => String(row[col] ?? '').length)));
11
+ const line = (cols) => cols.map((cell, col) => String(cell ?? '').padEnd(widths[col])).join(' ').trimEnd();
12
+ const separator = widths.map((width) => '-'.repeat(width));
13
+ return [line(headers), line(separator), ...rows.map(line)].join('\n');
14
+ }
15
+ /** Pretty-print the scalar fields of a record as `key: value` lines. */
16
+ function renderRecord(obj) {
17
+ const lines = Object.entries(obj)
18
+ .filter(([, value]) => value === null || ['string', 'number', 'boolean'].includes(typeof value))
19
+ .map(([key, value]) => `${key}: ${value === null ? '-' : value}`);
20
+ return lines.length > 0 ? lines.join('\n') : JSON.stringify(obj, null, 2);
21
+ }