@chalksurf/cli 0.2.1 → 0.2.3

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/README.md CHANGED
@@ -1,21 +1,19 @@
1
1
  # `@chalksurf/cli`
2
2
 
3
- Publishable ChalkSurf CLI package.
4
-
5
- Current internal release line: `0.2.1`. Expect breaking changes while the CLI is still only used internally.
3
+ Publishable ChalkSurf CLI package. Expect breaking changes while the CLI is still only used internally.
6
4
 
7
5
  ## Installation
8
6
 
9
7
  Run the published CLI without a global install:
10
8
 
11
9
  ```bash
12
- npx @chalksurf/cli@0.2.1 --help
10
+ npx @chalksurf/cli@ --help
13
11
  ```
14
12
 
15
13
  Install it globally when you want a persistent local binary:
16
14
 
17
15
  ```bash
18
- npm install -g @chalksurf/cli@0.2.1
16
+ npm install -g @chalksurf/cli
19
17
  chalksurf --version
20
18
  ```
21
19
 
@@ -34,32 +32,33 @@ Headless or agent flow:
34
32
 
35
33
  ```bash
36
34
  CHALKSURF_TOKEN=cs_cli_... \
37
- printf '%s' "$CHALKSURF_TOKEN" | npx @chalksurf/cli@0.2.1 auth login \
35
+ printf '%s' "$CHALKSURF_TOKEN" | npx @chalksurf/cli auth login \
38
36
  --profile prod-codex \
39
37
  --base-url https://chalksurf-api.fly.dev \
40
38
  --with-token
41
39
 
42
- npx @chalksurf/cli@0.2.1 --profile prod-codex sheet import --manifest - --wait --json < import.json
40
+ npx @chalksurf/cli --profile prod-codex sheet import --manifest - --wait --json < import.json
43
41
  ```
44
42
 
45
- Sheet import manifests use top-level `sheets[]`, where each sheet has one `targetFolderPath` and one or more ordered `sources[]`. See [the canonical example](./docs/examples/sheet-import-manifest.json).
43
+ Sheet import manifests use top-level `sheets[]`, where each sheet has one `targetFolderPath` and one or more ordered `sources[]`. See [the canonical example](./packages/cli/docs/examples/sheet-import-manifest.json).
46
44
 
47
45
  ## Docs
48
46
 
49
- - [Manual operator guide](./docs/manual.md)
50
- - [Agent and Codex guide](./docs/agents.md)
51
- - [Manifest reference](./docs/manifest.md)
52
- - [Exit codes and JSON errors](./docs/exit-codes.md)
53
- - [Sheet import schema](./schemas/sheet-import-manifest.schema.json)
54
- - [Exercise import schema](./schemas/exercise-import-manifest.schema.json)
55
- - [Exercise solution import schema](./schemas/exercise-solution-import-manifest.schema.json)
47
+ - [Manual operator guide](./packages/cli/docs/manual.md)
48
+ - [Agent and Codex guide](./packages/cli/docs/agents.md)
49
+ - [Manifest reference](./packages/cli/docs/manifest.md)
50
+ - [Exit codes and JSON errors](./packages/cli/docs/exit-codes.md)
51
+ - [Sheet import schema](./packages/cli/schemas/sheet-import-manifest.schema.json)
52
+ - [Exercise import schema](./packages/cli/schemas/exercise-import-manifest.schema.json)
53
+ - [Exercise solution import schema](./packages/cli/schemas/exercise-solution-import-manifest.schema.json)
54
+ - [Exercise sheet solution import schema](./packages/cli/schemas/exercise-sheet-solution-import-manifest.schema.json)
56
55
 
57
56
  ## Local And Staging Testing
58
57
 
59
58
  Use separate profiles so local, staging, production, human, and agent tokens do not overwrite each other:
60
59
 
