toolcraft 0.0.97 → 0.0.99

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
@@ -354,7 +354,16 @@ Toolcraft exports a fixed HTTP error hierarchy for transports and generated API
354
354
  Each error carries `status`, optional `code`, optional `requestId`, and serializable `request` and
355
355
  `response` context.
356
356
 
357
- CLI picks `rich` by default, `--json` switches to `json`. SDK calls return the raw handler value. MCP calls with `result:` return the handler value as `structuredContent` using the configured MCP casing; MCP calls without `result:` keep content-block behavior.
357
+ CLI picks `rich` by default. Enable the generated output selector when starting the CLI:
358
+
359
+ ```ts
360
+ await runCLI(root, { controls: { output: true } });
361
+ ```
362
+
363
+ Then choose structured output with `mytool command --output json`. The selector also accepts `rich`,
364
+ `md`, and `markdown`. SDK calls return the raw handler value. MCP calls with `result:` return the
365
+ handler value as `structuredContent` using the configured MCP casing; MCP calls without `result:`
366
+ keep content-block behavior.
358
367
 
359
368
  ## MCP proxy: adopt an existing MCP server
360
369
 
@@ -578,6 +587,7 @@ additional long CLI flags. For example, `rawResponse` normally maps to `--raw-re
578
587
  - `services?: TServices` — merged into every handler context.
579
588
  - `version?: string` — surfaced via `--version`.
580
589
  - `presets?: boolean` — enables `--preset <path>` for loading parameter defaults from JSON files.
590
+ - `controls?: { debug?, logLevel?, output?, verbose?, yes? }` — enables generated global CLI controls. Set `output: true` to expose `--output <rich|md|markdown|json>`.
581
591
  - `apiVersion?: string` — for `requires.apiVersion`.
582
592
  - `humanInLoop?: HumanInLoopRuntimeOptions`
583
593
  - `errorReports?: boolean | { dir?: string }`
package/composition.json CHANGED
@@ -48,7 +48,7 @@
48
48
  },
49
49
  {
50
50
  "name": "toolcraft",
51
- "version": "0.0.97",
51
+ "version": "0.0.99",
52
52
  "license": "MIT"
53
53
  },
54
54
  {
@@ -48,7 +48,7 @@
48
48
  },
49
49
  {
50
50
  "name": "toolcraft",
51
- "version": "0.0.97",
51
+ "version": "0.0.99",
52
52
  "license": "MIT"
53
53
  },
54
54
  {
@@ -0,0 +1,9 @@
1
+ import { type FileChange, type FileChangeDisplayMode } from "toolcraft-design";
2
+ import type { Renderers } from "./index.js";
3
+ export interface FileChangeResult {
4
+ changes: readonly FileChange[];
5
+ }
6
+ export interface FileChangeRendererOptions {
7
+ mode?: FileChangeDisplayMode;
8
+ }
9
+ export declare function createFileChangeRenderers<TResult extends FileChangeResult = FileChangeResult>(options?: FileChangeRendererOptions): Renderers<TResult>;
@@ -0,0 +1,10 @@
1
+ import { renderFileChanges } from "toolcraft-design";
2
+ export function createFileChangeRenderers(options = {}) {
3
+ return {
4
+ rich: (result) => {
5
+ process.stdout.write(`${renderFileChanges(result.changes, { mode: options.mode })}\n`);
6
+ },
7
+ markdown: (result) => renderFileChanges(result.changes, { mode: options.mode, format: "markdown" }),
8
+ json: (result) => result
9
+ };
10
+ }
@@ -1,5 +1,6 @@
1
1
  import { S } from "toolcraft-schema";
2
2
  import { defineCommand, defineGroup } from "./index.js";
3
+ import { createFileChangeRenderers } from "./file-change-renderer.js";
3
4
  const ignoredScope = ["cli", "sdk"];
4
5
  const ignoredSecrets = {
5
6
  apiKey: { env: "API_KEY" },
@@ -29,6 +30,14 @@ const ignoredCommand = defineCommand({
29
30
  };
30
31
  },
31
32
  });
33
+ const ignoredFileChangeCommand = defineCommand({
34
+ name: "status",
35
+ params: S.Object({}),
36
+ render: createFileChangeRenderers({ mode: "status" }),
37
+ handler: async () => ({
38
+ changes: [{ kind: "added", path: "flows/morning.json", newContent: "{}\n" }]
39
+ })
40
+ });
32
41
  const ignoredGroup = defineGroup({
33
42
  name: "root",
34
43
  scope: ignoredScope,
package/dist/index.d.ts CHANGED
@@ -208,6 +208,8 @@ export { ApprovalDeclinedError, ToolcraftBugError, UserError };
208
208
  export { createRuntimeLogger, isLogLevel, shouldEmitDiagnostic } from "./runtime-logging.js";
209
209
  export { findPackageMetadata, packageMetadata } from "./package-metadata.js";
210
210
  export type { PackageMetadata } from "./package-metadata.js";
211
+ export type { FileChangeRendererOptions, FileChangeResult } from "./file-change-renderer.js";
212
+ export type { FileChange, FileChangeDisplayMode, FileChangeKind } from "toolcraft-design";
211
213
  export type { DiagnosticLogEvent, LogLevel, RuntimeLogger, RuntimeLoggerInput } from "./runtime-logging.js";
212
214
  export type { AnySchema, ArraySchema, BooleanSchema, EnumSchema, JsonSchema, NumberSchema, ObjectSchema, OptionalSchema, Static, StringSchema } from "toolcraft-schema";
213
215
  export type { HumanInLoopConfig, HumanInLoopPending, HumanInLoopRuntimeOptions };
@@ -0,0 +1,16 @@
1
+ export type FileChangeKind = "added" | "modified" | "deleted" | "renamed";
2
+ export type FileChangeDisplayMode = "status" | "diff";
3
+ export type FileChangeOutputFormat = "terminal" | "markdown";
4
+ export interface FileChange {
5
+ path: string;
6
+ kind: FileChangeKind;
7
+ oldPath?: string;
8
+ conflict?: boolean;
9
+ oldContent?: string;
10
+ newContent?: string;
11
+ }
12
+ export interface RenderFileChangesOptions {
13
+ mode?: FileChangeDisplayMode;
14
+ format?: FileChangeOutputFormat;
15
+ }
16
+ export declare function renderFileChanges(changes: readonly FileChange[], options?: RenderFileChangesOptions): string;
@@ -0,0 +1,162 @@
1
+ import { color } from "./color.js";
2
+ const STATUS_MARKERS = {
3
+ added: "A",
4
+ modified: "M",
5
+ deleted: "D",
6
+ renamed: "R"
7
+ };
8
+ const STATUS_LABELS = {
9
+ added: "added",
10
+ modified: "modified",
11
+ deleted: "deleted",
12
+ renamed: "renamed"
13
+ };
14
+ function changePath(change) {
15
+ if (change.kind === "renamed" && change.oldPath) {
16
+ return `${change.oldPath} -> ${change.path}`;
17
+ }
18
+ return change.path;
19
+ }
20
+ function colorStatusMarker(change, marker) {
21
+ if (change.conflict) {
22
+ return color.red.bold(marker);
23
+ }
24
+ switch (change.kind) {
25
+ case "added":
26
+ return color.green(marker);
27
+ case "modified":
28
+ return color.yellow(marker);
29
+ case "deleted":
30
+ return color.red(marker);
31
+ case "renamed":
32
+ return color.cyan(marker);
33
+ }
34
+ }
35
+ function formatSummary(changes) {
36
+ const counts = new Map();
37
+ let conflicts = 0;
38
+ for (const change of changes) {
39
+ counts.set(change.kind, (counts.get(change.kind) ?? 0) + 1);
40
+ if (change.conflict) {
41
+ conflicts += 1;
42
+ }
43
+ }
44
+ const details = Object.keys(STATUS_LABELS)
45
+ .flatMap((kind) => {
46
+ const count = counts.get(kind) ?? 0;
47
+ return count > 0 ? [`${count} ${STATUS_LABELS[kind]}`] : [];
48
+ });
49
+ if (conflicts > 0) {
50
+ details.push(`${conflicts} ${conflicts === 1 ? "conflict" : "conflicts"}`);
51
+ }
52
+ return `${changes.length} ${changes.length === 1 ? "change" : "changes"} (${details.join(", ")})`;
53
+ }
54
+ function renderStatus(changes, format) {
55
+ const lines = changes.map((change) => {
56
+ const marker = `${STATUS_MARKERS[change.kind]}${change.conflict ? "!" : " "}`;
57
+ const renderedMarker = format === "terminal" ? colorStatusMarker(change, marker) : marker;
58
+ return `${renderedMarker} ${changePath(change)}`;
59
+ });
60
+ const output = [...lines, "", formatSummary(changes)].join("\n");
61
+ return format === "markdown" ? `\`\`\`text\n${output}\n\`\`\`` : output;
62
+ }
63
+ function diffPath(prefix, path) {
64
+ return `${prefix}/${path}`;
65
+ }
66
+ function contentLines(content) {
67
+ const lines = content.split("\n");
68
+ if (lines.at(-1) === "") {
69
+ lines.pop();
70
+ }
71
+ return lines;
72
+ }
73
+ function commonPrefixLength(oldLines, newLines) {
74
+ const limit = Math.min(oldLines.length, newLines.length);
75
+ let index = 0;
76
+ while (index < limit && oldLines[index] === newLines[index]) {
77
+ index += 1;
78
+ }
79
+ return index;
80
+ }
81
+ function commonSuffixLength(oldLines, newLines, prefixLength) {
82
+ const limit = Math.min(oldLines.length, newLines.length) - prefixLength;
83
+ let length = 0;
84
+ while (length < limit &&
85
+ oldLines[oldLines.length - length - 1] === newLines[newLines.length - length - 1]) {
86
+ length += 1;
87
+ }
88
+ return length;
89
+ }
90
+ function hunkRange(startIndex, count) {
91
+ const lineNumber = count === 0 ? 0 : startIndex + 1;
92
+ return `${lineNumber},${count}`;
93
+ }
94
+ function createUnifiedHunk(oldContent, newContent) {
95
+ const oldLines = contentLines(oldContent);
96
+ const newLines = contentLines(newContent);
97
+ const prefixLength = commonPrefixLength(oldLines, newLines);
98
+ const suffixLength = commonSuffixLength(oldLines, newLines, prefixLength);
99
+ const context = 3;
100
+ const oldChangeEnd = oldLines.length - suffixLength;
101
+ const newChangeEnd = newLines.length - suffixLength;
102
+ const oldStart = Math.max(0, prefixLength - context);
103
+ const newStart = Math.max(0, prefixLength - context);
104
+ const oldEnd = Math.min(oldLines.length, oldChangeEnd + context);
105
+ const newEnd = Math.min(newLines.length, newChangeEnd + context);
106
+ const oldCount = oldEnd - oldStart;
107
+ const newCount = newEnd - newStart;
108
+ const before = oldLines.slice(oldStart, prefixLength).map((line) => ` ${line}`);
109
+ const removed = oldLines.slice(prefixLength, oldChangeEnd).map((line) => `-${line}`);
110
+ const added = newLines.slice(prefixLength, newChangeEnd).map((line) => `+${line}`);
111
+ const after = oldLines.slice(oldChangeEnd, oldEnd).map((line) => ` ${line}`);
112
+ return [
113
+ `@@ -${hunkRange(oldStart, oldCount)} +${hunkRange(newStart, newCount)} @@`,
114
+ ...before,
115
+ ...removed,
116
+ ...added,
117
+ ...after
118
+ ].join("\n");
119
+ }
120
+ function createChangePatch(change) {
121
+ const oldPath = change.kind === "added"
122
+ ? "/dev/null"
123
+ : diffPath("a", change.oldPath ?? change.path);
124
+ const newPath = change.kind === "deleted" ? "/dev/null" : diffPath("b", change.path);
125
+ const oldContent = change.kind === "added" ? "" : (change.oldContent ?? "");
126
+ const newContent = change.kind === "deleted" ? "" : (change.newContent ?? "");
127
+ const headers = [`--- ${oldPath}`, `+++ ${newPath}`];
128
+ if (oldContent !== newContent) {
129
+ return [...headers, createUnifiedHunk(oldContent, newContent)].join("\n");
130
+ }
131
+ return headers.join("\n");
132
+ }
133
+ function colorDiffLine(line) {
134
+ if (line.startsWith("@@")) {
135
+ return color.cyan(line);
136
+ }
137
+ if (line.startsWith("+++") || line.startsWith("---")) {
138
+ return color.bold(line);
139
+ }
140
+ if (line.startsWith("+")) {
141
+ return color.green(line);
142
+ }
143
+ if (line.startsWith("-")) {
144
+ return color.red(line);
145
+ }
146
+ return line;
147
+ }
148
+ function renderDiff(changes, format) {
149
+ const output = changes.map(createChangePatch).join("\n\n");
150
+ if (format === "markdown") {
151
+ return `\`\`\`diff\n${output}\n\`\`\``;
152
+ }
153
+ return output.split("\n").map(colorDiffLine).join("\n");
154
+ }
155
+ export function renderFileChanges(changes, options = {}) {
156
+ if (changes.length === 0) {
157
+ return "No file changes.";
158
+ }
159
+ const mode = options.mode ?? "status";
160
+ const format = options.format ?? "terminal";
161
+ return mode === "diff" ? renderDiff(changes, format) : renderStatus(changes, format);
162
+ }
@@ -10,6 +10,8 @@ export { formatCommandNotFound } from "./command-errors.js";
10
10
  export { formatCommandNotFoundPanel } from "./command-errors.js";
