@jskit-ai/jskit-cli 0.2.78 → 0.2.80

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/jskit-cli",
3
- "version": "0.2.78",
3
+ "version": "0.2.80",
4
4
  "description": "Bundle and package orchestration CLI for JSKIT apps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -20,12 +20,12 @@
20
20
  "test": "node --test"
21
21
  },
22
22
  "dependencies": {
23
- "@jskit-ai/jskit-catalog": "0.1.77",
24
- "@jskit-ai/kernel": "0.1.69",
25
- "@jskit-ai/shell-web": "0.1.68"
23
+ "@jskit-ai/jskit-catalog": "0.1.79",
24
+ "@jskit-ai/kernel": "0.1.71",
25
+ "@jskit-ai/shell-web": "0.1.70"
26
26
  },
27
27
  "engines": {
28
- "node": "20.x"
28
+ "node": ">=20 <23"
29
29
  },
30
30
  "publishConfig": {
31
31
  "access": "public"
@@ -0,0 +1,126 @@
1
+ import { mkdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import {
4
+ PROMPT_DIRECTORY
5
+ } from "./sessionRuntime/constants.js";
6
+ import {
7
+ fileExists,
8
+ normalizeText,
9
+ readTextIfExists,
10
+ writeTextFile
11
+ } from "./sessionRuntime/io.js";
12
+ import {
13
+ renderTemplate
14
+ } from "./sessionRuntime/promptRenderer.js";
15
+
16
+ const APP_BLUEPRINT_RELATIVE_PATH = ".jskit/APP_BLUEPRINT.md";
17
+ const APP_PROMPT_OVERRIDE_RELATIVE_ROOT = ".jskit/prompts";
18
+
19
+ function resolveAppBlueprintPaths(targetRoot = process.cwd()) {
20
+ const normalizedTargetRoot = path.resolve(normalizeText(targetRoot) || process.cwd());
21
+ return Object.freeze({
22
+ appBlueprintPath: path.join(normalizedTargetRoot, APP_BLUEPRINT_RELATIVE_PATH),
23
+ promptOverrideRoot: path.join(normalizedTargetRoot, APP_PROMPT_OVERRIDE_RELATIVE_ROOT),
24
+ targetRoot: normalizedTargetRoot
25
+ });
26
+ }
27
+
28
+ function extractAppBlueprintText(value = "") {
29
+ const text = normalizeText(value);
30
+ const match = /\[app_blueprint\]([\s\S]*?)\[\/app_blueprint\]/u.exec(text);
31
+ return normalizeText(match ? match[1] : text);
32
+ }
33
+
34
+ async function readAppPromptTemplate(paths, templateName) {
35
+ const normalizedName = normalizeText(templateName);
36
+ const overridePath = path.join(paths.promptOverrideRoot, normalizedName);
37
+ if (await fileExists(overridePath)) {
38
+ return readTextIfExists(overridePath);
39
+ }
40
+ return readTextIfExists(path.join(PROMPT_DIRECTORY, normalizedName));
41
+ }
42
+
43
+ async function renderAppBlueprintPrompt({ targetRoot = process.cwd(), appBrief = "" } = {}) {
44
+ const paths = resolveAppBlueprintPaths(targetRoot);
45
+ const normalizedBrief = normalizeText(appBrief);
46
+ if (!normalizedBrief) {
47
+ return {
48
+ ok: false,
49
+ appBlueprintPath: paths.appBlueprintPath,
50
+ errors: [
51
+ {
52
+ code: "app_brief_required",
53
+ message: "jskit blueprint prompt requires --brief, --brief-file, or --brief -.",
54
+ repairCommand: "jskit blueprint prompt --brief \"<what app are we building>\""
55
+ }
56
+ ],
57
+ prompt: ""
58
+ };
59
+ }
60
+ const template = await readAppPromptTemplate(paths, "app_blueprint.md");
61
+ return {
62
+ ok: true,
63
+ appBlueprintPath: paths.appBlueprintPath,
64
+ errors: [],
65
+ prompt: renderTemplate(template, {
66
+ app_brief: normalizedBrief
67
+ }).trim()
68
+ };
69
+ }
70
+
71
+ async function readAppBlueprint({ targetRoot = process.cwd() } = {}) {
72
+ const paths = resolveAppBlueprintPaths(targetRoot);
73
+ const blueprintText = await readTextIfExists(paths.appBlueprintPath);
74
+ return {
75
+ ok: true,
76
+ appBlueprintPath: paths.appBlueprintPath,
77
+ blueprintText: blueprintText.trim(),
78
+ exists: Boolean(blueprintText.trim()),
79
+ errors: []
80
+ };
81
+ }
82
+
83
+ async function writeAppBlueprint({ targetRoot = process.cwd(), appBlueprint = "" } = {}) {
84
+ const paths = resolveAppBlueprintPaths(targetRoot);
85
+ const blueprintText = extractAppBlueprintText(appBlueprint);
86
+ if (!blueprintText) {
87
+ return {
88
+ ok: false,
89
+ appBlueprintPath: paths.appBlueprintPath,
90
+ blueprintText: "",
91
+ exists: false,
92
+ errors: [
93
+ {
94
+ code: "app_blueprint_required",
95
+ message: "jskit blueprint set requires --blueprint, --blueprint-file, or --blueprint -.",
96
+ repairCommand: "jskit blueprint set --blueprint -"
97
+ }
98
+ ]
99
+ };
100
+ }
101
+ await mkdir(path.dirname(paths.appBlueprintPath), { recursive: true });
102
+ await writeTextFile(paths.appBlueprintPath, blueprintText);
103
+ return {
104
+ ok: true,
105
+ appBlueprintPath: paths.appBlueprintPath,
106
+ blueprintText,
107
+ exists: true,
108
+ errors: []
109
+ };
110
+ }
111
+
112
+ async function readTextInputFile(cwd, inputPath) {
113
+ const resolvedPath = path.resolve(cwd, normalizeText(inputPath));
114
+ return readFile(resolvedPath, "utf8");
115
+ }
116
+
117
+ export {
118
+ APP_BLUEPRINT_RELATIVE_PATH,
119
+ APP_PROMPT_OVERRIDE_RELATIVE_ROOT,
120
+ extractAppBlueprintText,
121
+ readAppBlueprint,
122
+ readTextInputFile,
123
+ renderAppBlueprintPrompt,
124
+ resolveAppBlueprintPaths,
125
+ writeAppBlueprint
126
+ };
@@ -52,6 +52,21 @@ function normalizeFileMutationRecord(value) {
52
52
  };
53
53
  }
54
54
 
55
+ function normalizeDependencyMutationRecord(value) {
56
+ const record = ensureObject(value);
57
+ if (Object.keys(record).length < 1) {
58
+ return {
59
+ version: String(value || ""),
60
+ when: null
61
+ };
62
+ }
63
+
64
+ return {
65
+ version: String(record.version || record.value || "").trim(),
66
+ when: normalizeMutationWhen(record.when)
67
+ };
68
+ }
69
+
55
70
  function normalizeMutationWhen(value) {
56
71
  const source = ensureObject(value);
57
72
  const allConditions = ensureArray(source.all)
@@ -304,6 +319,7 @@ function shouldApplyMutationWhen(
304
319
  export {
305
320
  normalizeMutationExtension,
306
321
  normalizeTemplateContextRecord,
322
+ normalizeDependencyMutationRecord,
307
323
  normalizeFileMutationRecord,
308
324
  normalizeMutationWhen,
309
325
  readObjectPath,
@@ -8,7 +8,9 @@ import {
8
8
  interpolateOptionValue
9
9
  } from "../shared/optionInterpolation.js";
10
10
  import {
11
- normalizeFileMutationRecord
11
+ normalizeDependencyMutationRecord,
12
+ normalizeFileMutationRecord,
13
+ shouldApplyMutationWhen
12
14
  } from "./mutationWhen.js";
13
15
  import {
14
16
  applyViteMutations,
@@ -141,6 +143,13 @@ function resolveManagedSourceRecord(packageEntry, existingInstall = {}) {
141
143
  return sourceRecord;
142
144
  }
143
145
 
146
+ function dependencyMutationUsesWhen(entries = []) {
147
+ return entries.some(([, rawDependencySpec]) => {
148
+ const dependencySpec = normalizeDependencyMutationRecord(rawDependencySpec);
149
+ return Boolean(dependencySpec.when);
150
+ });
151
+ }
152
+
144
153
  async function applyPackagePositioning({
145
154
  packageEntry,
146
155
  packageOptions,
@@ -413,9 +422,28 @@ async function applyPackageInstall({
413
422
  const mutationDependencies = ensureObject(mutations.dependencies);
414
423
  const runtimeDependencies = ensureObject(mutationDependencies.runtime);
415
424
  const devDependencies = ensureObject(mutationDependencies.dev);
425
+ const runtimeDependencyEntries = Object.entries(runtimeDependencies);
426
+ const devDependencyEntries = Object.entries(devDependencies);
427
+ const needsDependencyWhenConfig = dependencyMutationUsesWhen([
428
+ ...runtimeDependencyEntries,
429
+ ...devDependencyEntries
430
+ ]);
431
+ const dependencyWhenConfigContext = needsDependencyWhenConfig
432
+ ? await loadMutationWhenConfigContext(appRoot)
433
+ : {};
416
434
  const mutationScripts = ensureObject(ensureObject(mutations.packageJson).scripts);
417
435
 
418
- for (const [rawDependencyId, rawDependencyVersion] of Object.entries(runtimeDependencies)) {
436
+ for (const [rawDependencyId, rawDependencySpec] of runtimeDependencyEntries) {
437
+ const dependencySpec = normalizeDependencyMutationRecord(rawDependencySpec);
438
+ if (!shouldApplyMutationWhen(dependencySpec.when, {
439
+ options: packageOptions,
440
+ configContext: dependencyWhenConfigContext,
441
+ packageId: packageEntry.packageId,
442
+ mutationContext: `dependencies.runtime.${rawDependencyId}`
443
+ })) {
444
+ continue;
445
+ }
446
+
419
447
  const dependencyId = interpolateOptionValue(
420
448
  rawDependencyId,
421
449
  packageOptions,
@@ -423,7 +451,7 @@ async function applyPackageInstall({
423
451
  `dependencies.runtime.${rawDependencyId}.id`
424
452
  );
425
453
  const dependencyVersion = interpolateOptionValue(
426
- String(rawDependencyVersion || ""),
454
+ dependencySpec.version,
427
455
  packageOptions,
428
456
  packageEntry.packageId,
429
457
  `dependencies.runtime.${rawDependencyId}.value`
@@ -447,7 +475,17 @@ async function applyPackageInstall({
447
475
  }
448
476
  }
449
477
 
450
- for (const [rawDependencyId, rawDependencyVersion] of Object.entries(devDependencies)) {
478
+ for (const [rawDependencyId, rawDependencySpec] of devDependencyEntries) {
479
+ const dependencySpec = normalizeDependencyMutationRecord(rawDependencySpec);
480
+ if (!shouldApplyMutationWhen(dependencySpec.when, {
481
+ options: packageOptions,
482
+ configContext: dependencyWhenConfigContext,
483
+ packageId: packageEntry.packageId,
484
+ mutationContext: `dependencies.dev.${rawDependencyId}`
485
+ })) {
486
+ continue;
487
+ }
488
+
451
489
  const dependencyId = interpolateOptionValue(
452
490
  rawDependencyId,
453
491
  packageOptions,
@@ -455,7 +493,7 @@ async function applyPackageInstall({
455
493
  `dependencies.dev.${rawDependencyId}.id`
456
494
  );
457
495
  const dependencyVersion = interpolateOptionValue(
458
- String(rawDependencyVersion || ""),
496
+ dependencySpec.version,
459
497
  packageOptions,
460
498
  packageEntry.packageId,
461
499
  `dependencies.dev.${rawDependencyId}.value`
@@ -0,0 +1,151 @@
1
+ import {
2
+ readAppBlueprint,
3
+ readTextInputFile,
4
+ renderAppBlueprintPrompt,
5
+ writeAppBlueprint
6
+ } from "../appBlueprint.js";
7
+
8
+ function writeJson(stdout, payload) {
9
+ stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
10
+ }
11
+
12
+ function writeBlueprintText(stdout, payload) {
13
+ if (payload.prompt) {
14
+ stdout.write(`${payload.prompt}\n`);
15
+ return;
16
+ }
17
+ if (payload.blueprintText) {
18
+ stdout.write(`${payload.blueprintText}\n`);
19
+ return;
20
+ }
21
+ stdout.write(`No app blueprint set at ${payload.appBlueprintPath}.\n`);
22
+ }
23
+
24
+ async function readStream(stream) {
25
+ const chunks = [];
26
+ for await (const chunk of stream) {
27
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
28
+ }
29
+ return Buffer.concat(chunks).toString("utf8");
30
+ }
31
+
32
+ async function resolveTextInput({
33
+ cwd,
34
+ fileOption,
35
+ inlineOptions = {},
36
+ io = {},
37
+ stdinOption = "-",
38
+ textOption
39
+ }) {
40
+ if (Object.hasOwn(inlineOptions, fileOption)) {
41
+ const inputFile = String(inlineOptions[fileOption] || "").trim();
42
+ return inputFile ? readTextInputFile(cwd, inputFile) : "";
43
+ }
44
+ if (Object.hasOwn(inlineOptions, textOption)) {
45
+ const textValue = String(inlineOptions[textOption] ?? "");
46
+ return textValue === stdinOption ? readStream(io.stdin) : textValue;
47
+ }
48
+ return "";
49
+ }
50
+
51
+ function createBlueprintCommands(ctx = {}) {
52
+ const { resolveAppRootFromCwd } = ctx;
53
+
54
+ async function commandBlueprint({
55
+ positional = [],
56
+ options = {},
57
+ cwd,
58
+ stdout,
59
+ io = {}
60
+ } = {}) {
61
+ const appRoot = await resolveAppRootFromCwd(cwd);
62
+ const inlineOptions = options.inlineOptions || {};
63
+ const subcommand = String(positional[0] || "").trim();
64
+ let payload;
65
+
66
+ try {
67
+ if (positional.length > 1) {
68
+ payload = {
69
+ ok: false,
70
+ appBlueprintPath: "",
71
+ errors: [
72
+ {
73
+ code: "unexpected_blueprint_argument",
74
+ message: `Unexpected blueprint argument: ${positional.slice(1).join(" ")}`,
75
+ repairCommand: "jskit blueprint"
76
+ }
77
+ ]
78
+ };
79
+ } else if (!subcommand) {
80
+ payload = await readAppBlueprint({ targetRoot: appRoot });
81
+ } else if (subcommand === "prompt") {
82
+ const appBrief = await resolveTextInput({
83
+ cwd,
84
+ fileOption: "brief-file",
85
+ inlineOptions,
86
+ io,
87
+ textOption: "brief"
88
+ });
89
+ payload = await renderAppBlueprintPrompt({
90
+ targetRoot: appRoot,
91
+ appBrief
92
+ });
93
+ } else if (subcommand === "set") {
94
+ const appBlueprint = await resolveTextInput({
95
+ cwd,
96
+ fileOption: "blueprint-file",
97
+ inlineOptions,
98
+ io,
99
+ textOption: "blueprint"
100
+ });
101
+ payload = await writeAppBlueprint({
102
+ targetRoot: appRoot,
103
+ appBlueprint
104
+ });
105
+ } else {
106
+ payload = {
107
+ ok: false,
108
+ appBlueprintPath: "",
109
+ errors: [
110
+ {
111
+ code: "unknown_blueprint_subcommand",
112
+ message: `Unknown blueprint subcommand: ${subcommand}`,
113
+ repairCommand: "jskit blueprint"
114
+ }
115
+ ]
116
+ };
117
+ }
118
+ } catch (error) {
119
+ payload = {
120
+ ok: false,
121
+ appBlueprintPath: "",
122
+ errors: [
123
+ {
124
+ code: "blueprint_input_read_failed",
125
+ message: String(error?.message || error),
126
+ repairCommand: "jskit blueprint"
127
+ }
128
+ ]
129
+ };
130
+ }
131
+
132
+ if (options.json) {
133
+ writeJson(stdout, payload);
134
+ } else if (payload.ok === false) {
135
+ for (const error of payload.errors || []) {
136
+ stdout.write(`[${error.code}] ${error.message}\n`);
137
+ if (error.repairCommand) {
138
+ stdout.write(`Repair: ${error.repairCommand}\n`);
139
+ }
140
+ }
141
+ } else {
142
+ writeBlueprintText(stdout, payload);
143
+ }
144
+
145
+ return payload.ok === false ? 1 : 0;
146
+ }
147
+
148
+ return { commandBlueprint };
149
+ }
150
+
151
+ export { createBlueprintCommands };
@@ -0,0 +1,237 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import {
3
+ abandonSession,
4
+ adoptCodexThreadId,
5
+ buildSessionErrorResponse,
6
+ createSession,
7
+ inspectSessionDetails,
8
+ listSessions,
9
+ runSessionStep
10
+ } from "../sessionRuntime.js";
11
+
12
+ function writeJson(stdout, payload) {
13
+ stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
14
+ }
15
+
16
+ function writeSessionText(stdout, payload) {
17
+ if (payload.sessions) {
18
+ stdout.write("JSKIT sessions\n");
19
+ if (payload.sessions.length < 1) {
20
+ stdout.write("No sessions found.\n");
21
+ return;
22
+ }
23
+ for (const session of payload.sessions) {
24
+ stdout.write(`- ${session.sessionId} ${session.status} ${session.currentStep || "done"}\n`);
25
+ }
26
+ return;
27
+ }
28
+
29
+ stdout.write(`Session: ${payload.sessionId || "unknown"}\n`);
30
+ stdout.write(`Status: ${payload.status || "unknown"}\n`);
31
+ stdout.write(`Current step: ${payload.currentStep || "done"}\n`);
32
+ if (payload.issueUrl) {
33
+ stdout.write(`Issue: ${payload.issueUrl}\n`);
34
+ }
35
+ if (payload.prUrl) {
36
+ stdout.write(`PR: ${payload.prUrl}\n`);
37
+ }
38
+ if (payload.branch) {
39
+ stdout.write(`Branch: ${payload.branch}\n`);
40
+ }
41
+ if (payload.worktree) {
42
+ stdout.write(`Worktree: ${payload.worktree}\n`);
43
+ }
44
+ if (payload.completedSteps?.length) {
45
+ stdout.write("Done steps:\n");
46
+ for (const step of payload.completedSteps) {
47
+ stdout.write(`- ${step}\n`);
48
+ }
49
+ }
50
+ if (payload.prompt) {
51
+ stdout.write("\n");
52
+ stdout.write(payload.prompt);
53
+ stdout.write("\n");
54
+ }
55
+ if (payload.errors?.length) {
56
+ stdout.write("Errors:\n");
57
+ for (const error of payload.errors) {
58
+ stdout.write(`- [${error.code}] ${error.message}\n`);
59
+ if (error.repairCommand) {
60
+ stdout.write(` Repair: ${error.repairCommand}\n`);
61
+ }
62
+ }
63
+ }
64
+ if (payload.nextCommand) {
65
+ stdout.write(`Next: ${payload.nextCommand}\n`);
66
+ }
67
+ }
68
+
69
+ async function readStream(stream) {
70
+ const chunks = [];
71
+ for await (const chunk of stream) {
72
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
73
+ }
74
+ return Buffer.concat(chunks).toString("utf8");
75
+ }
76
+
77
+ function resolveInputFilePath(cwd, filePath) {
78
+ return filePath.startsWith("/") ? filePath : `${cwd}/${filePath}`;
79
+ }
80
+
81
+ async function resolveTextInput({
82
+ codePrefix,
83
+ fileOption,
84
+ inlineOptions = {},
85
+ io = {},
86
+ repairCommand,
87
+ cwd,
88
+ stdinOption,
89
+ textOption,
90
+ sessionId
91
+ }) {
92
+ if (Object.hasOwn(inlineOptions, fileOption)) {
93
+ const inputFile = String(inlineOptions[fileOption] || "").trim();
94
+ if (!inputFile) {
95
+ return { ok: true, value: "" };
96
+ }
97
+ const resolvedInputFile = resolveInputFilePath(cwd, inputFile);
98
+ try {
99
+ return {
100
+ ok: true,
101
+ value: await readFile(resolvedInputFile, "utf8")
102
+ };
103
+ } catch (error) {
104
+ return {
105
+ ok: false,
106
+ payload: buildSessionErrorResponse({
107
+ targetRoot: cwd,
108
+ sessionId,
109
+ code: `${codePrefix}_file_read_failed`,
110
+ message: `Could not read ${codePrefix.replaceAll("_", " ")} file ${resolvedInputFile}: ${error.message}`,
111
+ repairCommand
112
+ })
113
+ };
114
+ }
115
+ }
116
+ if (Object.hasOwn(inlineOptions, textOption)) {
117
+ const textValue = String(inlineOptions[textOption] ?? "");
118
+ if (textValue === stdinOption) {
119
+ return {
120
+ ok: true,
121
+ value: await readStream(io.stdin)
122
+ };
123
+ }
124
+ return {
125
+ ok: true,
126
+ value: textValue
127
+ };
128
+ }
129
+ return { ok: true, value: "" };
130
+ }
131
+
132
+ async function resolveStepInputs({
133
+ inlineOptions = {},
134
+ io = {},
135
+ cwd,
136
+ sessionId
137
+ }) {
138
+ const issue = await resolveTextInput({
139
+ codePrefix: "issue",
140
+ fileOption: "issue-file",
141
+ inlineOptions,
142
+ io,
143
+ repairCommand: `jskit session ${sessionId} step --issue -`,
144
+ cwd,
145
+ sessionId,
146
+ stdinOption: "-",
147
+ textOption: "issue"
148
+ });
149
+ if (issue.ok === false) {
150
+ return issue;
151
+ }
152
+
153
+ return {
154
+ issue: issue.value,
155
+ ok: true
156
+ };
157
+ }
158
+
159
+ function normalizeStepOptions(inlineOptions = {}) {
160
+ return {
161
+ ...inlineOptions,
162
+ prompt: inlineOptions.prompt,
163
+ userCheck: inlineOptions["user-check"] || inlineOptions.userCheck
164
+ };
165
+ }
166
+
167
+ function createSessionCommands() {
168
+ return {
169
+ async commandSession({
170
+ positional = [],
171
+ options = {},
172
+ cwd,
173
+ stdout,
174
+ io = {}
175
+ } = {}) {
176
+ const [first, second] = positional;
177
+ const inlineOptions = options.inlineOptions || {};
178
+ let payload;
179
+
180
+ if (!first) {
181
+ payload = await listSessions({ targetRoot: cwd });
182
+ } else if (first === "create") {
183
+ payload = await createSession({ targetRoot: cwd });
184
+ } else if (second === "step") {
185
+ const stepInputs = await resolveStepInputs({
186
+ inlineOptions,
187
+ io,
188
+ cwd,
189
+ sessionId: first
190
+ });
191
+ payload = stepInputs.ok === false
192
+ ? stepInputs.payload
193
+ : await runSessionStep({
194
+ targetRoot: cwd,
195
+ sessionId: first,
196
+ options: {
197
+ ...normalizeStepOptions(inlineOptions),
198
+ issue: stepInputs.issue
199
+ }
200
+ });
201
+ } else if (second === "abandon") {
202
+ payload = await abandonSession({
203
+ targetRoot: cwd,
204
+ sessionId: first
205
+ });
206
+ } else if (second === "adopt-codex-thread") {
207
+ payload = await adoptCodexThreadId({
208
+ targetRoot: cwd,
209
+ sessionId: first,
210
+ codexThreadId: inlineOptions["codex-thread-id"] || inlineOptions.codexThreadId
211
+ });
212
+ } else if (!second) {
213
+ payload = await inspectSessionDetails({
214
+ targetRoot: cwd,
215
+ sessionId: first
216
+ });
217
+ } else {
218
+ payload = buildSessionErrorResponse({
219
+ targetRoot: cwd,
220
+ sessionId: first,
221
+ code: "unknown_session_subcommand",
222
+ message: `Unknown session subcommand: ${second}`,
223
+ repairCommand: `jskit session ${first}`
224
+ });
225
+ }
226
+
227
+ if (options.json) {
228
+ writeJson(stdout, payload);
229
+ } else {
230
+ writeSessionText(stdout, payload);
231
+ }
232
+ return payload.ok === false ? 1 : 0;
233
+ }
234
+ };
235
+ }
236
+
237
+ export { createSessionCommands };
@@ -2,6 +2,9 @@ import {
2
2
  ensureArray,
3
3
  ensureObject
4
4
  } from "../../shared/collectionUtils.js";
5
+ import {
6
+ normalizeDependencyMutationRecord
7
+ } from "../../cliRuntime/mutationWhen.js";
5
8
  import { createShowRenderHelpers } from "./renderHelpers.js";
6
9
  import { writePackageExportsSection } from "./renderPackageExports.js";
7
10
  import { writeCapabilitiesSections } from "./renderPackageCapabilities.js";
@@ -35,6 +38,12 @@ function resolveOwnershipGuidance(payload = {}) {
35
38
  return ensureObject(ensureObject(ensureObject(payload.metadata).jskit).ownershipGuidance);
36
39
  }
37
40
 
41
+ function renderDependencyMutationSpec(versionSpec) {
42
+ const dependencySpec = normalizeDependencyMutationRecord(versionSpec);
43
+ const whenSuffix = dependencySpec.when ? ` when ${JSON.stringify(dependencySpec.when)}` : "";
44
+ return `${dependencySpec.version}${whenSuffix}`.trim();
45
+ }
46
+
38
47
  function renderPackagePayloadText({
39
48
  payload,
40
49
  provides,
@@ -255,7 +264,7 @@ function renderPackagePayloadText({
255
264
  wrapWidth,
256
265
  items: runtimeMutationEntries.map(([dependencyId, versionSpec]) => {
257
266
  const dependencyText = String(dependencyId);
258
- const versionText = String(versionSpec);
267
+ const versionText = renderDependencyMutationSpec(versionSpec);
259
268
  return {
260
269
  text: `${dependencyText} ${versionText}`,
261
270
  rendered: `${color.item(dependencyText)} ${color.installed(versionText)}`
@@ -325,7 +334,7 @@ function renderPackagePayloadText({
325
334
  wrapWidth,
326
335
  items: devMutationEntries.map(([dependencyId, versionSpec]) => {
327
336
  const dependencyText = String(dependencyId);
328
- const versionText = String(versionSpec);
337
+ const versionText = renderDependencyMutationSpec(versionSpec);
329
338
  return {
330
339
  text: `${dependencyText} ${versionText}`,
331
340
  rendered: `${color.item(dependencyText)} ${color.installed(versionText)}`