@chalksurf/cli 0.2.0 → 0.2.1
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 +17 -14
- package/dist/bin/chalksurf.js +8 -1
- package/dist/commands/auth.js +19 -12
- package/dist/commands/exercise.js +4 -4
- package/dist/commands/job.js +2 -2
- package/dist/commands/org.js +10 -6
- package/dist/commands/profile.js +97 -0
- package/dist/commands/sheet.js +2 -2
- package/dist/lib/config-store.js +188 -10
- package/docs/agents.md +19 -11
- package/docs/manual.md +26 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Publishable ChalkSurf CLI package.
|
|
4
4
|
|
|
5
|
-
Current internal release line: `0.1
|
|
5
|
+
Current internal release line: `0.2.1`. Expect breaking changes while the CLI is still only used internally.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
Run the published CLI without a global install:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx @chalksurf/cli@0.1
|
|
12
|
+
npx @chalksurf/cli@0.2.1 --help
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Install it globally when you want a persistent local binary:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install -g @chalksurf/cli@0.1
|
|
18
|
+
npm install -g @chalksurf/cli@0.2.1
|
|
19
19
|
chalksurf --version
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -24,7 +24,8 @@ chalksurf --version
|
|
|
24
24
|
Interactive operator flow:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
chalksurf auth login --base-url https://api.
|
|
27
|
+
chalksurf auth login --profile prod-cztamas --base-url https://chalksurf-api.fly.dev
|
|
28
|
+
chalksurf profile use prod-cztamas
|
|
28
29
|
chalksurf org list
|
|
29
30
|
chalksurf sheet import ./fixtures/algebra.pdf --wait
|
|
30
31
|
```
|
|
@@ -32,10 +33,13 @@ chalksurf sheet import ./fixtures/algebra.pdf --wait
|
|
|
32
33
|
Headless or agent flow:
|
|
33
34
|
|
|
34
35
|
```bash
|
|
35
|
-
CHALKSURF_BASE_URL=https://api.chalksurf.com \
|
|
36
36
|
CHALKSURF_TOKEN=cs_cli_... \
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
printf '%s' "$CHALKSURF_TOKEN" | npx @chalksurf/cli@0.2.1 auth login \
|
|
38
|
+
--profile prod-codex \
|
|
39
|
+
--base-url https://chalksurf-api.fly.dev \
|
|
40
|
+
--with-token
|
|
41
|
+
|
|
42
|
+
npx @chalksurf/cli@0.2.1 --profile prod-codex sheet import --manifest - --wait --json < import.json
|
|
39
43
|
```
|
|
40
44
|
|
|
41
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).
|
|
@@ -52,18 +56,17 @@ Sheet import manifests use top-level `sheets[]`, where each sheet has one `targe
|
|
|
52
56
|
|
|
53
57
|
## Local And Staging Testing
|
|
54
58
|
|
|
55
|
-
Use separate
|
|
59
|
+
Use separate profiles so local, staging, production, human, and agent tokens do not overwrite each other:
|
|
56
60
|
|
|
57
61
|
```bash
|
|
58
|
-
|
|
59
|
-
npm run cli-dev --
|
|
62
|
+
npm run cli-dev -- auth login --profile dev-cztamas --base-url http://localhost:8080
|
|
63
|
+
npm run cli-dev -- profile use dev-cztamas
|
|
60
64
|
npm run cli-dev -- auth status --json
|
|
61
65
|
npm run cli-dev -- sheet import ./fixtures/algebra.pdf --wait --json
|
|
62
66
|
```
|
|
63
67
|
|
|
64
68
|
```bash
|
|
65
|
-
|
|
66
|
-
npm run cli-dev -- auth
|
|
67
|
-
npm run cli-dev --
|
|
68
|
-
npm run cli-dev -- sheet import https://example.com/worksheet.docx --single-sheet --target-folder Imported --json
|
|
69
|
+
npm run cli-dev -- auth login --profile staging-codex --base-url https://chalksurf-api-staging.fly.dev
|
|
70
|
+
npm run cli-dev -- --profile staging-codex auth status --json
|
|
71
|
+
npm run cli-dev -- --profile staging-codex sheet import https://example.com/worksheet.docx --single-sheet --target-folder Imported --json
|
|
69
72
|
```
|
package/dist/bin/chalksurf.js
CHANGED
|
@@ -7,6 +7,7 @@ import { registerAuthCommands } from '../commands/auth.js';
|
|
|
7
7
|
import { registerExerciseCommands } from '../commands/exercise.js';
|
|
8
8
|
import { registerJobCommands } from '../commands/job.js';
|
|
9
9
|
import { registerOrgCommands } from '../commands/org.js';
|
|
10
|
+
import { registerProfileCommands } from '../commands/profile.js';
|
|
10
11
|
import { registerSheetCommands } from '../commands/sheet.js';
|
|
11
12
|
import { CliCommandError, createSerializableCliError, serializeCliError } from '../lib/cli-error.js';
|
|
12
13
|
import { createConfigStore } from '../lib/config-store.js';
|
|
@@ -42,6 +43,11 @@ const createCli = ({ cwd, configPath, env, now, output, sleep, stdin, promptSecr
|
|
|
42
43
|
type: 'string',
|
|
43
44
|
global: true,
|
|
44
45
|
describe: 'Organization ID to use for this command',
|
|
46
|
+
})
|
|
47
|
+
.option('profile', {
|
|
48
|
+
type: 'string',
|
|
49
|
+
global: true,
|
|
50
|
+
describe: 'CLI profile to use for this command',
|
|
45
51
|
})
|
|
46
52
|
.option('version', {
|
|
47
53
|
type: 'boolean',
|
|
@@ -50,6 +56,7 @@ const createCli = ({ cwd, configPath, env, now, output, sleep, stdin, promptSecr
|
|
|
50
56
|
})
|
|
51
57
|
.command('auth <subcommand>', 'Authentication commands', (authYargs) => registerAuthCommands(authYargs, commandContext), () => { })
|
|
52
58
|
.command('org <subcommand>', 'Organization commands', (orgYargs) => registerOrgCommands(orgYargs, commandContext), () => { })
|
|
59
|
+
.command('profile <subcommand>', 'Profile commands', (profileYargs) => registerProfileCommands(profileYargs, commandContext), () => { })
|
|
53
60
|
.command('exercise <subcommand>', 'Exercise commands', (exerciseYargs) => registerExerciseCommands(exerciseYargs, commandContext), () => { })
|
|
54
61
|
.command('sheet <subcommand>', 'Exercise sheet commands', (sheetYargs) => registerSheetCommands(sheetYargs, commandContext), () => { })
|
|
55
62
|
.command('job <subcommand>', 'Job commands', (jobYargs) => registerJobCommands(jobYargs, commandContext), () => { })
|
|
@@ -74,7 +81,7 @@ const parseCliWithCapturedOutput = async ({ cli, argv, stdout, }) => {
|
|
|
74
81
|
const hasFlag = (argv, flags) => {
|
|
75
82
|
return argv.some((argument) => flags.includes(argument));
|
|
76
83
|
};
|
|
77
|
-
const topLevelCommands = new Set(['auth', 'org', 'exercise', 'sheet', 'job']);
|
|
84
|
+
const topLevelCommands = new Set(['auth', 'org', 'profile', 'exercise', 'sheet', 'job']);
|
|
78
85
|
const hasTopLevelCommand = (argv) => {
|
|
79
86
|
return argv.some((argument) => topLevelCommands.has(argument));
|
|
80
87
|
};
|
package/dist/commands/auth.js
CHANGED
|
@@ -38,10 +38,10 @@ export const registerAuthCommands = (authYargs, context) => {
|
|
|
38
38
|
default: false,
|
|
39
39
|
describe: 'Read the token from stdin when piping input',
|
|
40
40
|
})
|
|
41
|
-
.example('chalksurf auth login --base-url https://api.
|
|
42
|
-
.example(`printf '%s' "$CHALKSURF_TOKEN" | chalksurf auth login --base-url https://api.
|
|
41
|
+
.example('chalksurf auth login --profile prod-cztamas --base-url https://chalksurf-api.fly.dev', 'Prompt for a CLI token and store it in the prod-cztamas profile')
|
|
42
|
+
.example(`printf '%s' "$CHALKSURF_TOKEN" | chalksurf auth login --profile prod-codex --base-url https://chalksurf-api.fly.dev`, 'Read a CLI token from stdin into an explicit profile');
|
|
43
43
|
}, async (argv) => {
|
|
44
|
-
const config = await context.configStore.
|
|
44
|
+
const config = await context.configStore.loadProfile({ profileName: argv.profile });
|
|
45
45
|
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
46
46
|
flagValue: argv.baseUrl,
|
|
47
47
|
env: context.env,
|
|
@@ -70,7 +70,7 @@ export const registerAuthCommands = (authYargs, context) => {
|
|
|
70
70
|
config,
|
|
71
71
|
profile,
|
|
72
72
|
});
|
|
73
|
-
await context.configStore.update((currentConfig) => ({
|
|
73
|
+
await context.configStore.update({ profileName: argv.profile }, (currentConfig) => ({
|
|
74
74
|
...currentConfig,
|
|
75
75
|
token,
|
|
76
76
|
baseUrl: resolvedBaseUrl.value,
|
|
@@ -84,6 +84,8 @@ export const registerAuthCommands = (authYargs, context) => {
|
|
|
84
84
|
baseUrl: resolvedBaseUrl.value,
|
|
85
85
|
baseUrlSource: resolvedBaseUrl.source,
|
|
86
86
|
organization: serializeOrganization(resolvedOrganization.organization),
|
|
87
|
+
profile: config.profileName,
|
|
88
|
+
profileSource: config.profileSource,
|
|
87
89
|
user: {
|
|
88
90
|
email: profile.email,
|
|
89
91
|
id: profile.id,
|
|
@@ -95,14 +97,15 @@ export const registerAuthCommands = (authYargs, context) => {
|
|
|
95
97
|
})
|
|
96
98
|
.command('status', 'Show the current authentication and organization state', (statusYargs) => statusYargs
|
|
97
99
|
.example('chalksurf auth status', 'Show the current authenticated user and default organization')
|
|
98
|
-
.example('
|
|
100
|
+
.example('chalksurf --profile prod-codex auth status --json', 'Inspect a specific profile in machine-readable mode')
|
|
99
101
|
.epilogue([
|
|
100
102
|
'Resolution order:',
|
|
101
|
-
'
|
|
102
|
-
'
|
|
103
|
-
'
|
|
103
|
+
' profile: --profile, then CHALKSURF_PROFILE, then stored default profile',
|
|
104
|
+
' base URL: --base-url, then CHALKSURF_BASE_URL, then active profile',
|
|
105
|
+
' token: CHALKSURF_TOKEN, then active profile',
|
|
106
|
+
' organization: --organization, then CHALKSURF_ORGANIZATION_ID, then active profile',
|
|
104
107
|
].join('\n')), async (argv) => {
|
|
105
|
-
const config = await context.configStore.
|
|
108
|
+
const config = await context.configStore.loadProfile({ profileName: argv.profile });
|
|
106
109
|
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
107
110
|
flagValue: argv.baseUrl,
|
|
108
111
|
env: context.env,
|
|
@@ -138,6 +141,8 @@ export const registerAuthCommands = (authYargs, context) => {
|
|
|
138
141
|
baseUrlSource: resolvedBaseUrl.source,
|
|
139
142
|
organization: serializeOrganization(resolvedOrganization.organization),
|
|
140
143
|
organizationSource: resolvedOrganization.source,
|
|
144
|
+
profile: config.profileName,
|
|
145
|
+
profileSource: config.profileSource,
|
|
141
146
|
tokenSource: resolvedToken.source,
|
|
142
147
|
user: {
|
|
143
148
|
email: profile.email,
|
|
@@ -148,16 +153,18 @@ export const registerAuthCommands = (authYargs, context) => {
|
|
|
148
153
|
command: 'auth status',
|
|
149
154
|
});
|
|
150
155
|
})
|
|
151
|
-
.command('logout', 'Remove the locally stored CLI token', (logoutYargs) => logoutYargs.example('chalksurf auth logout', 'Remove the stored CLI token from local config'), async () => {
|
|
152
|
-
const config = await context.configStore.
|
|
156
|
+
.command('logout', 'Remove the locally stored CLI token', (logoutYargs) => logoutYargs.example('chalksurf auth logout', 'Remove the stored CLI token from local config'), async (argv) => {
|
|
157
|
+
const config = await context.configStore.loadProfile({ profileName: argv.profile });
|
|
153
158
|
const hadStoredToken = Boolean(config.token);
|
|
154
|
-
await context.configStore.update((currentConfig) => ({
|
|
159
|
+
await context.configStore.update({ profileName: argv.profile }, (currentConfig) => ({
|
|
155
160
|
...currentConfig,
|
|
156
161
|
token: undefined,
|
|
157
162
|
}));
|
|
158
163
|
context.output.print({
|
|
159
164
|
clearedStoredToken: hadStoredToken,
|
|
160
165
|
loggedOut: true,
|
|
166
|
+
profile: config.profileName,
|
|
167
|
+
profileSource: config.profileSource,
|
|
161
168
|
}, hadStoredToken ? 'Stored CLI token removed.' : 'No stored CLI token was present.', {
|
|
162
169
|
command: 'auth logout',
|
|
163
170
|
});
|
|
@@ -110,9 +110,9 @@ export const registerExerciseCommands = (exerciseYargs, context) => {
|
|
|
110
110
|
describe: 'Wait for the queued job to finish',
|
|
111
111
|
})
|
|
112
112
|
.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
|
-
.example('cat exercise-import.json | chalksurf exercise import --manifest - --wait --json', 'Drive imports from a manifest in an agent or CI flow')
|
|
113
|
+
.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
114
|
.epilogue('When --wait is set, the command exits after the queued import job reaches a terminal state.'), async (argv) => {
|
|
115
|
-
const config = await context.configStore.
|
|
115
|
+
const config = await context.configStore.loadProfile({ profileName: argv.profile });
|
|
116
116
|
const rawSources = (argv.sources ?? []).map(String);
|
|
117
117
|
const manifestPath = argv.manifest === '' ? '-' : argv.manifest;
|
|
118
118
|
const normalizedRawSources = manifestPath === '-' && rawSources[0] === '-' ? rawSources.slice(1) : rawSources;
|
|
@@ -283,9 +283,9 @@ export const registerExerciseCommands = (exerciseYargs, context) => {
|
|
|
283
283
|
describe: 'Wait for the queued job to finish',
|
|
284
284
|
})
|
|
285
285
|
.example('chalksurf exercise import-solution 00000000-0000-4000-8000-000000000001 ./fixtures/solution.pdf --wait', 'Attach a solution file to an existing exercise')
|
|
286
|
-
.example('cat exercise-solution-import.json | chalksurf exercise import-solution --manifest - --wait --json', 'Drive solution imports from a manifest in an agent or CI flow')
|
|
286
|
+
.example('cat exercise-solution-import.json | chalksurf --profile prod-codex exercise import-solution --manifest - --wait --json', 'Drive solution imports from a manifest in an agent or CI flow')
|
|
287
287
|
.epilogue('When --wait is set, the command exits after the queued import job reaches a terminal state.'), async (argv) => {
|
|
288
|
-
const config = await context.configStore.
|
|
288
|
+
const config = await context.configStore.loadProfile({ profileName: argv.profile });
|
|
289
289
|
const rawSources = (argv.sources ?? []).map(String);
|
|
290
290
|
const manifestPath = argv.manifest === '' ? '-' : argv.manifest;
|
|
291
291
|
const normalizedRawSources = manifestPath === '-' && rawSources[0] === '-' ? rawSources.slice(1) : rawSources;
|
package/dist/commands/job.js
CHANGED
|
@@ -18,7 +18,7 @@ export const registerJobCommands = (jobYargs, context) => {
|
|
|
18
18
|
})
|
|
19
19
|
.example('chalksurf job get job_123', 'Read one job in human-readable mode')
|
|
20
20
|
.example('chalksurf job get job_123 --json', 'Read one job in machine-readable mode'), async (argv) => {
|
|
21
|
-
const config = await context.configStore.
|
|
21
|
+
const config = await context.configStore.loadProfile({ profileName: argv.profile });
|
|
22
22
|
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
23
23
|
flagValue: argv.baseUrl,
|
|
24
24
|
env: context.env,
|
|
@@ -63,7 +63,7 @@ export const registerJobCommands = (jobYargs, context) => {
|
|
|
63
63
|
})
|
|
64
64
|
.example('chalksurf job wait job_123 job_124', 'Wait for one or more jobs to reach a terminal state')
|
|
65
65
|
.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.
|
|
66
|
+
const config = await context.configStore.loadProfile({ profileName: argv.profile });
|
|
67
67
|
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
68
68
|
flagValue: argv.baseUrl,
|
|
69
69
|
env: context.env,
|
package/dist/commands/org.js
CHANGED
|
@@ -14,9 +14,9 @@ const serializeOrganization = ({ organizationId, organizationName, organizationT
|
|
|
14
14
|
export const registerOrgCommands = (orgYargs, context) => {
|
|
15
15
|
return orgYargs
|
|
16
16
|
.command('list', 'List organizations available to the authenticated user', (listYargs) => listYargs
|
|
17
|
-
.example('chalksurf org list', 'List organizations for the
|
|
18
|
-
.example('chalksurf org list --json', 'Inspect
|
|
19
|
-
const config = await context.configStore.
|
|
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
20
|
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
21
21
|
flagValue: argv.baseUrl,
|
|
22
22
|
env: context.env,
|
|
@@ -52,6 +52,8 @@ export const registerOrgCommands = (orgYargs, context) => {
|
|
|
52
52
|
}));
|
|
53
53
|
context.output.print({
|
|
54
54
|
organizations,
|
|
55
|
+
profile: config.profileName,
|
|
56
|
+
profileSource: config.profileSource,
|
|
55
57
|
selectedOrganizationId: resolvedOrganization.organization?.organizationId ?? null,
|
|
56
58
|
selectedOrganizationSource: resolvedOrganization.source,
|
|
57
59
|
}, (result) => result.organizations
|
|
@@ -66,9 +68,9 @@ export const registerOrgCommands = (orgYargs, context) => {
|
|
|
66
68
|
type: 'string',
|
|
67
69
|
describe: 'Organization ID to store as the default',
|
|
68
70
|
})
|
|
69
|
-
.example('chalksurf org use org_123', 'Store org_123
|
|
71
|
+
.example('chalksurf --profile prod-codex org use org_123', 'Store org_123 in the prod-codex profile');
|
|
70
72
|
}, async (argv) => {
|
|
71
|
-
const config = await context.configStore.
|
|
73
|
+
const config = await context.configStore.loadProfile({ profileName: argv.profile });
|
|
72
74
|
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
73
75
|
flagValue: argv.baseUrl,
|
|
74
76
|
env: context.env,
|
|
@@ -93,7 +95,7 @@ export const registerOrgCommands = (orgYargs, context) => {
|
|
|
93
95
|
if (!organization) {
|
|
94
96
|
throw new CliCommandError(`Organization "${String(argv.organizationId)}" is not accessible to the current user.`, 2);
|
|
95
97
|
}
|
|
96
|
-
await context.configStore.update((currentConfig) => ({
|
|
98
|
+
await context.configStore.update({ profileName: argv.profile }, (currentConfig) => ({
|
|
97
99
|
...currentConfig,
|
|
98
100
|
organizationId: organization.organizationId,
|
|
99
101
|
}));
|
|
@@ -102,6 +104,8 @@ export const registerOrgCommands = (orgYargs, context) => {
|
|
|
102
104
|
...organization,
|
|
103
105
|
selected: true,
|
|
104
106
|
}),
|
|
107
|
+
profile: config.profileName,
|
|
108
|
+
profileSource: config.profileSource,
|
|
105
109
|
}, (result) => `Default organization set to ${result.organization.name} (${result.organization.id}).`, {
|
|
106
110
|
command: 'org use',
|
|
107
111
|
});
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
};
|
package/dist/commands/sheet.js
CHANGED
|
@@ -271,12 +271,12 @@ export const registerSheetCommands = (sheetYargs, context) => {
|
|
|
271
271
|
})
|
|
272
272
|
.example('chalksurf sheet import ./fixtures/algebra.pdf --title "OKTV 2014 Round 1" --translate-to english --wait', 'Import a sheet, override its title, and request an English translation')
|
|
273
273
|
.example('chalksurf sheet import ./fixtures/round-1-a.pdf ./fixtures/round-1-b.pdf --single-sheet --target-folder Contests --wait', 'Import multiple source files into one sheet and place the result into a target folder')
|
|
274
|
-
.example('cat sheet-import.json | chalksurf sheet import --manifest - --wait --json', 'Drive multi-source sheet imports from a manifest in an agent or CI flow')
|
|
274
|
+
.example('cat sheet-import.json | chalksurf --profile prod-codex sheet import --manifest - --wait --json', 'Drive multi-source sheet imports from a manifest in an agent or CI flow')
|
|
275
275
|
.epilogue([
|
|
276
276
|
'When --wait is set, the command exits only after the full requested import is complete.',
|
|
277
277
|
'For translated sheet imports, that includes generating and storing the requested translations.',
|
|
278
278
|
].join('\n')), async (argv) => {
|
|
279
|
-
const config = await context.configStore.
|
|
279
|
+
const config = await context.configStore.loadProfile({ profileName: argv.profile });
|
|
280
280
|
const rawSources = (argv.sources ?? []).map(String);
|
|
281
281
|
const manifestPath = argv.manifest === '' ? '-' : argv.manifest;
|
|
282
282
|
const normalizedRawSources = manifestPath === '-' && rawSources[0] === '-' ? rawSources.slice(1) : rawSources;
|
package/dist/lib/config-store.js
CHANGED
|
@@ -5,7 +5,11 @@ import { CliCommandError } from './cli-error.js';
|
|
|
5
5
|
export const chalksurfBaseUrlEnvVar = 'CHALKSURF_BASE_URL';
|
|
6
6
|
export const chalksurfTokenEnvVar = 'CHALKSURF_TOKEN';
|
|
7
7
|
export const chalksurfOrganizationIdEnvVar = 'CHALKSURF_ORGANIZATION_ID';
|
|
8
|
+
export const chalksurfProfileEnvVar = 'CHALKSURF_PROFILE';
|
|
8
9
|
export const chalksurfConfigPathEnvVar = 'CHALKSURF_CONFIG_PATH';
|
|
10
|
+
export const cliConfigSchemaVersion = 2;
|
|
11
|
+
export const implicitDefaultProfileName = 'default';
|
|
12
|
+
const profileNamePattern = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
9
13
|
const normalizeOptionalString = (value) => {
|
|
10
14
|
const trimmedValue = value?.trim();
|
|
11
15
|
return trimmedValue ? trimmedValue : undefined;
|
|
@@ -14,13 +18,91 @@ const normalizeStoredBaseUrl = (value) => {
|
|
|
14
18
|
const normalizedValue = normalizeOptionalString(value);
|
|
15
19
|
return normalizedValue?.replace(/\/+$/, '') || undefined;
|
|
16
20
|
};
|
|
17
|
-
const
|
|
21
|
+
const isRecord = (value) => {
|
|
22
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
23
|
+
};
|
|
24
|
+
export const assertValidProfileName = (profileName) => {
|
|
25
|
+
if (!profileNamePattern.test(profileName)) {
|
|
26
|
+
throw new CliCommandError(`Invalid profile "${profileName}". Profile names must start with a letter or number and contain only letters, numbers, ".", "_", or "-".`, 2);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const normalizeProfileName = (value) => {
|
|
30
|
+
const profileName = normalizeOptionalString(value);
|
|
31
|
+
if (!profileName) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
assertValidProfileName(profileName);
|
|
35
|
+
return profileName;
|
|
36
|
+
};
|
|
37
|
+
const sanitizeProfileConfig = (config) => {
|
|
18
38
|
return Object.fromEntries(Object.entries({
|
|
19
39
|
token: normalizeOptionalString(config.token),
|
|
20
40
|
baseUrl: normalizeStoredBaseUrl(config.baseUrl),
|
|
21
41
|
organizationId: normalizeOptionalString(config.organizationId),
|
|
22
42
|
}).filter(([, value]) => value !== undefined));
|
|
23
43
|
};
|
|
44
|
+
const hasProfileConfigValues = (config) => {
|
|
45
|
+
return Boolean(config.baseUrl || config.token || config.organizationId);
|
|
46
|
+
};
|
|
47
|
+
const readProfileConfig = (value) => {
|
|
48
|
+
if (!isRecord(value)) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
return sanitizeProfileConfig({
|
|
52
|
+
baseUrl: typeof value.baseUrl === 'string' ? value.baseUrl : undefined,
|
|
53
|
+
organizationId: typeof value.organizationId === 'string' ? value.organizationId : undefined,
|
|
54
|
+
token: typeof value.token === 'string' ? value.token : undefined,
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
const sanitizeRootConfig = (config) => {
|
|
58
|
+
const profiles = Object.fromEntries(Object.entries(config.profiles)
|
|
59
|
+
.map(([profileName, profileConfig]) => {
|
|
60
|
+
const normalizedProfileName = normalizeProfileName(profileName);
|
|
61
|
+
const sanitizedProfileConfig = sanitizeProfileConfig(profileConfig);
|
|
62
|
+
return normalizedProfileName && hasProfileConfigValues(sanitizedProfileConfig)
|
|
63
|
+
? [normalizedProfileName, sanitizedProfileConfig]
|
|
64
|
+
: null;
|
|
65
|
+
})
|
|
66
|
+
.filter((entry) => entry !== null));
|
|
67
|
+
const defaultProfile = normalizeProfileName(config.defaultProfile);
|
|
68
|
+
return {
|
|
69
|
+
schemaVersion: cliConfigSchemaVersion,
|
|
70
|
+
...(defaultProfile && profiles[defaultProfile] ? { defaultProfile } : {}),
|
|
71
|
+
profiles,
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
const readRootConfig = (value) => {
|
|
75
|
+
if (!isRecord(value)) {
|
|
76
|
+
return { schemaVersion: cliConfigSchemaVersion, profiles: {} };
|
|
77
|
+
}
|
|
78
|
+
if (isRecord(value.profiles)) {
|
|
79
|
+
const profiles = Object.fromEntries(Object.entries(value.profiles)
|
|
80
|
+
.map(([profileName, profileConfig]) => {
|
|
81
|
+
const sanitizedProfileConfig = readProfileConfig(profileConfig);
|
|
82
|
+
return hasProfileConfigValues(sanitizedProfileConfig)
|
|
83
|
+
? [profileName, sanitizedProfileConfig]
|
|
84
|
+
: null;
|
|
85
|
+
})
|
|
86
|
+
.filter((entry) => entry !== null));
|
|
87
|
+
return sanitizeRootConfig({
|
|
88
|
+
schemaVersion: cliConfigSchemaVersion,
|
|
89
|
+
defaultProfile: typeof value.defaultProfile === 'string' ? value.defaultProfile : undefined,
|
|
90
|
+
profiles,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const legacyProfile = readProfileConfig(value);
|
|
94
|
+
if (!hasProfileConfigValues(legacyProfile)) {
|
|
95
|
+
return { schemaVersion: cliConfigSchemaVersion, profiles: {} };
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
schemaVersion: cliConfigSchemaVersion,
|
|
99
|
+
defaultProfile: implicitDefaultProfileName,
|
|
100
|
+
profiles: {
|
|
101
|
+
[implicitDefaultProfileName]: legacyProfile,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
const rootConfigHasProfiles = (config) => Object.keys(config.profiles).length > 0;
|
|
24
106
|
const getDefaultConfigPath = ({ env, platform, homeDirectory, }) => {
|
|
25
107
|
const explicitConfigPath = normalizeOptionalString(env[chalksurfConfigPathEnvVar]);
|
|
26
108
|
if (explicitConfigPath) {
|
|
@@ -73,6 +155,24 @@ export const resolveToken = ({ env, config }) => {
|
|
|
73
155
|
}
|
|
74
156
|
return { source: 'none' };
|
|
75
157
|
};
|
|
158
|
+
export const resolveProfile = ({ flagValue, env, config, }) => {
|
|
159
|
+
const flagProfileName = normalizeProfileName(flagValue);
|
|
160
|
+
if (flagProfileName) {
|
|
161
|
+
return { profileName: flagProfileName, profileSource: 'flag' };
|
|
162
|
+
}
|
|
163
|
+
const envProfileName = normalizeProfileName(env[chalksurfProfileEnvVar]);
|
|
164
|
+
if (envProfileName) {
|
|
165
|
+
return { profileName: envProfileName, profileSource: 'env' };
|
|
166
|
+
}
|
|
167
|
+
const defaultProfile = normalizeProfileName(config.defaultProfile);
|
|
168
|
+
if (defaultProfile) {
|
|
169
|
+
return { profileName: defaultProfile, profileSource: 'config' };
|
|
170
|
+
}
|
|
171
|
+
if (!rootConfigHasProfiles(config)) {
|
|
172
|
+
return { profileName: implicitDefaultProfileName, profileSource: 'implicit-default' };
|
|
173
|
+
}
|
|
174
|
+
throw new CliCommandError(`No active ChalkSurf profile selected. Pass --profile, set ${chalksurfProfileEnvVar}, or run "chalksurf profile use <profile>".`, 2);
|
|
175
|
+
};
|
|
76
176
|
export const resolveOrganization = ({ flagValue, env, config, profile, }) => {
|
|
77
177
|
const flagOrganizationId = normalizeOptionalString(flagValue);
|
|
78
178
|
if (flagOrganizationId) {
|
|
@@ -134,14 +234,23 @@ export const resolveOrganization = ({ flagValue, env, config, profile, }) => {
|
|
|
134
234
|
export const createConfigStore = ({ configPath, env = process.env, platform = process.platform, homeDirectory = homedir(), fs = { chmod, mkdir, readFile, rm, writeFile }, } = {}) => {
|
|
135
235
|
const resolvedConfigPath = configPath || getDefaultConfigPath({ env, platform, homeDirectory });
|
|
136
236
|
const load = async () => {
|
|
237
|
+
const rootConfig = await loadRoot();
|
|
238
|
+
const resolvedProfile = resolveProfile({ env, config: rootConfig });
|
|
239
|
+
const profileConfig = rootConfig.profiles[resolvedProfile.profileName] ?? {};
|
|
240
|
+
return {
|
|
241
|
+
...profileConfig,
|
|
242
|
+
...resolvedProfile,
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
const loadRoot = async () => {
|
|
137
246
|
try {
|
|
138
247
|
const configContents = await fs.readFile(resolvedConfigPath, 'utf8');
|
|
139
248
|
const parsedConfig = JSON.parse(configContents);
|
|
140
|
-
return
|
|
249
|
+
return readRootConfig(parsedConfig);
|
|
141
250
|
}
|
|
142
251
|
catch (error) {
|
|
143
252
|
if (error.code === 'ENOENT') {
|
|
144
|
-
return {};
|
|
253
|
+
return { schemaVersion: cliConfigSchemaVersion, profiles: {} };
|
|
145
254
|
}
|
|
146
255
|
if (error instanceof SyntaxError) {
|
|
147
256
|
throw new CliCommandError(`Failed to parse CLI config at ${resolvedConfigPath}.`, 2);
|
|
@@ -149,9 +258,18 @@ export const createConfigStore = ({ configPath, env = process.env, platform = pr
|
|
|
149
258
|
throw error;
|
|
150
259
|
}
|
|
151
260
|
};
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
|
|
261
|
+
const loadProfile = async ({ profileName } = {}) => {
|
|
262
|
+
const rootConfig = await loadRoot();
|
|
263
|
+
const resolvedProfile = resolveProfile({ flagValue: profileName, env, config: rootConfig });
|
|
264
|
+
const profileConfig = rootConfig.profiles[resolvedProfile.profileName] ?? {};
|
|
265
|
+
return {
|
|
266
|
+
...profileConfig,
|
|
267
|
+
...resolvedProfile,
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
const saveRoot = async (config) => {
|
|
271
|
+
const sanitizedConfig = sanitizeRootConfig(config);
|
|
272
|
+
if (!rootConfigHasProfiles(sanitizedConfig)) {
|
|
155
273
|
await fs.rm(resolvedConfigPath, { force: true });
|
|
156
274
|
return sanitizedConfig;
|
|
157
275
|
}
|
|
@@ -162,15 +280,75 @@ export const createConfigStore = ({ configPath, env = process.env, platform = pr
|
|
|
162
280
|
await fs.chmod(resolvedConfigPath, 0o600);
|
|
163
281
|
return sanitizedConfig;
|
|
164
282
|
};
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
283
|
+
const save = async (config) => {
|
|
284
|
+
const rootConfig = await loadRoot();
|
|
285
|
+
const nextProfileConfig = sanitizeProfileConfig(config);
|
|
286
|
+
const nextRootConfig = {
|
|
287
|
+
...rootConfig,
|
|
288
|
+
profiles: {
|
|
289
|
+
...rootConfig.profiles,
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
if (hasProfileConfigValues(nextProfileConfig)) {
|
|
293
|
+
nextRootConfig.profiles[config.profileName] = nextProfileConfig;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
delete nextRootConfig.profiles[config.profileName];
|
|
297
|
+
}
|
|
298
|
+
if (config.profileSource === 'implicit-default' && hasProfileConfigValues(nextProfileConfig)) {
|
|
299
|
+
nextRootConfig.defaultProfile = config.profileName;
|
|
300
|
+
}
|
|
301
|
+
return await saveRoot(nextRootConfig);
|
|
302
|
+
};
|
|
303
|
+
const update = async ({ profileName }, updater) => {
|
|
304
|
+
const currentConfig = await loadProfile({ profileName });
|
|
305
|
+
const nextProfileConfig = await updater(currentConfig);
|
|
306
|
+
return await save({
|
|
307
|
+
...nextProfileConfig,
|
|
308
|
+
profileName: currentConfig.profileName,
|
|
309
|
+
profileSource: currentConfig.profileSource,
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
const setDefaultProfile = async (profileName) => {
|
|
313
|
+
const normalizedProfileName = normalizeProfileName(profileName);
|
|
314
|
+
if (!normalizedProfileName) {
|
|
315
|
+
throw new CliCommandError('Profile name is required.', 2);
|
|
316
|
+
}
|
|
317
|
+
const rootConfig = await loadRoot();
|
|
318
|
+
if (!rootConfig.profiles[normalizedProfileName]) {
|
|
319
|
+
throw new CliCommandError(`Profile "${normalizedProfileName}" does not exist. Run "chalksurf auth login --profile ${normalizedProfileName} --base-url <url>" first.`, 2);
|
|
320
|
+
}
|
|
321
|
+
return await saveRoot({
|
|
322
|
+
...rootConfig,
|
|
323
|
+
defaultProfile: normalizedProfileName,
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
const deleteProfile = async (profileName) => {
|
|
327
|
+
const normalizedProfileName = normalizeProfileName(profileName);
|
|
328
|
+
if (!normalizedProfileName) {
|
|
329
|
+
throw new CliCommandError('Profile name is required.', 2);
|
|
330
|
+
}
|
|
331
|
+
const rootConfig = await loadRoot();
|
|
332
|
+
if (!rootConfig.profiles[normalizedProfileName]) {
|
|
333
|
+
throw new CliCommandError(`Profile "${normalizedProfileName}" does not exist.`, 2);
|
|
334
|
+
}
|
|
335
|
+
const profiles = { ...rootConfig.profiles };
|
|
336
|
+
delete profiles[normalizedProfileName];
|
|
337
|
+
return await saveRoot({
|
|
338
|
+
schemaVersion: cliConfigSchemaVersion,
|
|
339
|
+
...(rootConfig.defaultProfile === normalizedProfileName ? {} : { defaultProfile: rootConfig.defaultProfile }),
|
|
340
|
+
profiles,
|
|
341
|
+
});
|
|
169
342
|
};
|
|
170
343
|
return {
|
|
171
344
|
path: resolvedConfigPath,
|
|
172
345
|
load,
|
|
346
|
+
loadProfile,
|
|
347
|
+
loadRoot,
|
|
173
348
|
save,
|
|
349
|
+
saveRoot,
|
|
174
350
|
update,
|
|
351
|
+
setDefaultProfile,
|
|
352
|
+
deleteProfile,
|
|
175
353
|
};
|
|
176
354
|
};
|
package/docs/agents.md
CHANGED
|
@@ -4,21 +4,29 @@ Use this flow when ChalkSurf is driven by Codex, CI, or another orchestration la
|
|
|
4
4
|
|
|
5
5
|
## Authentication Strategy
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Agents must always use an explicit profile. The profile name must follow the
|
|
8
|
+
`ENV-AGENT_TYPE` convention, such as `prod-codex`, `staging-codex`, `dev-claude`,
|
|
9
|
+
or `prod-claude`.
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
|
-
|
|
11
|
-
export CHALKSURF_TOKEN=cs_cli_...
|
|
12
|
-
export CHALKSURF_ORGANIZATION_ID=org_123
|
|
12
|
+
chalksurf --profile prod-codex auth status --json
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Do not rely on the default profile in agent-driven runs. The default profile is
|
|
16
|
+
for the human operator's terminal workflow and may point at a different
|
|
17
|
+
environment, user token, or selected organization.
|
|
18
|
+
|
|
19
|
+
Create or refresh an agent profile explicitly:
|
|
16
20
|
|
|
17
21
|
```bash
|
|
18
|
-
chalksurf auth
|
|
22
|
+
printf '%s' "$CHALKSURF_TOKEN" | chalksurf auth login \
|
|
23
|
+
--profile prod-codex \
|
|
24
|
+
--base-url https://chalksurf-api.fly.dev \
|
|
25
|
+
--with-token
|
|
19
26
|
```
|
|
20
27
|
|
|
21
|
-
|
|
28
|
+
Use `CHALKSURF_TOKEN` only as the source for login or as a short-lived override.
|
|
29
|
+
Prefer storing the token in the explicit agent profile for repeated Codex/CI runs.
|
|
22
30
|
|
|
23
31
|
## Command Contract
|
|
24
32
|
|
|
@@ -31,7 +39,7 @@ For agent-driven imports, always use:
|
|
|
31
39
|
Recommended invocation shape:
|
|
32
40
|
|
|
33
41
|
```bash
|
|
34
|
-
chalksurf sheet import --manifest - --wait --json
|
|
42
|
+
chalksurf --profile prod-codex sheet import --manifest - --wait --json
|
|
35
43
|
```
|
|
36
44
|
|
|
37
45
|
In `--json` mode:
|
|
@@ -131,7 +139,7 @@ Each job includes:
|
|
|
131
139
|
For example:
|
|
132
140
|
|
|
133
141
|
```bash
|
|
134
|
-
cat import.json | chalksurf sheet import --manifest - --wait --json
|
|
142
|
+
cat import.json | chalksurf --profile prod-codex sheet import --manifest - --wait --json
|
|
135
143
|
```
|
|
136
144
|
|
|
137
145
|
On success, the envelope looks like:
|
|
@@ -168,13 +176,13 @@ Recommended workflow:
|
|
|
168
176
|
3. Codex builds a sheet-import manifest with one `sheets[]` entry per resulting sheet.
|
|
169
177
|
4. Codex groups multiple source files inside one `sources[]` array when they belong to the same resulting sheet.
|
|
170
178
|
5. Codex sets `translateTo: ["english"]` on the sheet entries that should produce an English translation.
|
|
171
|
-
6. Codex runs `chalksurf sheet import --manifest - --wait --json`.
|
|
179
|
+
6. Codex runs `chalksurf --profile prod-codex sheet import --manifest - --wait --json`.
|
|
172
180
|
7. Codex inspects `result.summary.failed`, `result.summary.timedOut`, and `jobs` to decide whether to retry or report failures.
|
|
173
181
|
|
|
174
182
|
Minimal shell shape:
|
|
175
183
|
|
|
176
184
|
```bash
|
|
177
|
-
cat import.json | chalksurf sheet import --manifest - --wait --json
|
|
185
|
+
cat import.json | chalksurf --profile prod-codex sheet import --manifest - --wait --json
|
|
178
186
|
```
|
|
179
187
|
|
|
180
188
|
The discovery step belongs outside the ChalkSurf CLI. The CLI contract starts at a concrete manifest and ends at structured import results.
|
package/docs/manual.md
CHANGED
|
@@ -4,16 +4,25 @@ Use this flow when a person is driving the CLI directly from a terminal.
|
|
|
4
4
|
|
|
5
5
|
## Authentication
|
|
6
6
|
|
|
7
|
-
Interactive login stores the token and base URL in
|
|
7
|
+
Interactive login stores the token and base URL in a named profile:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
chalksurf auth login --base-url https://api.
|
|
10
|
+
chalksurf auth login --profile prod-cztamas --base-url https://chalksurf-api.fly.dev
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
The same command also works with piped stdin:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
printf '%s' "$CHALKSURF_TOKEN" | chalksurf auth login
|
|
16
|
+
printf '%s' "$CHALKSURF_TOKEN" | chalksurf auth login \
|
|
17
|
+
--profile prod-cztamas \
|
|
18
|
+
--base-url https://chalksurf-api.fly.dev \
|
|
19
|
+
--with-token
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Set the profile you want to use by default in your terminal workflow:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
chalksurf profile use prod-cztamas
|
|
17
26
|
```
|
|
18
27
|
|
|
19
28
|
Check what the CLI will use for the current session:
|
|
@@ -24,9 +33,10 @@ chalksurf auth status
|
|
|
24
33
|
|
|
25
34
|
Config resolution rules:
|
|
26
35
|
|
|
27
|
-
- `--
|
|
28
|
-
- `
|
|
29
|
-
-
|
|
36
|
+
- `--profile` overrides `CHALKSURF_PROFILE`, which overrides the stored default profile.
|
|
37
|
+
- `--base-url` overrides `CHALKSURF_BASE_URL`, which overrides the active profile.
|
|
38
|
+
- `CHALKSURF_TOKEN` overrides the active profile token.
|
|
39
|
+
- `--organization` overrides manifest `organizationId`, which overrides `CHALKSURF_ORGANIZATION_ID`, which overrides the active profile.
|
|
30
40
|
|
|
31
41
|
## Organization Selection
|
|
32
42
|
|
|
@@ -42,6 +52,12 @@ Store a default organization for future commands:
|
|
|
42
52
|
chalksurf org use org_123
|
|
43
53
|
```
|
|
44
54
|
|
|
55
|
+
Store a default organization in a different profile without changing your default profile:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
chalksurf --profile prod-codex org use org_456
|
|
59
|
+
```
|
|
60
|
+
|
|
45
61
|
Override it for one command:
|
|
46
62
|
|
|
47
63
|
```bash
|
|
@@ -132,11 +148,12 @@ Missing auth:
|
|
|
132
148
|
chalksurf auth status
|
|
133
149
|
```
|
|
134
150
|
|
|
135
|
-
Use
|
|
151
|
+
Use profiles when switching between local, staging, production, humans, and agents:
|
|
136
152
|
|
|
137
153
|
```bash
|
|
138
|
-
|
|
139
|
-
chalksurf auth login --base-url https://
|
|
154
|
+
chalksurf auth login --profile staging-cztamas --base-url https://chalksurf-api-staging.fly.dev
|
|
155
|
+
chalksurf auth login --profile prod-cztamas --base-url https://chalksurf-api.fly.dev
|
|
156
|
+
chalksurf profile use prod-cztamas
|
|
140
157
|
```
|
|
141
158
|
|
|
142
159
|
Use JSON mode when you need exact machine-readable output:
|