dsh-office-tools 0.2.0 → 1.0.2

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.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Self-contained OOXML zip container (1.0.0).
3
+ *
4
+ * Two halves:
5
+ *
6
+ * - `buildAsciiZip` WRITES a package whose every byte is at most 0x7F. All
7
+ * entries are STOREd and the planner pads each XML part with trailing
8
+ * newlines (legal after the root element) until every CRC-32, size, and
9
+ * offset field of the archive is itself ASCII-safe. That makes the whole
10
+ * document a pure-ASCII string, so it can travel through any UTF-8 text
11
+ * channel — specifically the official `ctx.fs.writeText` service — and land
12
+ * on disk byte-identical. No compression, no binary parts: this is the
13
+ * contract that keeps the plugin free of direct file-system access while
14
+ * still producing real .docx/.xlsx/.pptx files every Office suite opens.
15
+ *
16
+ * - `readZip` READS any real-world OOXML package: STORE and DEFLATE entries
17
+ * (via node:zlib), bounded by the same declared-size budgets the jszip
18
+ * guard enforced in <=0.6.x — per-entry, total, and entry-count caps are
19
+ * checked against the central directory BEFORE anything is inflated.
20
+ */
21
+ /** Hard cap for reading an existing Office file into memory. */
22
+ export declare const MAX_OFFICE_FILE_BYTES: number;
23
+ /** Declared uncompressed ceiling for one zip entry inside an Office file. */
24
+ export declare const MAX_ZIP_ENTRY_BYTES: number;
25
+ /** Declared uncompressed ceiling summed over all entries of one archive. */
26
+ export declare const MAX_ZIP_TOTAL_BYTES: number;
27
+ /** Maximum entries (files + directories) in one archive. */
28
+ export declare const MAX_ZIP_ENTRIES = 100000;
29
+ /** Overridable budgets so tests can trip the guard with tiny values. */
30
+ export interface ZipGuardLimits {
31
+ maxEntryBytes?: number;
32
+ maxTotalBytes?: number;
33
+ maxEntries?: number;
34
+ }
35
+ /** ASCII-safe = every byte at most 0x7F, so UTF-8 encoding is the identity. */
36
+ export declare function assertAscii(value: string, label: string): void;
37
+ export interface ZipPart {
38
+ name: string;
39
+ content: string;
40
+ }
41
+ /**
42
+ * Build the whole package as one ASCII-safe string: local STORE entries in
43
+ * order, the central directory, and the end-of-central-directory record.
44
+ */
45
+ export declare function buildAsciiZip(parts: ZipPart[]): string;
46
+ export interface ZipEntryInfo {
47
+ name: string;
48
+ method: number;
49
+ compressedSize: number;
50
+ uncompressedSize: number;
51
+ localOffset: number;
52
+ }
53
+ export declare class ZipArchive {
54
+ private readonly bytes;
55
+ readonly entries: ReadonlyMap<string, ZipEntryInfo>;
56
+ constructor(bytes: Uint8Array, entries: Map<string, ZipEntryInfo>);
57
+ has(name: string): boolean;
58
+ entryNames(): string[];
59
+ entryCount(): number;
60
+ /**
61
+ * Raw (still compressed) bytes of one entry, located through its own local
62
+ * header so central-directory offsets are never trusted for slicing.
63
+ */
64
+ entryBytes(name: string): Uint8Array;
65
+ /** Inflated (stored or decompressed) bytes of one entry, bounded by its declared size. */
66
+ private entryData;
67
+ /** Decode one entry as UTF-8 text after inflating it when needed. */
68
+ entryText(name: string): string;
69
+ /** True when an entry's INFLATED content can round-trip an ASCII-safe rewrite. */
70
+ entryIsAsciiSafe(name: string): boolean;
71
+ }
72
+ /**
73
+ * Parse and guard one archive: the end-of-central-directory record is found
74
+ * by scanning backwards, every entry's DECLARED uncompressed size is checked
75
+ * against the budgets before anything is inflated, and every local slice is
76
+ * re-derived from the entry's own local header.
77
+ */
78
+ export declare function readZip(bytes: Uint8Array, limits?: ZipGuardLimits): ZipArchive;
79
+ /**
80
+ * OOXML parts are plain element trees; a DOCTYPE/ENTITY declaration is never
81
+ * legitimate in one. Our extractors never resolve entities, but we refuse to
82
+ * look at such a part at all so entity-expansion payloads die at the door.
83
+ */
84
+ export declare function assertNoXmlDtd(xml: string, label: string): void;
85
+ /**
86
+ * Read one zip part as text and refuse DTD/entity-bearing XML before any
87
+ * caller parses it. Returns null when the archive has no such part.
88
+ */
89
+ export declare function readZipXmlPart(zip: ZipArchive, name: string): string | null;
90
+ /**
91
+ * Materialize every file entry as text for an ASCII-safe re-emit. Any entry
92
+ * with binary (non-ASCII) bytes throws: the re-publish path goes through the
93
+ * official UTF-8 text channel and cannot round-trip binary parts.
94
+ */
95
+ export declare function asciiPartsOf(zip: ZipArchive): ZipPart[];
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The official file-system channel (1.0.0).
3
+ *
4
+ * Every byte this plugin reads or writes goes through the host's `ctx.fs`
5
+ * service (`@deepseek-ai/dsh-fs`): reads arrive as raw byte arrays via
6
+ * `readBytes`, writes leave as UTF-8 text via `writeText` — which is exactly
7
+ * why `buildAsciiZip` keeps generated packages pure ASCII. The plugin itself
8
+ * never touches the file system directly: workspace containment, symlink
9
+ * resolution, and atomic publication all belong to the backend, and this
10
+ * module only adds the Office-tool path policy on top (extension allow-lists,
11
+ * size caps, overwrite refusal, display paths). Writes carry the per-call
12
+ * sandbox policy (`ctx.sandboxPolicy.resolve({ session })` when a confining
13
+ * backend is mounted) so sandboxing backends fence them by the session's
14
+ * effective policy rather than the deployment default.
15
+ */
16
+ import type { Context } from '@deepseek-ai/cordis';
17
+ import type { FileSystem, FsTarget } from '@deepseek-ai/dsh-fs';
18
+ import type { ToolRunContext } from '@deepseek-ai/dsh-tools';
19
+ /** Cap for text materialized into a single tool result. */
20
+ export declare const MAX_TEXT_CHARS = 200000;
21
+ /** Cap for worksheet cells materialized into a single tool result. */
22
+ export declare const MAX_READ_CELLS = 200000;
23
+ /** Cap for worksheet cells accepted by one create/update call. */
24
+ export declare const MAX_WRITE_CELLS = 200000;
25
+ /** The slice of the Cordis context this suite consumes: the official fs service. */
26
+ export interface FsContext {
27
+ fs: FileSystem;
28
+ }
29
+ /**
30
+ * The per-call sandbox execution policy `ctx.fs.writeText` accepts as its
31
+ * fifth parameter (`SandboxExecutionPolicy` from `@deepseek-ai/dsh-sandbox`):
32
+ * a sandboxing backend fences the write by it, the bare backend ignores it,
33
+ * and omitting it leaves the backend its deployment default — whose writable
34
+ * roots need not include this session's workspace. Derived from the official
35
+ * `writeText` signature so the plugin never depends on `@deepseek-ai/dsh-sandbox`
36
+ * directly.
37
+ */
38
+ export type OfficeWritePolicy = NonNullable<Parameters<FileSystem['writeText']>[4]>;
39
+ /**
40
+ * Resolve the per-call write policy the way the official fs tool layer does:
41
+ * skip when the mounted backend does not confine (`fs.sandboxMode` undefined),
42
+ * otherwise ask the shared `sandboxPolicy` service for the calling session.
43
+ */
44
+ export declare function resolveWritePolicy(ctx: Context & FsContext, exec: ToolRunContext): Promise<OfficeWritePolicy | undefined>;
45
+ export interface ResolvedOfficePath {
46
+ /** The path exactly as the model passed it. */
47
+ input: string;
48
+ /** The backend's stable target for every subsequent operation. */
49
+ target: FsTarget;
50
+ /** The backend's canonical absolute process path (display and extension source). */
51
+ absolute: string;
52
+ /** Path rendered back to the model (workspace-relative when possible). */
53
+ display: string;
54
+ /** Lowercased extension including the leading dot. */
55
+ ext: string;
56
+ }
57
+ /**
58
+ * Resolve one model-supplied path to a workspace-confined backend target.
59
+ *
60
+ * @param exec - the running tool call (carries the session cwd + abort signal).
61
+ * @param rawPath - the model-supplied path string.
62
+ * @param allowedExts - acceptable lowercased extensions WITH dots (e.g. `.docx`).
63
+ * @param mustExist - when true, stat the target and refuse anything but a regular file.
64
+ * @param writePolicy - the per-call write policy, when a sandbox controller is
65
+ * mounted; its `workspaceRoot` is the authoritative workspace the write is
66
+ * fenced to, so relative paths resolve against it exactly like the official
67
+ * fs tools do. Omitted (reads, bare backends) keeps the session cwd.
68
+ */
69
+ export declare function resolveOfficePath(exec: ToolRunContext, ctx: FsContext, rawPath: string, allowedExts: readonly string[], mustExist: boolean, writePolicy?: OfficeWritePolicy): Promise<ResolvedOfficePath>;
70
+ /**
71
+ * Read a bounded Office file through the backend, observing tool-call
72
+ * cancellation and refusing files above the hard cap before any transfer.
73
+ */
74
+ export declare function readOfficeBytes(exec: ToolRunContext, ctx: FsContext, target: FsTarget): Promise<{
75
+ bytes: Uint8Array;
76
+ sizeBytes: number;
77
+ }>;
78
+ /**
79
+ * Publish one generated package: the text is pure ASCII (asserted by the zip
80
+ * planner), so the backend's atomic UTF-8 write lands it on disk
81
+ * byte-identical. The per-call write policy rides along as `writeText`'s
82
+ * fifth parameter so a sandboxing backend fences the write by the session's
83
+ * effective policy instead of its deployment default. Returns the on-disk size.
84
+ */
85
+ export declare function saveOfficeText(exec: ToolRunContext, ctx: FsContext, target: FsTarget, text: string, writePolicy?: OfficeWritePolicy): Promise<number>;
86
+ /**
87
+ * Reject an overwrite when `overwrite` is false and the target already
88
+ * exists. Callers use this BEFORE doing expensive generation so the model
89
+ * gets a fast refusal instead of wasted work.
90
+ */
91
+ export declare function assertMayCreate(exec: ToolRunContext, ctx: FsContext, target: FsTarget, overwrite: boolean): Promise<void>;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Intrinsic image dimensions from file headers (1.0.0). PNG, JPEG, and GIF
3
+ * expose their pixel size in the first few hundred of bytes, so the linked
4
+ * image placement can default to natural size — and compute contain/cover
5
+ * crops — by reading the workspace file through the official fs channel and
6
+ * inspecting bytes directly. No image decoder, no dependency, no signal.
7
+ */
8
+ /** Returned dimensions in pixels; undefined when the bytes are not a recognized image. */
9
+ export interface ImageSize {
10
+ width: number;
11
+ height: number;
12
+ }
13
+ /** Sniff the pixel size of a PNG/JPEG/GIF byte buffer; undefined otherwise. */
14
+ export declare function sniffImageSize(bytes: Uint8Array): ImageSize | undefined;
@@ -1,18 +1,20 @@
1
1
  /**
2
2
  * dsh-office-tools host plugin.
3
3
  *
4
- * Registers seven model-facing tools on `ctx.tools`:
4
+ * Registers eight model-facing tools on `ctx.tools`:
5
5
  *
6
- * word_create / word_read
6
+ * word_create / word_read / word_update
7
7
  * excel_create / excel_read / excel_update
8
8
  * ppt_create / ppt_read
9
9
  *
10
10
  * All file access is confined to the calling agent's session workspace and
11
- * every registration is wrapped in `ctx.effect` so Cordis disposes the tools
12
- * with the plugin fiber.
11
+ * flows exclusively through the official `ctx.fs` service — reads as raw
12
+ * bytes, writes as UTF-8 text (generated packages are pure ASCII by
13
+ * construction). Every registration is wrapped in `ctx.effect` so Cordis
14
+ * disposes the tools with the plugin fiber.
13
15
  *
14
- * The PowerPoint pair is config-gated: dedicated presentation plugins such as
15
- * dsh-ppt register a colliding `ppt_create`, and DSH refuses duplicate tool
16
+ * The PowerPoint pair is config-gated: dedicated presentation plugins such
17
+ * as dsh-ppt register a colliding `ppt_create`, and DSH refuses duplicate tool
16
18
  * names at startup, so profiles running one of those set `enablePptTools: false`
17
19
  * to load this plugin for Word/Excel only.
18
20
  */