61
60
  ```bash
62
- npm run cli-dev -- auth login --profile dev-cztamas --base-url http://localhost:8080
61
+ npm run cli-dev -- auth login --profile dev-cztamas --base-url http://localhost:3101
63
62
  npm run cli-dev -- profile use dev-cztamas
64
63
  npm run cli-dev -- auth status --json
65
64
  npm run cli-dev -- sheet import ./fixtures/algebra.pdf --wait --json
@@ -14,7 +14,8 @@ import { createConfigStore } from '../lib/config-store.js';
14
14
  import { createOutput } from '../lib/output.js';
15
15
  import { createPromptSecret } from '../lib/prompt-secret.js';
16
16
  const packageJson = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
17
- const createCli = ({ cwd, configPath, env, now, output, sleep, stdin, promptSecret, }) => {
17
+ const createCli = ({ cwd, configPath, env, now, output, sleep, stdin, }) => {
18
+ const promptSecret = createPromptSecret();
18
19
  const commandContext = {
19
20
  cwd,
20
21
  configStore: createConfigStore({ configPath, env }),
@@ -101,7 +102,7 @@ const resolveCommandName = (argv) => {
101
102
  }
102
103
  return 'chalksurf';
103
104
  };
104
- export const runCli = async ({ argv, cwd = process.cwd(), configPath, env = process.env, nowImpl = Date.now, promptSecretImpl = createPromptSecret(), sleepImpl = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)), stdin = process.stdin, stdout = process.stdout, stderr = process.stderr, }) => {
105
+ export const runCli = async ({ argv, cwd = process.cwd(), configPath, env = process.env, nowImpl = Date.now, sleepImpl = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)), stdin = process.stdin, stdout = process.stdout, stderr = process.stderr, }) => {
105
106
  const output = createOutput({
106
107
  json: hasFlag(argv, ['--json']),
107
108
  stdout,
@@ -114,7 +115,6 @@ export const runCli = async ({ argv, cwd = process.cwd(), configPath, env = proc
114
115
  env,
115
116
  now: nowImpl,
116
117
  output,
117
- promptSecret: promptSecretImpl,
118
118
  sleep: sleepImpl,
119
119
  stdin,
120
120
  });
@@ -1,12 +1,39 @@
1
1
  import { z } from 'zod';
2
2
  import { createApiClient } from '../lib/api-client.js';
3
3
  import { CliCommandError } from '../lib/cli-error.js';
4
+ import { normalizeChoiceValues, normalizeLimit, normalizeOffset, normalizeOptionalInteger, normalizeOptionalText, normalizeOwnership, normalizeStringValues, toApiOwnership, } from '../lib/command-options.js';
4
5
  import { buildFileImportCommandSources, buildFileImportFormData, normalizeWaitTimeoutMs } from '../lib/import-files.js';
5
- import { buildCliImportSources, summarizeCliImportJobs, } from '../lib/import-output.js';
6
+ import { buildCliImportSources, formatCliImportTranslationJobs, summarizeCliImportJobs, } from '../lib/import-output.js';
6
7
  import { loadExerciseImportManifest, loadExerciseSolutionImportManifest } from '../lib/manifest.js';
7
- import { mapApiErrorToCliError, requireResolvedBaseUrl, requireResolvedToken, resolveRequestedOrganizationId, } from '../lib/session.js';
8
+ import { createResolvedApiClient, mapApiErrorToCliError, requireResolvedBaseUrl, requireResolvedToken, resolveRequestedOrganizationId, } from '../lib/session.js';
8
9
  import { cleanupResolvedSources, resolveSources } from '../lib/source-resolver.js';
10
+ import { resolveRequestedTranslateToLanguages, translationLanguages, } from '../lib/translation-languages.js';
9
11
  import { getWaitError, getWaitExitCode, throwSilentExitCode, waitForCliJobs } from '../lib/user-jobs.js';
12
+ const exerciseStatusOptions = ['verified', 'unverified', 'invalid'];
13
+ const getExerciseTextPreview = ({ exercise, language }) => {
14
+ if (language && exercise.text[language]) {
15
+ return exercise.text[language];
16
+ }
17
+ return Object.values(exercise.text)[0] ?? '';
18
+ };
19
+ const formatExerciseSearchOutput = ({ exercises, language, totalCount, }) => {
20
+ if (exercises.length === 0) {
21
+ return 'No exercises found.';
22
+ }
23
+ return [
24
+ `${exercises.length} of ${totalCount} exercise${totalCount === 1 ? '' : 's'} returned.`,
25
+ ...exercises.map((exercise) => {
26
+ const preview = getExerciseTextPreview({ exercise, language }).replace(/\s+/g, ' ').trim();
27
+ const labels = exercise.labels.length > 0 ? ` [${exercise.labels.join(', ')}]` : '';
28
+ return `${exercise.id} ${exercise.status}${labels}${preview ? ` ${preview}` : ''}`;
29
+ }),
30
+ ].join('\n');
31
+ };
32
+ const normalizeSearchNumberRange = ({ maxDifficulty, minDifficulty, }) => {
33
+ if (minDifficulty !== null && maxDifficulty !== null && minDifficulty > maxDifficulty) {
34
+ throw new CliCommandError('--min-difficulty cannot be greater than --max-difficulty.', 2);
35
+ }
36
+ };
10
37
  const resolveRequestedUuid = ({ flagValue, fallbackValue, label, required = false, }) => {
11
38
  const resolvedValue = flagValue?.trim() || fallbackValue?.trim();
12
39
  if (!resolvedValue) {
@@ -50,6 +77,7 @@ const buildWaitedExerciseImportJob = ({ jobId, sources, waitedJob, }) => {
50
77
  error: waitedJob?.error,
51
78
  exerciseId: waitedJob?.exerciseId,
52
79
  exerciseIds: waitedJob?.exerciseIds,
80
+ translationJobs: waitedJob?.translationJobs,
53
81
  };
54
82
  };
55
83
  const formatQueuedImportOutput = (job) => {
@@ -66,13 +94,17 @@ const formatQueuedSolutionImportOutput = (job) => {
66
94
  };
67
95
  const formatWaitedImportOutput = (job) => {
68
96
  if (job.status === 'completed') {
97
+ const translationJobLines = formatCliImportTranslationJobs(job.translationJobs);
69
98
  if (job.exerciseIds && job.exerciseIds.length > 0) {
70
- return `${job.jobId} completed -> ${job.exerciseIds.length} exercise${job.exerciseIds.length === 1 ? '' : 's'}`;
99
+ return [
100
+ `${job.jobId} completed -> ${job.exerciseIds.length} exercise${job.exerciseIds.length === 1 ? '' : 's'}`,
101
+ ...translationJobLines,
102
+ ].join('\n');
71
103
  }
72
104
  if (job.exerciseId) {
73
- return `${job.jobId} completed -> ${job.exerciseId}`;
105
+ return [`${job.jobId} completed -> ${job.exerciseId}`, ...translationJobLines].join('\n');
74
106
  }
75
- return `${job.jobId} completed`;
107
+ return [`${job.jobId} completed`, ...translationJobLines].join('\n');
76
108
  }
77
109
  if (job.status === 'failed') {
78
110
  return `${job.jobId} failed -> ${job.error ?? 'Unknown error'}`;
@@ -81,6 +113,133 @@ const formatWaitedImportOutput = (job) => {
81
113
  };
82
114
  export const registerExerciseCommands = (exerciseYargs, context) => {
83
115
  return exerciseYargs
116
+ .command('search', 'Search exercises visible to the selected organization', (searchYargs) => searchYargs
117
+ .option('text', {
118
+ type: 'string',
119
+ describe: 'Search exercise text. Requires --language.',
120
+ })
121
+ .option('language', {
122
+ type: 'string',
123
+ describe: 'Language to search or preview, such as english or hungarian',
124
+ })
125
+ .option('semantic', {
126
+ type: 'string',
127
+ describe: 'Run semantic search with this query text',
128
+ })
129
+ .option('label', {
130
+ array: true,
131
+ type: 'string',
132
+ describe: 'Require an exercise label or label prefix',
133
+ })
134
+ .option('age', {
135
+ type: 'number',
136
+ describe: 'Filter exercises appropriate for this age',
137
+ })
138
+ .option('min-difficulty', {
139
+ type: 'number',
140
+ describe: 'Minimum difficulty',
141
+ })
142
+ .option('max-difficulty', {
143
+ type: 'number',
144
+ describe: 'Maximum difficulty',
145
+ })
146
+ .option('status', {
147
+ array: true,
148
+ type: 'string',
149
+ describe: `Only include exercises with these statuses (${exerciseStatusOptions.join(', ')})`,
150
+ })
151
+ .option('ownership', {
152
+ type: 'string',
153
+ default: 'own',
154
+ describe: 'Visibility scope: own, public, or all',
155
+ })
156
+ .option('limit', {
157
+ type: 'number',
158
+ default: 20,
159
+ describe: 'Maximum number of exercises to return',
160
+ })
161
+ .option('offset', {
162
+ type: 'number',
163
+ default: 0,
164
+ describe: 'Number of matching exercises to skip',
165
+ })
166
+ .example('chalksurf exercise search --text "binomial theorem" --language english --ownership own --json', 'Verify imported exercises in the selected organization')
167
+ .example('chalksurf --profile prod-codex exercise search --label geometry.triangles --status unverified --json', 'Find agent-created exercises that still need review'), async (argv) => {
168
+ const text = normalizeOptionalText(argv.text);
169
+ const language = normalizeOptionalText(argv.language);
170
+ const semanticSearch = normalizeOptionalText(argv.semantic);
171
+ const ownership = normalizeOwnership(argv.ownership);
172
+ const labels = normalizeStringValues(argv.label);
173
+ const status = normalizeChoiceValues({
174
+ allowedValues: exerciseStatusOptions,
175
+ label: '--status',
176
+ values: argv.status,
177
+ });
178
+ const age = normalizeOptionalInteger({ label: '--age', value: argv.age });
179
+ const minDifficulty = normalizeOptionalInteger({
180
+ label: '--min-difficulty',
181
+ value: argv.minDifficulty,
182
+ });
183
+ const maxDifficulty = normalizeOptionalInteger({
184
+ label: '--max-difficulty',
185
+ value: argv.maxDifficulty,
186
+ });
187
+ normalizeSearchNumberRange({ minDifficulty, maxDifficulty });
188
+ if (text !== null && language === null) {
189
+ throw new CliCommandError('--language is required when --text is provided.', 2);
190
+ }
191
+ const limit = normalizeLimit(argv.limit);
192
+ const offset = normalizeOffset(argv.offset);
193
+ const { apiClient, organizationId } = await createResolvedApiClient({
194
+ context,
195
+ baseUrlFlagValue: argv.baseUrl,
196
+ organizationFlagValue: argv.organization,
197
+ profileName: argv.profile,
198
+ });
199
+ let searchResult;
200
+ try {
201
+ searchResult = await apiClient.searchExercises({
202
+ age,
203
+ language,
204
+ labels,
205
+ minDifficulty,
206
+ maxDifficulty,
207
+ text,
208
+ semanticSearch,
209
+ status,
210
+ ownership: toApiOwnership(ownership),
211
+ limit,
212
+ offset,
213
+ });
214
+ }
215
+ catch (error) {
216
+ throw mapApiErrorToCliError(error);
217
+ }
218
+ context.output.print({
219
+ request: {
220
+ age,
221
+ language,
222
+ labels,
223
+ minDifficulty,
224
+ maxDifficulty,
225
+ text,
226
+ semanticSearch,
227
+ status: status ?? [],
228
+ ownership,
229
+ limit,
230
+ offset,
231
+ organizationId: organizationId ?? null,
232
+ },
233
+ exercises: searchResult.exercises,
234
+ totalCount: searchResult.totalCount,
235
+ }, (result) => formatExerciseSearchOutput({
236
+ exercises: result.exercises,
237
+ language: result.request.language ?? undefined,
238
+ totalCount: result.totalCount,
239
+ }), {
240
+ command: 'exercise search',
241
+ });
242
+ })
84
243
  .command('import [sources..]', 'Import one or more exercises', (importYargs) => importYargs
85
244
  .positional('sources', {
86
245
  array: true,
@@ -103,6 +262,11 @@ export const registerExerciseCommands = (exerciseYargs, context) => {
103
262
  type: 'number',
104
263
  default: 300000,
105
264
  describe: 'Maximum time to wait before timing out',
265
+ })
266
+ .option('translate-to', {
267
+ array: true,
268
+ type: 'string',
269
+ describe: `Generate translated exercise content for these languages (${translationLanguages.join(', ')})`,
106
270
  })
107
271
  .option('wait', {
108
272
  type: 'boolean',
@@ -111,11 +275,15 @@ export const registerExerciseCommands = (exerciseYargs, context) => {
111
275
  })
112
276
  .example('chalksurf exercise import ./fixtures/problem-set.pdf --sheet-id 00000000-0000-4000-8000-000000000001 --wait', 'Import a problem set into an existing sheet')
113
277
  .example('cat exercise-import.json | chalksurf --profile prod-codex exercise import --manifest - --wait --json', 'Drive imports from a manifest in an agent or CI flow')
114
- .epilogue('When --wait is set, the command exits after the queued import job reaches a terminal state.'), async (argv) => {
278
+ .epilogue([
279
+ 'When --wait is set, the command exits after the queued import job reaches a terminal state.',
280
+ 'For translated exercise imports, requested translations continue as separate jobs listed in the import result.',
281
+ ].join('\n')), async (argv) => {
115
282
  const config = await context.configStore.loadProfile({ profileName: argv.profile });
116
283
  const rawSources = (argv.sources ?? []).map(String);
117
284
  const manifestPath = argv.manifest === '' ? '-' : argv.manifest;
118
285
  const normalizedRawSources = manifestPath === '-' && rawSources[0] === '-' ? rawSources.slice(1) : rawSources;
286
+ const requestedTranslateTo = resolveRequestedTranslateToLanguages(argv.translateTo);
119
287
  const resolvedBaseUrl = requireResolvedBaseUrl({
120
288
  flagValue: argv.baseUrl,
121
289
  env: context.env,
@@ -145,6 +313,7 @@ export const registerExerciseCommands = (exerciseYargs, context) => {
145
313
  fallbackValue: commandSources.exerciseSheetId,
146
314
  label: '--sheet-id',
147
315
  });
316
+ const translateToLanguages = requestedTranslateTo ?? commandSources.translateTo;
148
317
  const apiClient = createApiClient({
149
318
  baseUrl: resolvedBaseUrl.value,
150
319
  token: resolvedToken.value,
@@ -170,6 +339,9 @@ export const registerExerciseCommands = (exerciseYargs, context) => {
170
339
  if (exerciseSheetId) {
171
340
  formData.set('exerciseSheetId', exerciseSheetId);
172
341
  }
342
+ if (translateToLanguages && translateToLanguages.length > 0) {
343
+ formData.set('translateToLanguages', JSON.stringify(translateToLanguages));
344
+ }
173
345
  let importResult;
174
346
  try {
175
347
  importResult = await apiClient.importExercise(formData);
@@ -1,7 +1,16 @@
1
- import { createApiClient } from '../lib/api-client.js';
2
1
  import { CliCommandError } from '../lib/cli-error.js';
3
- import { mapApiErrorToCliError, requireResolvedBaseUrl, requireResolvedToken, resolveRequestedOrganizationId, } from '../lib/session.js';
2
+ import { normalizeChoiceValues, normalizeLimit, normalizeOffset, normalizeOptionalText, } from '../lib/command-options.js';
3
+ import { createResolvedApiClient, mapApiErrorToCliError } from '../lib/session.js';
4
4
  import { formatCliJobSummary, getWaitError, getWaitExitCode, serializeCliJob, throwSilentExitCode, waitForCliJobs, } from '../lib/user-jobs.js';
5
+ const jobStatusOptions = ['pending', 'in_progress', 'completed', 'failed'];
6
+ const jobTypeOptions = [
7
+ 'exercise_sheet_import',
8
+ 'exercise_import',
9
+ 'exercise_solution_import',
10
+ 'exercise_solution_generation',
11
+ 'exercise_translation_generation',
12
+ 'exercise_sheet_translation_generation',
13
+ ];
5
14
  const normalizeTimeoutMs = (timeoutMs) => {
6
15
  const normalizedTimeoutMs = timeoutMs ?? 300000;
7
16
  if (!Number.isFinite(normalizedTimeoutMs) || normalizedTimeoutMs <= 0) {
@@ -9,8 +18,95 @@ const normalizeTimeoutMs = (timeoutMs) => {
9
18
  }
10
19
  return normalizedTimeoutMs;
11
20
  };
21
+ const formatJobListOutput = ({ jobs }) => {
22
+ if (jobs.length === 0) {
23
+ return 'No jobs found.';
24
+ }
25
+ return jobs.map((job) => formatCliJobSummary(job)).join('\n');
26
+ };
12
27
  export const registerJobCommands = (jobYargs, context) => {
13
28
  return jobYargs
29
+ .command('list', 'List jobs for the current user', (listYargs) => listYargs
30
+ .option('status', {
31
+ array: true,
32
+ type: 'string',
33
+ describe: `Only include jobs with these statuses (${jobStatusOptions.join(', ')})`,
34
+ })
35
+ .option('type', {
36
+ array: true,
37
+ type: 'string',
38
+ describe: `Only include jobs with these types (${jobTypeOptions.join(', ')})`,
39
+ })
40
+ .option('include-viewed', {
41
+ type: 'boolean',
42
+ default: false,
43
+ describe: 'Include jobs already marked as viewed',
44
+ })
45
+ .option('include-dismissed', {
46
+ type: 'boolean',
47
+ default: false,
48
+ describe: 'Include jobs already dismissed in the UI',
49
+ })
50
+ .option('limit', {
51
+ type: 'number',
52
+ default: 20,
53
+ describe: 'Maximum number of jobs to return',
54
+ })
55
+ .option('offset', {
56
+ type: 'number',
57
+ default: 0,
58
+ describe: 'Number of matching jobs to skip',
59
+ })
60
+ .example('chalksurf job list --status failed --include-dismissed --json', 'Find failed jobs for retry')
61
+ .example('chalksurf --profile prod-codex job list --type exercise_sheet_import --json', 'Inspect agent imports'), async (argv) => {
62
+ const statuses = normalizeChoiceValues({
63
+ allowedValues: jobStatusOptions,
64
+ label: '--status',
65
+ values: argv.status,
66
+ });
67
+ const types = normalizeChoiceValues({
68
+ allowedValues: jobTypeOptions,
69
+ label: '--type',
70
+ values: argv.type,
71
+ });
72
+ const limit = normalizeLimit(argv.limit);
73
+ const offset = normalizeOffset(argv.offset);
74
+ const { apiClient } = await createResolvedApiClient({
75
+ context,
76
+ baseUrlFlagValue: normalizeOptionalText(argv.baseUrl) ?? undefined,
77
+ organizationFlagValue: normalizeOptionalText(argv.organization) ?? undefined,
78
+ profileName: argv.profile,
79
+ });
80
+ let jobs;
81
+ try {
82
+ jobs = await apiClient.listUserJobs({
83
+ statuses,
84
+ types,
85
+ includeViewed: argv.includeViewed === true,
86
+ includeDismissed: argv.includeDismissed === true,
87
+ limit,
88
+ offset,
89
+ });
90
+ }
91
+ catch (error) {
92
+ throw mapApiErrorToCliError(error);
93
+ }
94
+ const serializedJobs = jobs.map(serializeCliJob);
95
+ context.output.print({
96
+ request: {
97
+ statuses: statuses ?? [],
98
+ types: types ?? [],
99
+ includeViewed: argv.includeViewed === true,
100
+ includeDismissed: argv.includeDismissed === true,
101
+ limit,
102
+ offset,
103
+ },
104
+ jobs: serializedJobs,
105
+ count: serializedJobs.length,
106
+ }, (result) => formatJobListOutput({ jobs: result.jobs }), {
107
+ command: 'job list',
108
+ });
109
+ })
14
110
  .command('get <jobId>', 'Fetch a single job', (getYargs) => getYargs
15
111
  .positional('jobId', {
16
112
  type: 'string',
@@ -18,25 +114,11 @@ export const registerJobCommands = (jobYargs, context) => {
18
114
  })
19
115
  .example('chalksurf job get job_123', 'Read one job in human-readable mode')
20
116
  .example('chalksurf job get job_123 --json', 'Read one job in machine-readable mode'), async (argv) => {
21
- const config = await context.configStore.loadProfile({ profileName: argv.profile });
22
- const resolvedBaseUrl = requireResolvedBaseUrl({
23
- flagValue: argv.baseUrl,
24
- env: context.env,
25
- config,
26
- });
27
- const resolvedToken = requireResolvedToken({
28
- env: context.env,
29
- config,
30
- });
31
- const organizationId = resolveRequestedOrganizationId({
32
- flagValue: argv.organization,
33
- env: context.env,
34
- config,
35
- });
36
- const apiClient = createApiClient({
37
- baseUrl: resolvedBaseUrl.value,
38
- token: resolvedToken.value,
39
- organizationId,
117
+ const { apiClient } = await createResolvedApiClient({
118
+ context,
119
+ baseUrlFlagValue: argv.baseUrl,
120
+ organizationFlagValue: argv.organization,
121
+ profileName: argv.profile,
40
122
  });
41
123
  let job;
42
124
  try {
@@ -63,25 +145,11 @@ export const registerJobCommands = (jobYargs, context) => {
63
145
  })
64
146
  .example('chalksurf job wait job_123 job_124', 'Wait for one or more jobs to reach a terminal state')
65
147
  .example('chalksurf job wait job_123 --timeout-ms 60000 --json', 'Poll a job for up to 60 seconds'), async (argv) => {
66
- const config = await context.configStore.loadProfile({ profileName: argv.profile });
67
- const resolvedBaseUrl = requireResolvedBaseUrl({
68
- flagValue: argv.baseUrl,
69
- env: context.env,
70
- config,
71
- });
72
- const resolvedToken = requireResolvedToken({
73
- env: context.env,
74
- config,
75
- });
76
- const organizationId = resolveRequestedOrganizationId({
77
- flagValue: argv.organization,
78
- env: context.env,
79
- config,
80
- });
81
- const apiClient = createApiClient({
82
- baseUrl: resolvedBaseUrl.value,
83
- token: resolvedToken.value,
84
- organizationId,
148
+ const { apiClient } = await createResolvedApiClient({
149
+ context,
150
+ baseUrlFlagValue: argv.baseUrl,
151
+ organizationFlagValue: argv.organization,
152
+ profileName: argv.profile,
85
153
  });
86
154
  const timeoutMs = normalizeTimeoutMs(argv.timeoutMs);
87
155
  const waitResult = await waitForCliJobs({