linear-cli-agents 0.4.1 → 0.5.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.
@@ -0,0 +1,11 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class IssuesBulkLabel extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ ids: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ 'add-labels': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ 'remove-labels': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ run(): Promise<void>;
11
+ }
@@ -0,0 +1,113 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { getClient } from '../../lib/client.js';
3
+ import { success, print } from '../../lib/output.js';
4
+ import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
5
+ import { resolveIssueId } from '../../lib/issue-utils.js';
6
+ export default class IssuesBulkLabel extends Command {
7
+ static description = 'Add or remove labels from multiple issues';
8
+ static examples = [
9
+ '<%= config.bin %> issues bulk-label --ids ENG-1,ENG-2,ENG-3 --add-labels LABEL_ID1,LABEL_ID2',
10
+ '<%= config.bin %> issues bulk-label --ids ENG-1,ENG-2 --remove-labels LABEL_ID1',
11
+ '<%= config.bin %> issues bulk-label --ids ENG-1,ENG-2,ENG-3 --add-labels LABEL_ID1 --remove-labels LABEL_ID2',
12
+ ];
13
+ static flags = {
14
+ ids: Flags.string({
15
+ description: 'Comma-separated issue IDs or identifiers (e.g., ENG-1,ENG-2,ENG-3)',
16
+ required: true,
17
+ }),
18
+ 'add-labels': Flags.string({
19
+ description: 'Comma-separated label IDs to add',
20
+ }),
21
+ 'remove-labels': Flags.string({
22
+ description: 'Comma-separated label IDs to remove',
23
+ }),
24
+ };
25
+ async run() {
26
+ try {
27
+ const { flags } = await this.parse(IssuesBulkLabel);
28
+ const client = getClient();
29
+ const addLabelIds = flags['add-labels']?.split(',').map((id) => id.trim()) ?? [];
30
+ const removeLabelIds = flags['remove-labels']?.split(',').map((id) => id.trim()) ?? [];
31
+ if (addLabelIds.length === 0 && removeLabelIds.length === 0) {
32
+ throw new CliError(ErrorCodes.INVALID_INPUT, 'No label operations specified. Use --add-labels and/or --remove-labels');
33
+ }
34
+ // Parse issue identifiers
35
+ const identifiers = flags.ids.split(',').map((id) => id.trim());
36
+ if (identifiers.length === 0) {
37
+ throw new CliError(ErrorCodes.INVALID_INPUT, 'No issue IDs provided');
38
+ }
39
+ // Process all label operations
40
+ const results = [];
41
+ // Process each issue
42
+ await Promise.all(identifiers.map(async (identifier) => {
43
+ try {
44
+ const issueId = await resolveIssueId(client, identifier);
45
+ const issue = await client.issue(issueId);
46
+ if (!issue) {
47
+ results.push({
48
+ identifier,
49
+ id: issueId,
50
+ success: false,
51
+ error: 'Issue not found',
52
+ });
53
+ return;
54
+ }
55
+ // Get existing labels
56
+ const existingLabels = await issue.labels();
57
+ const existingLabelIds = existingLabels.nodes.map((l) => l.id);
58
+ // Calculate new label set
59
+ const labelsToAdd = addLabelIds.filter((id) => !existingLabelIds.includes(id));
60
+ const labelsToRemove = removeLabelIds.filter((id) => existingLabelIds.includes(id));
61
+ // Build new label array
62
+ const newLabelIds = [
63
+ ...existingLabelIds.filter((id) => !removeLabelIds.includes(id)),
64
+ ...labelsToAdd,
65
+ ];
66
+ // Update the issue
67
+ const payload = await client.updateIssue(issueId, {
68
+ labelIds: newLabelIds,
69
+ });
70
+ if (!payload.success) {
71
+ results.push({
72
+ identifier,
73
+ id: issueId,
74
+ success: false,
75
+ error: 'Failed to update labels',
76
+ });
77
+ return;
78
+ }
79
+ const updatedIssue = await payload.issue;
80
+ results.push({
81
+ identifier: updatedIssue?.identifier ?? identifier,
82
+ id: issueId,
83
+ success: true,
84
+ labelsAdded: labelsToAdd.length > 0 ? labelsToAdd : undefined,
85
+ labelsRemoved: labelsToRemove.length > 0 ? labelsToRemove : undefined,
86
+ });
87
+ }
88
+ catch (err) {
89
+ results.push({
90
+ identifier,
91
+ id: '',
92
+ success: false,
93
+ error: err instanceof Error ? err.message : 'Unknown error',
94
+ });
95
+ }
96
+ }));
97
+ const successCount = results.filter((r) => r.success).length;
98
+ const failedCount = results.filter((r) => !r.success).length;
99
+ print(success({
100
+ totalRequested: identifiers.length,
101
+ successCount,
102
+ failedCount,
103
+ labelsToAdd: addLabelIds.length > 0 ? addLabelIds : undefined,
104
+ labelsToRemove: removeLabelIds.length > 0 ? removeLabelIds : undefined,
105
+ results,
106
+ }));
107
+ }
108
+ catch (err) {
109
+ handleError(err);
110
+ this.exit(1);
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,15 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class IssuesBulkUpdate extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ ids: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ 'state-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ priority: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ 'assignee-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ estimate: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ 'label-ids': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ };
14
+ run(): Promise<void>;
15
+ }
@@ -0,0 +1,131 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { getClient } from '../../lib/client.js';
3
+ import { success, print } from '../../lib/output.js';
4
+ import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
5
+ import { resolveIssueId } from '../../lib/issue-utils.js';
6
+ export default class IssuesBulkUpdate extends Command {
7
+ static description = 'Update multiple issues at once';
8
+ static examples = [
9
+ '<%= config.bin %> issues bulk-update --ids ENG-1,ENG-2,ENG-3 --state-id STATE_ID',
10
+ '<%= config.bin %> issues bulk-update --ids ENG-1,ENG-2 --priority 2',
11
+ '<%= config.bin %> issues bulk-update --ids ENG-1,ENG-2,ENG-3 --assignee-id USER_ID',
12
+ '<%= config.bin %> issues bulk-update --ids ENG-1,ENG-2 --state-id STATE_ID --priority 1',
13
+ ];
14
+ static flags = {
15
+ ids: Flags.string({
16
+ description: 'Comma-separated issue IDs or identifiers (e.g., ENG-1,ENG-2,ENG-3)',
17
+ required: true,
18
+ }),
19
+ 'state-id': Flags.string({
20
+ description: 'New state ID for all issues',
21
+ }),
22
+ priority: Flags.integer({
23
+ char: 'p',
24
+ description: 'New priority (0=none, 1=urgent, 2=high, 3=medium, 4=low)',
25
+ }),
26
+ 'assignee-id': Flags.string({
27
+ description: 'New assignee user ID (use empty string to unassign)',
28
+ }),
29
+ 'project-id': Flags.string({
30
+ description: 'New project ID',
31
+ }),
32
+ estimate: Flags.integer({
33
+ description: 'New estimate points',
34
+ }),
35
+ 'label-ids': Flags.string({
36
+ description: 'Comma-separated label IDs (replaces existing labels)',
37
+ }),
38
+ };
39
+ async run() {
40
+ try {
41
+ const { flags } = await this.parse(IssuesBulkUpdate);
42
+ const client = getClient();
43
+ // Build the update input from flags
44
+ const input = {};
45
+ if (flags['state-id'])
46
+ input.stateId = flags['state-id'];
47
+ if (flags.priority !== undefined)
48
+ input.priority = flags.priority;
49
+ if (flags['assignee-id'] !== undefined) {
50
+ input.assigneeId = flags['assignee-id'] || null;
51
+ }
52
+ if (flags['project-id'])
53
+ input.projectId = flags['project-id'];
54
+ if (flags.estimate !== undefined)
55
+ input.estimate = flags.estimate;
56
+ if (flags['label-ids'])
57
+ input.labelIds = flags['label-ids'].split(',').map((id) => id.trim());
58
+ if (Object.keys(input).length === 0) {
59
+ throw new CliError(ErrorCodes.INVALID_INPUT, 'No update fields provided. Use at least one of: --state-id, --priority, --assignee-id, --project-id, --estimate, --label-ids');
60
+ }
61
+ // Parse issue identifiers
62
+ const identifiers = flags.ids.split(',').map((id) => id.trim());
63
+ if (identifiers.length === 0) {
64
+ throw new CliError(ErrorCodes.INVALID_INPUT, 'No issue IDs provided');
65
+ }
66
+ // Process all updates
67
+ const results = [];
68
+ // Resolve all issue IDs first
69
+ const resolvedIds = await Promise.all(identifiers.map(async (identifier) => {
70
+ try {
71
+ const id = await resolveIssueId(client, identifier);
72
+ return { identifier, id, error: null };
73
+ }
74
+ catch (err) {
75
+ return { identifier, id: null, error: err instanceof Error ? err.message : 'Unknown error' };
76
+ }
77
+ }));
78
+ // Update all resolved issues
79
+ await Promise.all(resolvedIds.map(async ({ identifier, id, error }) => {
80
+ if (error || !id) {
81
+ results.push({
82
+ identifier,
83
+ id: id ?? '',
84
+ success: false,
85
+ error: error ?? 'Failed to resolve issue ID',
86
+ });
87
+ return;
88
+ }
89
+ try {
90
+ const payload = await client.updateIssue(id, input);
91
+ const issue = await payload.issue;
92
+ if (!issue) {
93
+ results.push({
94
+ identifier,
95
+ id,
96
+ success: false,
97
+ error: 'Failed to update issue',
98
+ });
99
+ return;
100
+ }
101
+ results.push({
102
+ identifier: issue.identifier,
103
+ id: issue.id,
104
+ success: true,
105
+ });
106
+ }
107
+ catch (err) {
108
+ results.push({
109
+ identifier,
110
+ id,
111
+ success: false,
112
+ error: err instanceof Error ? err.message : 'Unknown error',
113
+ });
114
+ }
115
+ }));
116
+ const successCount = results.filter((r) => r.success).length;
117
+ const failedCount = results.filter((r) => !r.success).length;
118
+ print(success({
119
+ totalRequested: identifiers.length,
120
+ successCount,
121
+ failedCount,
122
+ results,
123
+ updatedFields: Object.keys(input),
124
+ }));
125
+ }
126
+ catch (err) {
127
+ handleError(err);
128
+ this.exit(1);
129
+ }
130
+ }
131
+ }
@@ -2,6 +2,7 @@ import { Command, Flags } from '@oclif/core';
2
2
  import { getClient } from '../../lib/client.js';
3
3
  import { success, print } from '../../lib/output.js';
4
4
  import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
5
+ import { getDefaultTeamId } from '../../lib/config.js';
5
6
  export default class IssuesCreate extends Command {
6
7
  static description = 'Create a new issue';
7
8
  static examples = [
@@ -65,12 +66,14 @@ export default class IssuesCreate extends Command {
65
66
  if (!flags.title) {
66
67
  throw new CliError(ErrorCodes.MISSING_REQUIRED_FIELD, 'Title is required. Use --title or --input');
67
68
  }
68
- if (!flags['team-id']) {
69
- throw new CliError(ErrorCodes.MISSING_REQUIRED_FIELD, 'Team ID is required. Use --team-id or --input');
69
+ // Use provided team-id or fall back to default
70
+ const teamId = flags['team-id'] ?? getDefaultTeamId();
71
+ if (!teamId) {
72
+ throw new CliError(ErrorCodes.MISSING_REQUIRED_FIELD, 'Team ID is required. Use --team-id, --input, or configure default with "linear config set default-team-id TEAM_ID"');
70
73
  }
71
74
  input = {
72
75
  title: flags.title,
73
- teamId: flags['team-id'],
76
+ teamId,
74
77
  };
75
78
  if (flags.description)
76
79
  input.description = flags.description;
@@ -7,7 +7,7 @@ export default class ProjectsCreate extends Command {
7
7
  name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
8
  description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
9
  state: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
- 'team-ids': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'team-ids': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  'lead-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
12
  'start-date': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
13
  'target-date': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -2,6 +2,7 @@ import { Command, Flags } from '@oclif/core';
2
2
  import { getClient } from '../../lib/client.js';
3
3
  import { success, print, printItem } from '../../lib/output.js';
4
4
  import { handleError, CliError, ErrorCodes } from '../../lib/errors.js';
5
+ import { getDefaultTeamId } from '../../lib/config.js';
5
6
  export default class ProjectsCreate extends Command {
6
7
  static description = 'Create a project';
7
8
  static examples = [
@@ -31,8 +32,7 @@ export default class ProjectsCreate extends Command {
31
32
  options: ['planned', 'started', 'paused', 'completed', 'canceled'],
32
33
  }),
33
34
  'team-ids': Flags.string({
34
- description: 'Team IDs (comma-separated)',
35
- required: true,
35
+ description: 'Team IDs (comma-separated). Uses default team if not provided.',
36
36
  }),
37
37
  'lead-id': Flags.string({
38
38
  description: 'Lead user ID',
@@ -49,7 +49,18 @@ export default class ProjectsCreate extends Command {
49
49
  const { flags } = await this.parse(ProjectsCreate);
50
50
  const format = flags.format;
51
51
  const client = getClient();
52
- const teamIds = flags['team-ids'].split(',').map((id) => id.trim());
52
+ // Use provided team-ids or fall back to default
53
+ let teamIds;
54
+ if (flags['team-ids']) {
55
+ teamIds = flags['team-ids'].split(',').map((id) => id.trim());
56
+ }
57
+ else {
58
+ const defaultTeamId = getDefaultTeamId();
59
+ if (!defaultTeamId) {
60
+ throw new CliError(ErrorCodes.MISSING_REQUIRED_FIELD, 'Team IDs required. Use --team-ids or configure default with "linear config set default-team-id TEAM_ID"');
61
+ }
62
+ teamIds = [defaultTeamId];
63
+ }
53
64
  const payload = await client.createProject({
54
65
  name: flags.name,
55
66
  description: flags.description,
@@ -0,0 +1,9 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Setup extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ remove: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,91 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join, dirname } from 'node:path';
5
+ import { success, print } from '../lib/output.js';
6
+ import { handleError } from '../lib/errors.js';
7
+ const CLAUDE_MD_PATH = join(homedir(), '.claude', 'CLAUDE.md');
8
+ const LINEAR_CLI_SECTION_START = '<!-- LINEAR-CLI-START -->';
9
+ const LINEAR_CLI_SECTION_END = '<!-- LINEAR-CLI-END -->';
10
+ const LINEAR_CLI_DOCS = `${LINEAR_CLI_SECTION_START}
11
+ ## Linear CLI
12
+
13
+ CLI for Linear. Run \`linear info\` for full documentation.
14
+
15
+ \`\`\`bash
16
+ linear auth login # Authenticate (one-time)
17
+ linear info # Get all commands and docs
18
+ linear issues create --title "X" --team-id ID
19
+ linear issues bulk-update --ids A,B,C --state-id ID
20
+ \`\`\`
21
+ ${LINEAR_CLI_SECTION_END}`;
22
+ export default class Setup extends Command {
23
+ static description = 'Add Linear CLI instructions to your CLAUDE.md';
24
+ static examples = [
25
+ '<%= config.bin %> setup',
26
+ '<%= config.bin %> setup --remove',
27
+ ];
28
+ static flags = {
29
+ remove: Flags.boolean({
30
+ description: 'Remove Linear CLI section from CLAUDE.md',
31
+ default: false,
32
+ }),
33
+ };
34
+ async run() {
35
+ try {
36
+ const { flags } = await this.parse(Setup);
37
+ const dir = dirname(CLAUDE_MD_PATH);
38
+ if (!existsSync(dir)) {
39
+ mkdirSync(dir, { recursive: true });
40
+ }
41
+ let content = '';
42
+ if (existsSync(CLAUDE_MD_PATH)) {
43
+ content = readFileSync(CLAUDE_MD_PATH, 'utf-8');
44
+ }
45
+ const hasSection = content.includes(LINEAR_CLI_SECTION_START);
46
+ if (flags.remove) {
47
+ if (!hasSection) {
48
+ print(success({
49
+ action: 'none',
50
+ message: 'Linear CLI section not found in CLAUDE.md',
51
+ path: CLAUDE_MD_PATH,
52
+ }));
53
+ return;
54
+ }
55
+ const regex = new RegExp(`\\n?${LINEAR_CLI_SECTION_START}[\\s\\S]*?${LINEAR_CLI_SECTION_END}\\n?`, 'g');
56
+ content = content.replace(regex, '\n').trim() + '\n';
57
+ writeFileSync(CLAUDE_MD_PATH, content, 'utf-8');
58
+ print(success({
59
+ action: 'removed',
60
+ message: 'Linear CLI section removed from CLAUDE.md',
61
+ path: CLAUDE_MD_PATH,
62
+ }));
63
+ return;
64
+ }
65
+ if (hasSection) {
66
+ const regex = new RegExp(`${LINEAR_CLI_SECTION_START}[\\s\\S]*?${LINEAR_CLI_SECTION_END}`, 'g');
67
+ content = content.replace(regex, LINEAR_CLI_DOCS);
68
+ writeFileSync(CLAUDE_MD_PATH, content, 'utf-8');
69
+ print(success({
70
+ action: 'updated',
71
+ message: 'Linear CLI section updated in CLAUDE.md',
72
+ path: CLAUDE_MD_PATH,
73
+ }));
74
+ }
75
+ else {
76
+ const separator = content.length > 0 && !content.endsWith('\n\n') ? '\n\n' : '';
77
+ content = content + separator + LINEAR_CLI_DOCS + '\n';
78
+ writeFileSync(CLAUDE_MD_PATH, content, 'utf-8');
79
+ print(success({
80
+ action: 'added',
81
+ message: 'Linear CLI section added to CLAUDE.md',
82
+ path: CLAUDE_MD_PATH,
83
+ }));
84
+ }
85
+ }
86
+ catch (err) {
87
+ handleError(err);
88
+ this.exit(1);
89
+ }
90
+ }
91
+ }
@@ -1,4 +1,4 @@
1
- import type { ConfigFile } from './types.js';
1
+ import type { ConfigFile, ConfigDefaults } from './types.js';
2
2
  /**
3
3
  * Read the configuration file.
4
4
  * @throws {CliError} When config file exists but cannot be parsed.
@@ -31,3 +31,38 @@ export declare const removeApiKey: () => void;
31
31
  * Get the config file path (for display purposes).
32
32
  */
33
33
  export declare const getConfigPath: () => string;
34
+ /**
35
+ * Get defaults from config, with migration from legacy defaultTeamId.
36
+ */
37
+ export declare const getDefaults: () => ConfigDefaults;
38
+ /**
39
+ * Get the default team ID from config.
40
+ * Returns undefined if not configured.
41
+ */
42
+ export declare const getDefaultTeamId: () => string | undefined;
43
+ /**
44
+ * Get the default team key from config.
45
+ * Returns undefined if not configured.
46
+ */
47
+ export declare const getDefaultTeamKey: () => string | undefined;
48
+ /**
49
+ * Set a default configuration value.
50
+ */
51
+ export declare const setDefault: (key: keyof ConfigDefaults, value: string | undefined) => void;
52
+ /**
53
+ * Remove a default configuration value.
54
+ */
55
+ export declare const removeDefault: (key: keyof ConfigDefaults) => void;
56
+ /**
57
+ * Valid configuration keys that can be set.
58
+ */
59
+ export declare const CONFIG_KEYS: readonly ["default-team-id", "default-team-key"];
60
+ export type ConfigKey = (typeof CONFIG_KEYS)[number];
61
+ /**
62
+ * Map config key to internal defaults key.
63
+ */
64
+ export declare const configKeyToDefaultsKey: (key: ConfigKey) => keyof ConfigDefaults;
65
+ /**
66
+ * Check if a key is a valid config key.
67
+ */
68
+ export declare const isValidConfigKey: (key: string) => key is ConfigKey;
@@ -95,3 +95,79 @@ export const removeApiKey = () => {
95
95
  * Get the config file path (for display purposes).
96
96
  */
97
97
  export const getConfigPath = () => CONFIG_FILE;
98
+ /**
99
+ * Get defaults from config, with migration from legacy defaultTeamId.
100
+ */
101
+ export const getDefaults = () => {
102
+ const config = readConfig();
103
+ // Migrate legacy defaultTeamId to defaults.teamId
104
+ if (config.defaultTeamId && !config.defaults?.teamId) {
105
+ return {
106
+ teamId: config.defaultTeamId,
107
+ ...config.defaults,
108
+ };
109
+ }
110
+ return config.defaults ?? {};
111
+ };
112
+ /**
113
+ * Get the default team ID from config.
114
+ * Returns undefined if not configured.
115
+ */
116
+ export const getDefaultTeamId = () => {
117
+ const defaults = getDefaults();
118
+ return defaults.teamId;
119
+ };
120
+ /**
121
+ * Get the default team key from config.
122
+ * Returns undefined if not configured.
123
+ */
124
+ export const getDefaultTeamKey = () => {
125
+ const defaults = getDefaults();
126
+ return defaults.teamKey;
127
+ };
128
+ /**
129
+ * Set a default configuration value.
130
+ */
131
+ export const setDefault = (key, value) => {
132
+ const config = readConfig();
133
+ if (!config.defaults) {
134
+ config.defaults = {};
135
+ }
136
+ if (value === undefined) {
137
+ delete config.defaults[key];
138
+ }
139
+ else {
140
+ config.defaults[key] = value;
141
+ }
142
+ // Clean up legacy field if we're setting teamId
143
+ if (key === 'teamId' && config.defaultTeamId) {
144
+ delete config.defaultTeamId;
145
+ }
146
+ writeConfig(config);
147
+ };
148
+ /**
149
+ * Remove a default configuration value.
150
+ */
151
+ export const removeDefault = (key) => {
152
+ setDefault(key, undefined);
153
+ };
154
+ /**
155
+ * Valid configuration keys that can be set.
156
+ */
157
+ export const CONFIG_KEYS = ['default-team-id', 'default-team-key'];
158
+ /**
159
+ * Map config key to internal defaults key.
160
+ */
161
+ export const configKeyToDefaultsKey = (key) => {
162
+ const mapping = {
163
+ 'default-team-id': 'teamId',
164
+ 'default-team-key': 'teamKey',
165
+ };
166
+ return mapping[key];
167
+ };
168
+ /**
169
+ * Check if a key is a valid config key.
170
+ */
171
+ export const isValidConfigKey = (key) => {
172
+ return CONFIG_KEYS.includes(key);
173
+ };
@@ -34,7 +34,13 @@ export interface ErrorResponse {
34
34
  }
35
35
  export type CommandResponse<T> = SuccessResponse<T> | ErrorResponse;
36
36
  export type CommandListResponse<T> = SuccessListResponse<T> | ErrorResponse;
37
+ export interface ConfigDefaults {
38
+ teamId?: string;
39
+ teamKey?: string;
40
+ }
37
41
  export interface ConfigFile {
38
42
  apiKey?: string;
43
+ defaults?: ConfigDefaults;
44
+ /** @deprecated Use defaults.teamId instead */
39
45
  defaultTeamId?: string;
40
46
  }