@@ -20,7 +22,7 @@ import type { Context } from '@deepseek-ai/cordis';
20
22
  import z from '@deepseek-ai/schemastery';
21
23
  /** Plugin identity for cordis.yml rows. */
22
24
  export declare const name = "dsh-office-tools";
23
- /** The tool registry is the only runtime service this plugin requires. */
25
+ /** The tool registry and the official filesystem service are the only runtime services required. */
24
26
  export declare const inject: string[];
25
27
  /** Host plugin configuration, validated at load by the Loader. */
26
28
  export interface Config {
@@ -8,9 +8,21 @@
8
8
  * inside it, and a realpath check on the nearest existing ancestor closes the
9
9
  * symlink escape hatch.
10
10
  */
11
+ import JSZip from 'jszip';
11
12
  import type { ToolRunContext } from '@deepseek-ai/dsh-tools';
12
13
  /** Hard cap for reading an existing Office file into memory. */
13
14
  export declare const MAX_OFFICE_FILE_BYTES: number;
15
+ /**
16
+ * Declared uncompressed ceiling for one zip entry inside an Office file. The
17
+ * 50 MiB read cap only bounds the COMPRESSED bytes; deflate can expand a file
18
+ * a thousandfold, so {@link loadZipGuarded} also checks the archive's own
19
+ * declared sizes before any entry is inflated.
20
+ */
21
+ export declare const MAX_ZIP_ENTRY_BYTES: number;
22
+ /** Declared uncompressed ceiling summed over all entries of one archive. */
23
+ export declare const MAX_ZIP_TOTAL_BYTES: number;
24
+ /** Maximum entries (files + directories) in one archive. */
25
+ export declare const MAX_ZIP_ENTRIES = 100000;
14
26
  /** Cap for text materialized into a single tool result. */
15
27
  export declare const MAX_TEXT_CHARS = 200000;
16
28
  /** Cap for worksheet cells materialized into a single tool result. */
@@ -43,6 +55,30 @@ export declare function readOfficeBuffer(path: string, signal: AbortSignal): Pro
43
55
  buffer: Buffer;
44
56
  sizeBytes: number;
45
57
  }>;
