@bussolabs/closeyourit-cli 0.2.0 → 0.3.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/dist/base.js CHANGED
@@ -101,6 +101,13 @@ class BaseCommand extends core_1.Command {
101
101
  }
102
102
  return this.exit(1);
103
103
  }
104
+ // Oclif's default JSON formatter serializes the complete CommandError. That object references
105
+ // the command instance and therefore cfg/api.config, including the bearer token. Never pass an
106
+ // unknown error to that formatter in JSON mode: emit a deliberately small allow-listed envelope.
107
+ if (this.jsonEnabled()) {
108
+ this.logJson({ error: { code: error_codes_1.ErrorCodes.System.unexpected, message: 'Command failed' } });
109
+ return this.exit(1);
110
+ }
104
111
  return super.catch(error);
105
112
  }
106
113
  }
@@ -0,0 +1,9 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class PersonalDelete extends BaseCommand {
3
+ static args: {
4
+ name: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ run(): Promise<unknown>;
9
+ }
@@ -0,0 +1,21 @@
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 PersonalDelete extends base_1.BaseCommand {
6
+ static args = {
7
+ name: core_1.Args.string({ description: 'Secret name', required: true }),
8
+ };
9
+ static description = 'Delete a personal secret from the current organization';
10
+ static examples = ['<%= config.bin %> personal delete API_KEY'];
11
+ async run() {
12
+ const { args } = await this.parse(PersonalDelete);
13
+ const name = args.name.toUpperCase();
14
+ await this.api.delete(`/cli/v1/personal_secrets/${encodeURIComponent(name)}`);
15
+ if (!this.jsonEnabled()) {
16
+ this.log(`Personal secret ${name} deleted.`);
17
+ }
18
+ return { deleted: name };
19
+ }
20
+ }
21
+ exports.default = PersonalDelete;
@@ -0,0 +1,10 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class PersonalDownload extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ format: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
7
+ out: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ };
9
+ run(): Promise<unknown>;
10
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const promises_1 = require("node:fs/promises");
4
+ const core_1 = require("@oclif/core");
5
+ const base_1 = require("../../base");
6
+ class PersonalDownload extends base_1.BaseCommand {
7
+ static description = 'Download all your personal secrets as .env or JSON (to a file or stdout). Values with newlines: use --format json. Prefer `cyi personal run` to avoid writing plaintext to disk.';
8
+ static examples = [
9
+ '<%= config.bin %> personal download --out .env',
10
+ '<%= config.bin %> personal download --format json',
11
+ ];
12
+ static flags = {
13
+ format: core_1.Flags.string({ default: 'env', description: 'Output format', options: ['env', 'json'] }),
14
+ out: core_1.Flags.string({ description: 'Write to this file instead of stdout' }),
15
+ };
16
+ async run() {
17
+ const { flags } = await this.parse(PersonalDownload);
18
+ const res = await this.api.get('/cli/v1/personal_secrets/bundle');
19
+ const map = res.data ?? {};
20
+ const body = flags.format === 'json'
21
+ ? JSON.stringify(map, null, 2)
22
+ : Object.entries(map)
23
+ .map(([key, value]) => `${key}=${value}`)
24
+ .join('\n');
25
+ if (flags.out) {
26
+ await (0, promises_1.writeFile)(flags.out, `${body}\n`);
27
+ if (!this.jsonEnabled()) {
28
+ this.log(`Wrote ${Object.keys(map).length} personal secrets → ${flags.out}`);
29
+ this.warn('The file contains plaintext secrets. Prefer `cyi personal run` to inject them without touching disk.');
30
+ }
31
+ }
32
+ else if (!this.jsonEnabled()) {
33
+ this.log(body);
34
+ }
35
+ return { count: Object.keys(map).length, format: flags.format, out: flags.out ?? null };
36
+ }
37
+ }
38
+ exports.default = PersonalDownload;
@@ -0,0 +1,9 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class PersonalGet extends BaseCommand {
3
+ static args: {
4
+ name: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ run(): Promise<unknown>;
9
+ }
@@ -0,0 +1,25 @@
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 PersonalGet extends base_1.BaseCommand {
6
+ static args = {
7
+ name: core_1.Args.string({ description: 'Secret name', required: true }),
8
+ };
9
+ static description = 'Print the value of a single personal secret (pipeable).';
10
+ static examples = ['<%= config.bin %> personal get OPENAI_API_KEY'];
11
+ async run() {
12
+ const { args } = await this.parse(PersonalGet);
13
+ const res = await this.api.get('/cli/v1/personal_secrets/bundle');
14
+ const map = res.data ?? {};
15
+ const key = args.name.toUpperCase();
16
+ if (!(key in map)) {
17
+ this.error(`Personal secret not found: ${key}`, { exit: 1 });
18
+ }
19
+ if (!this.jsonEnabled()) {
20
+ this.log(map[key]);
21
+ }
22
+ return { name: key, value: map[key] };
23
+ }
24
+ }
25
+ exports.default = PersonalGet;
@@ -0,0 +1,10 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class PersonalImport extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'from-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
7
+ format: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ };
9
+ run(): Promise<unknown>;
10
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const promises_1 = require("node:fs/promises");
4
+ const core_1 = require("@oclif/core");
5
+ const base_1 = require("../../base");
6
+ const stdin_1 = require("../../lib/stdin");
7
+ // Reuse the exact env/JSON parsers from the project-secrets twin so the two vaults never diverge.
8
+ const import_1 = require("../secrets/import");
9
+ class PersonalImport extends base_1.BaseCommand {
10
+ static description = 'Import many personal secrets at once (all-or-nothing) from a .env or JSON file';
11
+ static examples = [
12
+ '<%= config.bin %> personal import --from-file .env',
13
+ '<%= config.bin %> personal import --format json --from-file secrets.json',
14
+ 'doppler secrets download --no-file --format json | <%= config.bin %> personal import --format json',
15
+ ];
16
+ static flags = {
17
+ 'from-file': core_1.Flags.string({ description: 'Path to a .env or JSON file (default: read from stdin)' }),
18
+ format: core_1.Flags.string({ default: 'env', description: 'Input format', options: ['env', 'json'] }),
19
+ };
20
+ async run() {
21
+ const { flags } = await this.parse(PersonalImport);
22
+ let text;
23
+ if (flags['from-file']) {
24
+ text = await (0, promises_1.readFile)(flags['from-file'], 'utf8');
25
+ }
26
+ else {
27
+ if (process.stdin.isTTY) {
28
+ this.error('Provide --from-file or pipe the input via stdin (e.g. `doppler secrets download --format json | cyi personal import --format json`)', { exit: 1 });
29
+ }
30
+ text = await (0, stdin_1.readStream)();
31
+ }
32
+ const variables = flags.format === 'json' ? (0, import_1.parseJson)(text) : (0, import_1.parseEnv)(text);
33
+ if (variables.length === 0) {
34
+ this.error('No variables found in the input', { exit: 1 });
35
+ }
36
+ const res = await this.api.post('/cli/v1/personal_secrets/import', { variables });
37
+ if (!this.jsonEnabled()) {
38
+ this.log(`Imported ${res.data?.imported ?? variables.length} personal secrets.`);
39
+ }
40
+ return res;
41
+ }
42
+ }
43
+ exports.default = PersonalImport;
@@ -0,0 +1,6 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class PersonalList extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<unknown>;
6
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const base_1 = require("../../base");
4
+ const output_1 = require("../../lib/output");
5
+ class PersonalList extends base_1.BaseCommand {
6
+ static description = 'List your personal secret names for the current organization. Never prints values.';
7
+ static examples = ['<%= config.bin %> personal list', '<%= config.bin %> personal list --json'];
8
+ async run() {
9
+ await this.parse(PersonalList);
10
+ const res = await this.api.get('/cli/v1/personal_secrets');
11
+ if (!this.jsonEnabled()) {
12
+ const rows = (res.data ?? []).map((secret) => [String(secret.name ?? ''), String(secret.description ?? '')]);
13
+ this.log((0, output_1.renderTable)(['NAME', 'DESCRIPTION'], rows));
14
+ }
15
+ return res;
16
+ }
17
+ }
18
+ exports.default = PersonalList;
@@ -0,0 +1,8 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class PersonalRun extends BaseCommand {
3
+ static strict: boolean;
4
+ static enableJsonFlag: boolean;
5
+ static description: string;
6
+ static examples: string[];
7
+ run(): Promise<unknown>;
8
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const base_1 = require("../../base");
4
+ const subprocess_1 = require("../../lib/subprocess");
5
+ class PersonalRun extends base_1.BaseCommand {
6
+ // Everything after `--` is the child command + its args (not parsed as flags).
7
+ static strict = false;
8
+ // The output belongs to the child process, not to us.
9
+ static enableJsonFlag = false;
10
+ static description = 'Run a command with your personal secrets injected as env vars (like `doppler run`). Secrets never touch disk.';
11
+ static examples = [
12
+ '<%= config.bin %> personal run -- bundle exec rails server',
13
+ '<%= config.bin %> personal run -- printenv OPENAI_API_KEY',
14
+ ];
15
+ async run() {
16
+ const { argv } = await this.parse(PersonalRun);
17
+ const parts = argv;
18
+ if (parts.length === 0) {
19
+ this.error('Provide a command to run after `--`, e.g. `cyi personal run -- printenv`', { exit: 1 });
20
+ }
21
+ const { data } = await this.api.get('/cli/v1/personal_secrets/bundle');
22
+ const [command, ...commandArgs] = parts;
23
+ const code = await (0, subprocess_1.runCommand)(command, commandArgs, { ...process.env, ...(data ?? {}) });
24
+ // Propagate the child's exit code (0 on success) so `cyi personal run … && next` behaves like the child.
25
+ this.exit(code);
26
+ }
27
+ }
28
+ exports.default = PersonalRun;
@@ -0,0 +1,13 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class PersonalSet extends BaseCommand {
3
+ static args: {
4
+ assignment: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ value: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ description: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<unknown>;
13
+ }
@@ -0,0 +1,37 @@
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 PersonalSet extends base_1.BaseCommand {
6
+ static args = {
7
+ assignment: core_1.Args.string({ description: 'NAME=VALUE, or just NAME with --value', required: true }),
8
+ };
9
+ static description = 'Set (create or update) a personal secret for the current organization';
10
+ static examples = [
11
+ '<%= config.bin %> personal set OPENAI_API_KEY=sk-live-...',
12
+ '<%= config.bin %> personal set API_KEY --value s3cr3t --description "Stripe key"',
13
+ ];
14
+ static flags = {
15
+ value: core_1.Flags.string({ description: 'Secret value (use when the arg is just NAME)' }),
16
+ description: core_1.Flags.string({ description: 'Optional description' }),
17
+ };
18
+ async run() {
19
+ const { args, flags } = await this.parse(PersonalSet);
20
+ const eq = args.assignment.indexOf('=');
21
+ const name = eq === -1 ? args.assignment : args.assignment.slice(0, eq);
22
+ const value = eq === -1 ? flags.value : args.assignment.slice(eq + 1);
23
+ if (value === undefined) {
24
+ this.error('Provide a value: use NAME=VALUE or pass --value', { exit: 1 });
25
+ }
26
+ const res = await this.api.post('/cli/v1/personal_secrets', {
27
+ name,
28
+ value,
29
+ description: flags.description,
30
+ });
31
+ if (!this.jsonEnabled()) {
32
+ this.log(`Personal secret ${String(name).toUpperCase()} saved.`);
33
+ }
34
+ return res;
35
+ }
36
+ }
37
+ exports.default = PersonalSet;
@@ -2,11 +2,30 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const base_1 = require("../base");
4
4
  class Whoami extends base_1.BaseCommand {
5
- static description = 'Show the authenticated account, organization, token and permissions';
5
+ static description = 'Show the authenticated account, organization, token prefix and permissions';
6
6
  static examples = ['<%= config.bin %> whoami', '<%= config.bin %> whoami --json'];
7
7
  async run() {
8
8
  const res = await this.api.get('/cli/v1/whoami');
9
- const who = res.data;
9
+ // Rebuild from an allow-list. If a future backend accidentally includes a token secret or other
10
+ // authentication internals, `--json` must not proxy them to stdout.
11
+ const who = {
12
+ account: res.data.account && {
13
+ id: res.data.account.id,
14
+ name: res.data.account.name,
15
+ email: res.data.account.email,
16
+ },
17
+ organization: res.data.organization && {
18
+ id: res.data.organization.id,
19
+ name: res.data.organization.name,
20
+ slug: res.data.organization.slug,
21
+ },
22
+ token: res.data.token && {
23
+ id: res.data.token.id,
24
+ name: res.data.token.name,
25
+ prefix: res.data.token.prefix,
26
+ },
27
+ permissions: res.data.permissions,
28
+ };
10
29
  if (!this.jsonEnabled()) {
11
30
  this.log(`Account: ${who.account?.name ?? '-'} <${who.account?.email ?? '-'}>`);
12
31
  this.log(`Organization: ${who.organization?.name ?? '-'} (${who.organization?.slug ?? '-'})`);