11
11
  export { renderTable } from "./table.js";
12
12
  export type { TableColumn, RenderTableOptions } from "./table.js";
13
+ export { renderFileChanges } from "./file-changes.js";
14
+ export type { FileChange, FileChangeDisplayMode, FileChangeKind, FileChangeOutputFormat, RenderFileChangesOptions } from "./file-changes.js";
13
15
  export { renderCatalog } from "./catalog.js";
14
16
  export type { CatalogGroup, CatalogItem, CatalogMetric, CatalogTone, RenderCatalogOptions } from "./catalog.js";
15
17
  export { getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./template.js";
@@ -6,5 +6,6 @@ export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption,
6
6
  export { formatCommandNotFound } from "./command-errors.js";
7
7
  export { formatCommandNotFoundPanel } from "./command-errors.js";
8
8
  export { renderTable } from "./table.js";
9
+ export { renderFileChanges } from "./file-changes.js";
9
10
  export { renderCatalog } from "./catalog.js";
10
11
  export { getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./template.js";
@@ -19,6 +19,8 @@ export { formatCommandNotFound } from "./components/command-errors.js";
19
19
  export { formatCommandNotFoundPanel } from "./components/command-errors.js";
20
20
  export { renderTable } from "./components/table.js";
21
21
  export type { TableColumn, RenderTableOptions } from "./components/table.js";
22
+ export { renderFileChanges } from "./components/file-changes.js";
23
+ export type { FileChange, FileChangeDisplayMode, FileChangeKind, FileChangeOutputFormat, RenderFileChangesOptions } from "./components/file-changes.js";
22
24
  export { renderCatalog } from "./components/catalog.js";
23
25
  export type { CatalogGroup, CatalogItem, CatalogMetric, CatalogTone, RenderCatalogOptions } from "./components/catalog.js";
24
26
  export { renderDetailCard } from "./components/detail-card.js";
@@ -15,6 +15,7 @@ export * as helpFormatterPlain from "./components/help-formatter-plain.js";
15
15
  export { formatCommandNotFound } from "./components/command-errors.js";
16
16
  export { formatCommandNotFoundPanel } from "./components/command-errors.js";
17
17
  export { renderTable } from "./components/table.js";
18
+ export { renderFileChanges } from "./components/file-changes.js";
18
19
  export { renderCatalog } from "./components/catalog.js";
19
20
  export { renderDetailCard } from "./components/detail-card.js";
20
21
  export { renderInspectorCard } from "./components/inspector-card.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.97",
3
+ "version": "0.0.99",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -70,6 +70,10 @@
70
70
  "types": "./dist/frontmatter.d.ts",
71
71
  "import": "./dist/frontmatter.js"
72
72
  },
73
+ "./file-changes": {
74
+ "types": "./dist/file-change-renderer.d.ts",
75
+ "import": "./dist/file-change-renderer.js"
76
+ },
73
77
  "./process-runner": {
74
78
  "types": "./dist/process-runner.d.ts",
75
79
  "import": "./dist/process-runner.js"
@@ -100,7 +104,7 @@
100
104
  "postpack": "node ../../scripts/manage-bundled-workspace-deps.mjs cleanup . toolcraft-design @poe-code/frontmatter @poe-code/agent-mcp-config @poe-code/agent-human-in-loop @poe-code/task-list @poe-code/agent-defs @poe-code/config-mutations @poe-code/process-runner tiny-mcp-client auth-store"
101
105
  },
102
106
  "dependencies": {
103
- "toolcraft-schema": "0.0.97",
107
+ "toolcraft-schema": "0.0.99",
104
108
  "commander": "^13.1.0",
105
109
  "fast-string-width": "^3.0.2",
106
110
  "fast-wrap-ansi": "^0.2.0",