58
+ /** Overridable budgets so tests can trip the guard with tiny values. */
59
+ export interface ZipGuardLimits {
60
+ maxEntryBytes?: number;
61
+ maxTotalBytes?: number;
62
+ maxEntries?: number;
63
+ }
64
+ /**
65
+ * Load a zip-backed Office file with jszip and refuse archives whose central
66
+ * directory already declares more uncompressed content than we are ever
67
+ * willing to inflate (the zip-bomb guard), or more entries than we will walk.
68
+ * Returns the loaded instance so callers never parse the buffer twice.
69
+ */
70
+ export declare function loadZipGuarded(buffer: Buffer, signal: AbortSignal, limits?: ZipGuardLimits): Promise<JSZip>;
71
+ /**
72
+ * OOXML parts are plain element trees; a DOCTYPE/ENTITY declaration is never
73
+ * legitimate in one. Our extractors never resolve entities, but we refuse to
74
+ * look at such a part at all so entity-expansion payloads die at the door.
75
+ */
76
+ export declare function assertNoXmlDtd(xml: string, label: string): void;
77
+ /**
78
+ * Read one zip part as text and refuse DTD/entity-bearing XML before any
79
+ * caller parses it. Returns null when the archive has no such part.
80
+ */
81
+ export declare function readZipXmlPart(zip: JSZip, name: string, signal: AbortSignal): Promise<string | null>;
46
82
  /**
47
83
  * Write a buffer through a same-directory temp file + rename so a failed
48
84
  * generation never leaves a half-written Office document behind.
@@ -1,8 +1,13 @@
1
1
  /**
2
- * Excel (.xlsx) tools over SheetJS: `excel_create` writes a new workbook,
3
- * `excel_read` materializes sheets as rows of scalar cells, and
4
- * `excel_update` replaces/creates whole sheets and/or writes individual cell
5
- * values into an existing workbook.
2
+ * Excel (.xlsx) tools over the self-contained OOXML container (1.0.0):
3
+ * `excel_create` writes a new workbook, `excel_read` materializes sheets as
4
+ * rows of scalar cells, and `excel_update` replaces/creates whole sheets
5
+ * and/or writes individual cell values into an existing workbook. Reads
6
+ * accept any real-world package (STORE and DEFLATE); writes and rewrites are
7
+ * ASCII-safe STORE packages published through the official fs channel, with
8
+ * strings written inline and '=…' strings materialized as real `<f>` formula
9
+ * cells exactly like the SheetJS-era behavior the tests pin.
6
10
  */
