@chalksurf/cli 0.2.2 → 0.2.4
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 +21 -8
- package/dist/bin/chalksurf.js +6336 -158
- package/docs/agents.md +123 -3
- package/docs/examples/sheet-import-manifest.json +24 -0
- package/docs/manifest.md +24 -1
- package/docs/manual.md +43 -0
- package/docs/mcp.md +88 -0
- package/package.json +3 -2
- package/schemas/sheet-import-manifest.schema.json +78 -22
- package/dist/commands/auth.js +0 -174
- package/dist/commands/exercise.js +0 -600
- package/dist/commands/job.js +0 -172
- package/dist/commands/org.js +0 -115
- package/dist/commands/profile.js +0 -97
- package/dist/commands/sheet.js +0 -729
- package/dist/lib/api-client.js +0 -86
- package/dist/lib/cli-error.js +0 -46
- package/dist/lib/command-options.js +0 -61
- package/dist/lib/config-store.js +0 -354
- package/dist/lib/import-files.js +0 -120
- package/dist/lib/import-output.js +0 -81
- package/dist/lib/manifest.js +0 -295
- package/dist/lib/output.js +0 -57
- package/dist/lib/prompt-secret.js +0 -32
- package/dist/lib/session.js +0 -72
- package/dist/lib/source-resolver.js +0 -272
- package/dist/lib/translation-languages.js +0 -16
- package/dist/lib/user-jobs.js +0 -99
package/dist/commands/job.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { CliCommandError } from '../lib/cli-error.js';
|
|
2
|
-
import { normalizeChoiceValues, normalizeLimit, normalizeOffset, normalizeOptionalText, } from '../lib/command-options.js';
|
|
3
|
-
import { createResolvedApiClient, mapApiErrorToCliError } from '../lib/session.js';
|
|
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
|
-
];
|
|
14
|
-
const normalizeTimeoutMs = (timeoutMs) => {
|
|
15
|
-
const normalizedTimeoutMs = timeoutMs ?? 300000;
|
|
16
|
-
if (!Number.isFinite(normalizedTimeoutMs) || normalizedTimeoutMs <= 0) {
|
|
17
|
-
throw new CliCommandError('--timeout-ms must be a positive number.', 2);
|
|
18
|
-
}
|
|
19
|
-
return normalizedTimeoutMs;
|
|
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
|
-
};
|
|
27
|
-
export const registerJobCommands = (jobYargs, context) => {
|
|
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
|
-
})
|
|
110
|
-
.command('get <jobId>', 'Fetch a single job', (getYargs) => getYargs
|
|
111
|
-
.positional('jobId', {
|
|
112
|
-
type: 'string',
|
|
113
|
-
describe: 'Job ID to fetch',
|
|
114
|
-
})
|
|
115
|
-
.example('chalksurf job get job_123', 'Read one job in human-readable mode')
|
|
116
|
-
.example('chalksurf job get job_123 --json', 'Read one job in machine-readable mode'), async (argv) => {
|
|
117
|
-
const { apiClient } = await createResolvedApiClient({
|
|
118
|
-
context,
|
|
119
|
-
baseUrlFlagValue: argv.baseUrl,
|
|
120
|
-
organizationFlagValue: argv.organization,
|
|
121
|
-
profileName: argv.profile,
|
|
122
|
-
});
|
|
123
|
-
let job;
|
|
124
|
-
try {
|
|
125
|
-
job = await apiClient.getUserJob(String(argv.jobId));
|
|
126
|
-
}
|
|
127
|
-
catch (error) {
|
|
128
|
-
throw mapApiErrorToCliError(error);
|
|
129
|
-
}
|
|
130
|
-
const serializedJob = serializeCliJob(job);
|
|
131
|
-
context.output.print({ job: serializedJob }, (result) => formatCliJobSummary(result.job), {
|
|
132
|
-
command: 'job get',
|
|
133
|
-
});
|
|
134
|
-
})
|
|
135
|
-
.command('wait <jobIds..>', 'Poll jobs until they complete, fail, or time out', (waitYargs) => waitYargs
|
|
136
|
-
.positional('jobIds', {
|
|
137
|
-
array: true,
|
|
138
|
-
type: 'string',
|
|
139
|
-
describe: 'One or more job IDs to wait for',
|
|
140
|
-
})
|
|
141
|
-
.option('timeout-ms', {
|
|
142
|
-
type: 'number',
|
|
143
|
-
default: 300000,
|
|
144
|
-
describe: 'Maximum time to wait before timing out',
|
|
145
|
-
})
|
|
146
|
-
.example('chalksurf job wait job_123 job_124', 'Wait for one or more jobs to reach a terminal state')
|
|
147
|
-
.example('chalksurf job wait job_123 --timeout-ms 60000 --json', 'Poll a job for up to 60 seconds'), async (argv) => {
|
|
148
|
-
const { apiClient } = await createResolvedApiClient({
|
|
149
|
-
context,
|
|
150
|
-
baseUrlFlagValue: argv.baseUrl,
|
|
151
|
-
organizationFlagValue: argv.organization,
|
|
152
|
-
profileName: argv.profile,
|
|
153
|
-
});
|
|
154
|
-
const timeoutMs = normalizeTimeoutMs(argv.timeoutMs);
|
|
155
|
-
const waitResult = await waitForCliJobs({
|
|
156
|
-
getUserJob: async (jobId) => await apiClient.getUserJob(jobId),
|
|
157
|
-
jobIds: (argv.jobIds ?? []).map(String),
|
|
158
|
-
now: context.now,
|
|
159
|
-
sleep: context.sleep,
|
|
160
|
-
timeoutMs,
|
|
161
|
-
});
|
|
162
|
-
const waitError = getWaitError(waitResult);
|
|
163
|
-
context.output.print(waitResult, (result) => result.jobs.map((job) => formatCliJobSummary(job)).join('\n'), {
|
|
164
|
-
command: 'job wait',
|
|
165
|
-
ok: waitError == null,
|
|
166
|
-
error: waitError,
|
|
167
|
-
});
|
|
168
|
-
throwSilentExitCode(getWaitExitCode(waitResult));
|
|
169
|
-
})
|
|
170
|
-
.demandCommand(1)
|
|
171
|
-
.strict();
|
|
172
|
-
};
|
package/dist/commands/org.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { createApiClient } from '../lib/api-client.js';
|
|
2
|
-
import { CliCommandError } from '../lib/cli-error.js';
|
|
3
|
-
import { resolveOrganization } from '../lib/config-store.js';
|
|
4
|
-
import { mapApiErrorToCliError, requireResolvedBaseUrl, requireResolvedToken } from '../lib/session.js';
|
|
5
|
-
const serializeOrganization = ({ organizationId, organizationName, organizationType, role, selected, }) => {
|
|
6
|
-
return {
|
|
7
|
-
id: organizationId,
|
|
8
|
-
name: organizationName,
|
|
9
|
-
role,
|
|
10
|
-
selected,
|
|
11
|
-
type: organizationType,
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
export const registerOrgCommands = (orgYargs, context) => {
|
|
15
|
-
return orgYargs
|
|
16
|
-
.command('list', 'List organizations available to the authenticated user', (listYargs) => listYargs
|
|
17
|
-
.example('chalksurf org list', 'List organizations for the default profile')
|
|
18
|
-
.example('chalksurf --profile prod-codex org list --json', 'Inspect organizations for a specific profile'), async (argv) => {
|
|
19
|
-
const config = await context.configStore.loadProfile({ profileName: argv.profile });
|
|
20
|
-
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
21
|
-
flagValue: argv.baseUrl,
|
|
22
|
-
env: context.env,
|
|
23
|
-
config,
|
|
24
|
-
});
|
|
25
|
-
const resolvedToken = requireResolvedToken({
|
|
26
|
-
env: context.env,
|
|
27
|
-
config,
|
|
28
|
-
});
|
|
29
|
-
const apiClient = createApiClient({
|
|
30
|
-
baseUrl: resolvedBaseUrl.value,
|
|
31
|
-
token: resolvedToken.value,
|
|
32
|
-
});
|
|
33
|
-
let profile;
|
|
34
|
-
try {
|
|
35
|
-
profile = await apiClient.getUserProfile();
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
throw mapApiErrorToCliError(error);
|
|
39
|
-
}
|
|
40
|
-
const resolvedOrganization = resolveOrganization({
|
|
41
|
-
flagValue: argv.organization,
|
|
42
|
-
env: context.env,
|
|
43
|
-
config,
|
|
44
|
-
profile,
|
|
45
|
-
});
|
|
46
|
-
resolvedOrganization.warnings.forEach((warning) => {
|
|
47
|
-
context.output.info(warning);
|
|
48
|
-
});
|
|
49
|
-
const organizations = profile.organizationMemberships.map((membership) => serializeOrganization({
|
|
50
|
-
...membership,
|
|
51
|
-
selected: membership.organizationId === resolvedOrganization.organization?.organizationId,
|
|
52
|
-
}));
|
|
53
|
-
context.output.print({
|
|
54
|
-
organizations,
|
|
55
|
-
profile: config.profileName,
|
|
56
|
-
profileSource: config.profileSource,
|
|
57
|
-
selectedOrganizationId: resolvedOrganization.organization?.organizationId ?? null,
|
|
58
|
-
selectedOrganizationSource: resolvedOrganization.source,
|
|
59
|
-
}, (result) => result.organizations
|
|
60
|
-
.map((organization) => `${organization.selected ? '*' : ' '} ${organization.name} (${organization.id}) [${organization.type}, ${organization.role}]`)
|
|
61
|
-
.join('\n'), {
|
|
62
|
-
command: 'org list',
|
|
63
|
-
});
|
|
64
|
-
})
|
|
65
|
-
.command('use <organizationId>', 'Set the default organization used by the CLI', (useYargs) => {
|
|
66
|
-
return useYargs
|
|
67
|
-
.positional('organizationId', {
|
|
68
|
-
type: 'string',
|
|
69
|
-
describe: 'Organization ID to store as the default',
|
|
70
|
-
})
|
|
71
|
-
.example('chalksurf --profile prod-codex org use org_123', 'Store org_123 in the prod-codex profile');
|
|
72
|
-
}, async (argv) => {
|
|
73
|
-
const config = await context.configStore.loadProfile({ profileName: argv.profile });
|
|
74
|
-
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
75
|
-
flagValue: argv.baseUrl,
|
|
76
|
-
env: context.env,
|
|
77
|
-
config,
|
|
78
|
-
});
|
|
79
|
-
const resolvedToken = requireResolvedToken({
|
|
80
|
-
env: context.env,
|
|
81
|
-
config,
|
|
82
|
-
});
|
|
83
|
-
const apiClient = createApiClient({
|
|
84
|
-
baseUrl: resolvedBaseUrl.value,
|
|
85
|
-
token: resolvedToken.value,
|
|
86
|
-
});
|
|
87
|
-
let profile;
|
|
88
|
-
try {
|
|
89
|
-
profile = await apiClient.getUserProfile();
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
throw mapApiErrorToCliError(error);
|
|
93
|
-
}
|
|
94
|
-
const organization = profile.organizationMemberships.find((membership) => membership.organizationId === String(argv.organizationId));
|
|
95
|
-
if (!organization) {
|
|
96
|
-
throw new CliCommandError(`Organization "${String(argv.organizationId)}" is not accessible to the current user.`, 2);
|
|
97
|
-
}
|
|
98
|
-
await context.configStore.update({ profileName: argv.profile }, (currentConfig) => ({
|
|
99
|
-
...currentConfig,
|
|
100
|
-
organizationId: organization.organizationId,
|
|
101
|
-
}));
|
|
102
|
-
context.output.print({
|
|
103
|
-
organization: serializeOrganization({
|
|
104
|
-
...organization,
|
|
105
|
-
selected: true,
|
|
106
|
-
}),
|
|
107
|
-
profile: config.profileName,
|
|
108
|
-
profileSource: config.profileSource,
|
|
109
|
-
}, (result) => `Default organization set to ${result.organization.name} (${result.organization.id}).`, {
|
|
110
|
-
command: 'org use',
|
|
111
|
-
});
|
|
112
|
-
})
|
|
113
|
-
.demandCommand(1)
|
|
114
|
-
.strict();
|
|
115
|
-
};
|
package/dist/commands/profile.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { CliCommandError } from '../lib/cli-error.js';
|
|
2
|
-
import { assertValidProfileName, resolveProfile } from '../lib/config-store.js';
|
|
3
|
-
const serializeProfile = ({ profileName, profileConfig, selected, }) => {
|
|
4
|
-
return {
|
|
5
|
-
baseUrl: profileConfig.baseUrl ?? null,
|
|
6
|
-
hasToken: Boolean(profileConfig.token),
|
|
7
|
-
name: profileName,
|
|
8
|
-
organizationId: profileConfig.organizationId ?? null,
|
|
9
|
-
selected,
|
|
10
|
-
};
|
|
11
|
-
};
|
|
12
|
-
const formatProfile = (profile) => {
|
|
13
|
-
const marker = profile.selected ? '*' : ' ';
|
|
14
|
-
const baseUrl = profile.baseUrl ?? 'no base URL';
|
|
15
|
-
const token = profile.hasToken ? 'token' : 'no token';
|
|
16
|
-
const organization = profile.organizationId ? `org ${profile.organizationId}` : 'no org';
|
|
17
|
-
return `${marker} ${profile.name} (${baseUrl}, ${token}, ${organization})`;
|
|
18
|
-
};
|
|
19
|
-
export const registerProfileCommands = (profileYargs, context) => {
|
|
20
|
-
return profileYargs
|
|
21
|
-
.command('list', 'List locally stored CLI profiles', (listYargs) => listYargs
|
|
22
|
-
.example('chalksurf profile list', 'List locally stored profiles')
|
|
23
|
-
.example('chalksurf --profile prod-codex profile list --json', 'Show the active profile in JSON mode'), async (argv) => {
|
|
24
|
-
const rootConfig = await context.configStore.loadRoot();
|
|
25
|
-
let activeProfile = null;
|
|
26
|
-
try {
|
|
27
|
-
activeProfile =
|
|
28
|
-
Object.keys(rootConfig.profiles).length === 0
|
|
29
|
-
? null
|
|
30
|
-
: resolveProfile({
|
|
31
|
-
flagValue: argv.profile,
|
|
32
|
-
env: context.env,
|
|
33
|
-
config: rootConfig,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
if (!(error instanceof CliCommandError) || !error.message.startsWith('No active ChalkSurf profile')) {
|
|
38
|
-
throw error;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const profiles = Object.entries(rootConfig.profiles).map(([profileName, profileConfig]) => serializeProfile({
|
|
42
|
-
profileName,
|
|
43
|
-
profileConfig,
|
|
44
|
-
selected: profileName === activeProfile?.profileName,
|
|
45
|
-
}));
|
|
46
|
-
context.output.print({
|
|
47
|
-
activeProfile: activeProfile?.profileName ?? null,
|
|
48
|
-
activeProfileSource: activeProfile?.profileSource ?? null,
|
|
49
|
-
defaultProfile: rootConfig.defaultProfile ?? null,
|
|
50
|
-
profiles,
|
|
51
|
-
}, (result) => result.profiles.length === 0 ? 'No profiles configured.' : result.profiles.map(formatProfile).join('\n'), {
|
|
52
|
-
command: 'profile list',
|
|
53
|
-
});
|
|
54
|
-
})
|
|
55
|
-
.command('use <profileName>', 'Set the default profile used by the CLI', (useYargs) => useYargs
|
|
56
|
-
.positional('profileName', {
|
|
57
|
-
type: 'string',
|
|
58
|
-
describe: 'Profile name to make the default',
|
|
59
|
-
})
|
|
60
|
-
.example('chalksurf profile use prod-cztamas', 'Use prod-cztamas by default for future commands'), async (argv) => {
|
|
61
|
-
const profileName = String(argv.profileName ?? '');
|
|
62
|
-
assertValidProfileName(profileName);
|
|
63
|
-
const rootConfig = await context.configStore.setDefaultProfile(profileName);
|
|
64
|
-
const profileConfig = rootConfig.profiles[profileName];
|
|
65
|
-
if (!profileConfig) {
|
|
66
|
-
throw new CliCommandError(`Profile "${profileName}" does not exist.`, 2);
|
|
67
|
-
}
|
|
68
|
-
context.output.print({
|
|
69
|
-
defaultProfile: profileName,
|
|
70
|
-
profile: serializeProfile({
|
|
71
|
-
profileName,
|
|
72
|
-
profileConfig,
|
|
73
|
-
selected: true,
|
|
74
|
-
}),
|
|
75
|
-
}, (result) => `Default profile set to ${result.defaultProfile}.`, {
|
|
76
|
-
command: 'profile use',
|
|
77
|
-
});
|
|
78
|
-
})
|
|
79
|
-
.command('delete <profileName>', 'Delete a locally stored CLI profile', (deleteYargs) => deleteYargs
|
|
80
|
-
.positional('profileName', {
|
|
81
|
-
type: 'string',
|
|
82
|
-
describe: 'Profile name to delete',
|
|
83
|
-
})
|
|
84
|
-
.example('chalksurf profile delete prod-codex', 'Delete the prod-codex profile'), async (argv) => {
|
|
85
|
-
const profileName = String(argv.profileName ?? '');
|
|
86
|
-
assertValidProfileName(profileName);
|
|
87
|
-
const rootConfig = await context.configStore.deleteProfile(profileName);
|
|
88
|
-
context.output.print({
|
|
89
|
-
deletedProfile: profileName,
|
|
90
|
-
defaultProfile: rootConfig.defaultProfile ?? null,
|
|
91
|
-
}, (result) => `Deleted profile ${result.deletedProfile}.`, {
|
|
92
|
-
command: 'profile delete',
|
|
93
|
-
});
|
|
94
|
-
})
|
|
95
|
-
.demandCommand(1)
|
|
96
|
-
.strict();
|
|
97
|
-
};
|