7
11
  import type { Context } from '@deepseek-ai/cordis';
8
- export declare function registerExcelTools(ctx: Context): () => void;
12
+ import type { FsContext } from '../fschannel.ts';
13
+ export declare function registerExcelTools(ctx: Context & FsContext): () => void;
@@ -1,8 +1,69 @@
1
1
  /**
2
- * PowerPoint (.pptx) tools: `ppt_create` builds slide decks with pptxgenjs
3
- * (text, bullets, speaker notes, and PNG/JPG/GIF images); `ppt_read` unzips a
4
- * deck with JSZip and extracts paragraph text, speaker notes, and per-slide
5
- * image counts without any native dependency.
2
+ * PowerPoint (.pptx) tools over the self-contained OOXML container (1.0.0).
3
+ *
4
+ * `ppt_create` builds a widescreen deck as an ASCII-safe STORE package
5
+ * published through the official fs channel — no third-party library, no
6
+ * binary package parts. Pictures are LINKED (`a:blip r:link`): the model
7
+ * fully controls placement (x/y/w/h inches, contain/cover cropping, alt
8
+ * text), the image file stays a workspace artifact, and intrinsic sizes are
9
+ * sniffed from PNG/JPEG/GIF headers so omitted dimensions default to natural
10
+ * size. The create result echoes every element's landing geometry, and both
11
+ * tools render a text wireframe sketch so the model can SEE the composition.
12
+ *
13
+ * `ppt_read` reads any real-world deck and returns, per slide in document
14
+ * order: paragraphs, tables, notes, image counts/alt texts — plus an
15
+ * `elements` array with every shape's bounding box in inches and the deck's
16
+ * canvas size. True raster rendering stays out of scope on purpose: it would
17
+ * need a headless renderer (a subprocess plus a binary dependency), which is
18
+ * exactly what the dependency-free, no-commands contract forbids.
6
19
  */
7
20
  import type { Context } from '@deepseek-ai/cordis';
8
- export declare function registerPptTools(ctx: Context): () => void;
21
+ import type { ToolRunContext } from '@deepseek-ai/dsh-tools';
22
+ import type { FsContext, ResolvedOfficePath } from '../fschannel.ts';
23
+ interface SlideImageSpec {
24
+ path: string;
25
+ x?: number;
26
+ y?: number;
27
+ w?: number;
28
+ h?: number;
29
+ sizing?: 'contain' | 'cover';
30
+ alt?: string;
31
+ }
32
+ interface SlideSpec {
33
+ title?: string;
34
+ paragraphs?: string[];
35
+ bullets?: string[];
36
+ notes?: string;
37
+ images?: SlideImageSpec[];
38
+ }
39
+ interface PptCreateArgs {
40
+ path: string;
41
+ title?: string;
42
+ slides?: SlideSpec[];
43
+ overwrite?: boolean;
44
+ }
45
+ /** One placed element, echoed by create and parsed back by read. */
46
+ export interface SlideElementBox {
47
+ type: 'text' | 'bullets' | 'image' | 'table';
48
+ xIn: number;
49
+ yIn: number;
50
+ wIn: number;
51
+ hIn: number;
52
+ text?: string;
53
+ items?: string[];
54
+ alt?: string;
55
+ path?: string;
56
+ sizing?: 'contain' | 'cover';
57
+ }
58
+ /** Build the whole deck as an ASCII-safe package plus the layout echo. */
59
+ export declare function buildPptxText(args: PptCreateArgs, exec: ToolRunContext, ctx: FsContext, deck: ResolvedOfficePath): Promise<{
60
+ text: string;
61
+ layout: Array<{
62
+ index: number;
63
+ elements: SlideElementBox[];
64
+ }>;
65
+ }>;
66
+ /** Render one slide as an ASCII wireframe: the canvas frame plus boxes. */
67
+ export declare function sketchSlide(widthIn: number, heightIn: number, elements: Array<Pick<SlideElementBox, 'type' | 'xIn' | 'yIn' | 'wIn' | 'hIn' | 'text'>>): string;
68
+ export declare function registerPptTools(ctx: Context & FsContext): () => void;
69
+ export {};
@@ -1,6 +1,9 @@
1
1
  /**
2
- * Shared schema fragments and cell types for the Office tool suite. Keeping
3
- * the schemas in one place keeps the seven tool contracts consistent.
2
+ * Shared schema fragments, cell types, and the ASCII-safe XML encoders for
3
+ * the Office tool suite. Keeping the schemas in one place keeps the eight
4
+ * tool contracts consistent; the encoders guarantee that generated XML stays
5
+ * pure ASCII (non-ASCII text becomes decimal character references), which is
6
+ * the invariant the ASCII-safe zip writer builds on.
4
7
  */
5
8
  /** One cell value accepted by the Excel tools. */
6
9
  export declare const CELL_VALUE_SCHEMA: {
@@ -46,3 +49,12 @@ export declare const FILE_RESULT_SCHEMA: {
46
49
  };
47
50
  export type CellValue = string | number | boolean | null;
48
51
  export type CellRow = CellValue[];
52
+ /** Escape text for XML element content, keeping the output pure ASCII. */
53
+ export declare function encodeXmlText(value: string): string;
54
+ /** Escape text for a double-quoted XML attribute value, keeping it pure ASCII. */
55
+ export declare function encodeXmlAttribute(value: string): string;
56
+ /**
57
+ * Decode the XML entities that can legally appear in OOXML text content: the
58
+ * five predefined names plus decimal/hex character references.
59
+ */
60
+ export declare function decodeXmlEntities(value: string): string;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `word_update`: append paragraphs, bullets, and one table to an existing
3
+ * .docx (1.0.0). The appended body children are generated by the same part
4
+ * builder word_create uses, then spliced into the document's body right
5
+ * before its trailing `<w:sectPr>`. The archive is re-emitted as an
6
+ * ASCII-safe STORE package through the official fs channel, so updates are
7
+ * limited to packages whose every part can round-trip as ASCII text — always
8
+ * true for files these tools write, and for any OOXML package that carries
9
+ * no binary media.
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ import type { FsContext } from '../fschannel.ts';
13
+ export declare function registerWordUpdate(ctx: Context & FsContext): () => void;
@@ -1,6 +1,66 @@
1
1
  /**
2
- * Word (.docx) tools: `word_create` builds a styled document with the `docx`
3
- * package; `word_read` extracts plain text with `mammoth`.
2
+ * Word (.docx) tools over the self-contained OOXML container (1.0.0):
3
+ * `word_create` builds a styled document part set directly (no third-party
4
+ * library — the package is an ASCII-safe STORE zip written through the
5
+ * official fs channel); `word_read` extracts plain text or markdown with the
6
+ * same in-house regex extractor whose behavior is pinned to mammoth 1.11.0's
7
+ * raw-text output by `tests/word-parity.spec.ts`.
4
8
  */
5
9
  import type { Context } from '@deepseek-ai/cordis';
6
- export declare function registerWordTools(ctx: Context): () => void;
10
+ import type { FsContext } from '../fschannel.ts';
11
+ export interface WordCreateArgs {
12
+ path: string;
13
+ title?: string;
14
+ paragraphs?: string[];
15
+ bullets?: string[];
16
+ table?: {
17
+ headers: string[];
18
+ rows: string[][];
19
+ };
20
+ overwrite?: boolean;
21
+ }
22
+ export interface WordUpdateArgs {
23
+ path: string;
24
+ paragraphs?: string[];
25
+ bullets?: string[];
26
+ table?: {
27
+ headers: string[];
28
+ rows: string[][];
29
+ };
30
+ }
31
+ export declare function wordCreateCounts(args: WordUpdateArgs & {
32
+ title?: string;
33
+ }): {
34
+ paragraphs: number;
35
+ cells: number;
36
+ };
37
+ /** Build the complete .docx package as an ASCII-safe zip string. */
38
+ export declare function buildDocxText(args: WordUpdateArgs & {
39
+ title?: string;
40
+ }): string;
41
+ /**
42
+ * Extract mammoth-compatible plain text from a `word/document.xml` string:
43
+ * every paragraph — body, table cell, hyperlink, or textbox — contributes its
44
+ * runs followed by `\n\n`, including the final and empty paragraphs.
45
+ */
46
+ export declare function extractDocxText(documentXml: string): string;
47
+ /**
48
+ * Extract structured markdown from a `word/document.xml` string: Title and
49
+ * Heading1-6 render as `#`..`######`, numbered/bullet paragraphs as indented
50
+ * `- ` items, tables as markdown tables, everything else as plain blocks —
51
+ * inline rules identical to {@link extractDocxText}.
52
+ */
53
+ export declare function extractDocxMarkdown(documentXml: string): string;
54
+ /**
55
+ * Body children for an append, generated by the same part builder
56
+ * word_create uses (so paragraphs, bullets, and tables come out identical)
57
+ * and extracted from the throwaway document minus its trailing `<w:sectPr>`.
58
+ */
59
+ export declare function buildAppendFragment(args: WordUpdateArgs): string;
60
+ /**
61
+ * Splice new body children into `word/document.xml`: appended content goes
62
+ * right before the trailing `<w:sectPr>` (page setup must stay the last body
63
+ * child), or before `</w:body>` when the document has no section properties.
64
+ */
65
+ export declare function appendBeforeSectPr(documentXml: string, addition: string): string;
66
+ export declare function registerWordTools(ctx: Context & FsContext): () => void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-office-tools",
3
- "version": "0.2.0",
4
- "description": "DSH model-facing Office tools: create, read, and update Word (.docx), Excel (.xlsx), and PowerPoint (.pptx) files inside the session workspace.",
3
+ "version": "1.0.2",
4
+ "description": "DSH model-facing Office tools: create, read, and update Word (.docx), Excel (.xlsx), and PowerPoint (.pptx) files inside the session workspace, entirely through the official DSH filesystem service.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
@@ -16,10 +16,20 @@
16
16
  "dsh": {
17
17
  "bundle": {
18
18
  "patch": "./cordis.patch.yml"
19
+ },
20
+ "compatibility": {
21
+ "dsh": ">=0.1.0-rc.6",
22
+ "dshReleases": {
23
+ "0.1.1-rc.2": "compatible",
24
+ "0.1.2-alpha.4": "compatible",
25
+ "0.1.2-alpha.5": "compatible",
26
+ "0.1.2-rc.1": "compatible"
27
+ }
19
28
  }
20
29
  },
21
30
  "files": [
22
- "lib",
31
+ "lib/*.js",
32
+ "lib/types",
23
33
  "cordis.patch.yml",
24
34
  "dsh.plugin.json",
25
35
  "README.md",
@@ -28,16 +38,18 @@
28
38
  ],
29
39
  "scripts": {
30
40
  "build": "node build.mjs",
31
- "typecheck": "tsc --noEmit",
41
+ "types": "tsc -p tsconfig.json",
42
+ "typecheck": "tsc --noEmit -p tsconfig.json",
32
43
  "test": "vitest run",
33
- "check": "pnpm run typecheck && pnpm run test && pnpm run build"
44
+ "check": "pnpm run typecheck && pnpm run types && pnpm run test && pnpm run build"
34
45
  },
35
46
  "peerDependencies": {
36
47
  "@deepseek-ai/cordis": "^4.0.1",
37
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
38
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
39
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
40
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6"
48
+ "@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0",
49
+ "@deepseek-ai/dsh-fs": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0",
50
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0",
51
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0",
52
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0"
41
53
  },
42
54
  "peerDependenciesMeta": {
43
55
  "@deepseek-ai/cordis": {
@@ -46,6 +58,9 @@
46
58
  "@deepseek-ai/dsh-agent": {
47
59
  "optional": true
48
60
  },
61
+ "@deepseek-ai/dsh-fs": {
62
+ "optional": true
63
+ },
49
64
  "@deepseek-ai/dsh-llm": {
50
65
  "optional": true
51
66
  },
@@ -58,20 +73,18 @@
58
73
  },
59
74
  "devDependencies": {
60
75
  "@deepseek-ai/cordis": "^4.0.1",
61
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
62
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
63
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
64
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
65
- "@deepseek-ai/schemastery": "^3.18.1",
76
+ "@deepseek-ai/dsh-agent": "0.1.2-rc.1",
77
+ "@deepseek-ai/dsh-fs": "0.1.2-rc.1",
78
+ "@deepseek-ai/dsh-llm": "0.1.2-rc.1",
79
+ "@deepseek-ai/dsh-session": "0.1.2-rc.1",
80
+ "@deepseek-ai/dsh-tools": "0.1.2-rc.1",
81
+ "@deepseek-ai/schemastery": "^3.18.2",
66
82
  "@types/node": "^24.0.0",
67
- "docx": "^9.5.1",
68
83
  "esbuild": "^0.25.0",
69
84
  "jszip": "^3.10.1",
70
- "mammoth": "^1.11.0",
71
- "pptxgenjs": "^4.0.1",
72
85
  "typescript": "^5.6.0",
73
86
  "vitest": "^4.1.8",
74
- "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
87
+ "xlsx": "^0.18.5"
75
88
  },
76
89
  "author": "kw78",
77
90
  "homepage": "https://github.com/kw78/dsh-office-tools#readme",
@@ -101,5 +114,47 @@
101
114
  "publishConfig": {
102
115
  "access": "public"
103
116
  },
104
- "packageManager": "pnpm@11.7.0"
117
+ "packageManager": "pnpm@10.34.5",
118
+ "dshWorkshop": {
119
+ "schema": "omdsh-workshop-package/v1",
120
+ "type": "plugin",
121
+ "integration": {
122
+ "protocol": "harness-profile",
123
+ "artifact": "cordis.patch.yml"
124
+ },
125
+ "install": {
126
+ "mode": "transactional",
127
+ "adapter": "profile-bundle",
128
+ "failurePolicy": "generation-rollback",
129
+ "touchesCurrentBeforeActivation": false
130
+ },
131
+ "lifecycle": {
132
+ "activation": "restart-profile",
133
+ "dispose": "supported"
134
+ },
135
+ "permissions": [
136
+ "files:workspace-read",
137
+ "files:workspace-write"
138
+ ],
139
+ "compatibility": {
140
+ "dshVersions": [
141
+ "0.1.1-rc.2",
142
+ "0.1.2-alpha.4",
143
+ "0.1.2-alpha.5",
144
+ "0.1.2-rc.1"
145
+ ]
146
+ },
147
+ "capability": {
148
+ "id": "word_create",
149
+ "kind": "tool",
150
+ "invocation": "word_create({\"path\": \"hub-check.docx\", \"paragraphs\": [\"dsh-hub intake check\"]}) in a session with workspace cwd",
151
+ "expected": "a real .docx (first two bytes PK) exists at the workspace path; the result reports sizeBytes > 0; word_read returns 'dsh-hub intake check'"
152
+ },
153
+ "evidence": {
154
+ "install": null,
155
+ "failureIsolation": null,
156
+ "hotReload": null,
157
+ "remove": null
158
+ }
159
+ }
105
160
  }