@copylee/dsh-office 0.1.0

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,39 @@
1
+ /**
2
+ * The accent colour shared by the copylee DSH plugins (docs/ui-spec.md).
3
+ *
4
+ * One choice — terracotta orange by default, blue or black — is kept in the
5
+ * browser under a key every plugin reads, and painted as two CSS variables on
6
+ * `<body>`, where the Host keeps its own theme variables. A plugin's styles
7
+ * only ever write `var(--cl-accent)` / `var(--cl-accent-ink)`, so changing the
8
+ * colour in any plugin's settings recolours all of them at once.
9
+ *
10
+ * This file is identical in every plugin; change it in all of them together.
11
+ */
12
+ import * as React from 'react';
13
+ export type Accent = 'orange' | 'blue' | 'black';
14
+ export declare const ACCENTS: Record<Accent, {
15
+ name: string;
16
+ accent: string;
17
+ ink: string;
18
+ }>;
19
+ export declare const ACCENT_IDS: Accent[];
20
+ export declare const DEFAULT_ACCENT: Accent;
21
+ /** Use these in styles; the fallbacks cover the moment before the first paint. */
22
+ export declare const ACCENT: string;
23
+ export declare const ACCENT_INK: string;
24
+ export declare function isAccent(value: unknown): value is Accent;
25
+ /** The choice made in this browser, or null while none has been made. */
26
+ export declare function storedAccent(): Accent | null;
27
+ export declare function readAccent(): Accent;
28
+ /** Choose the accent for every plugin. */
29
+ export declare function writeAccent(accent: Accent): void;
30
+ /** Paint the current accent and keep it current. Returns the undo for plugin disposal. */
31
+ export declare function installAccent(): () => void;
32
+ export declare function useAccent(): [Accent, (accent: Accent) => void];
33
+ /** A row of round swatches: one of them is always chosen. */
34
+ export declare function AccentPicker({ label, hint, names }: {
35
+ label?: string;
36
+ hint?: string;
37
+ /** Colour names in the page's language; the built-in ones are Chinese. */
38
+ names?: Partial<Record<Accent, string>>;
39
+ }): React.ReactElement;
@@ -0,0 +1,39 @@
1
+ type RemoteResult<T> = {
2
+ ok: true;
3
+ value: T;
4
+ } | {
5
+ ok: false;
6
+ error: {
7
+ code?: string;
8
+ message: string;
9
+ };
10
+ };
11
+ interface NamespaceView {
12
+ ns: string;
13
+ value: unknown;
14
+ revision: number;
15
+ }
16
+ interface ClientContext {
17
+ effect(execute: () => (() => void) | void, label?: string): void;
18
+ slots: {
19
+ inject(name: string, register: () => unknown): void;
20
+ register(meta: Record<string, unknown>, render: (props: unknown) => unknown): unknown;
21
+ };
22
+ remote: {
23
+ $on?(event: string, listener: (...args: unknown[]) => void): () => void;
24
+ settings: {
25
+ describe(): Promise<RemoteResult<{
26
+ writable: boolean;
27
+ namespaces: NamespaceView[];
28
+ }>>;
29
+ mutate(ns: string, ops: {
30
+ op: 'set' | 'unset';
31
+ path: string[];
32
+ value?: unknown;
33
+ }[], expectedRevision?: number): Promise<RemoteResult<unknown>>;
34
+ };
35
+ };
36
+ }
37
+ export declare const inject: string[];
38
+ export declare function apply(ctx: ClientContext): void;
39
+ export {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Give the "Office" row in the DSH settings navigation a document icon
3
+ * instead of the shell's fallback gear.
4
+ *
5
+ * `settings.section` registrations only carry `id`, `order` and `label`; the
6
+ * shell picks icons for built-in ids only. So the row is marked by its label
7
+ * and CSS swaps the glyph (Lucide file-text, drawn as a currentColor mask so
8
+ * hover / active colors still apply).
9
+ */
10
+ export declare const navIconCss = "\n[data-dsh-office-settings-nav] > svg:first-child { display: none; }\n[data-dsh-office-settings-nav]::before {\n content: '';\n flex: none;\n width: 16px;\n height: 16px;\n background: currentColor;\n -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z'/%3E%3Cpath d='M14 2v4a2 2 0 0 0 2 2h4'/%3E%3Cpath d='M10 9H8'/%3E%3Cpath d='M16 13H8'/%3E%3Cpath d='M16 17H8'/%3E%3C/svg%3E\") center / contain no-repeat;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z'/%3E%3Cpath d='M14 2v4a2 2 0 0 0 2 2h4'/%3E%3Cpath d='M10 9H8'/%3E%3Cpath d='M16 13H8'/%3E%3Cpath d='M16 17H8'/%3E%3C/svg%3E\") center / contain no-repeat;\n}\n";
11
+ /** Keep the marker on the settings-nav button labelled `label`; returns a disposer. */
12
+ export declare function registerNavIcon(label: string): () => void;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * What the helper reports, turned into the short plain text the model reads.
3
+ * Kept terse: every line here is paid for on each later request of the turn.
4
+ */
5
+ export type AppKind = 'word' | 'excel' | 'ppt';
6
+ export declare const APP_NAMES: Record<AppKind, string>;
7
+ export interface DocInfo {
8
+ app: AppKind;
9
+ name: string;
10
+ path: string | null;
11
+ saved: boolean;
12
+ active: boolean;
13
+ readOnly?: boolean;
14
+ how?: 'attached' | 'opened' | 'created';
15
+ }
16
+ export interface AppStatus {
17
+ app: AppKind;
18
+ installed: boolean;
19
+ running: boolean;
20
+ version?: string;
21
+ error?: string;
22
+ documents: DocInfo[];
23
+ }
24
+ export interface WordItem {
25
+ i: number;
26
+ text?: string;
27
+ style?: string;
28
+ level?: number;
29
+ to?: number;
30
+ table?: number;
31
+ size?: string;
32
+ }
33
+ export interface WordRead {
34
+ paragraphs: number;
35
+ tables: number;
36
+ pages?: number;
37
+ from?: number;
38
+ items?: WordItem[];
39
+ more?: string;
40
+ table?: number;
41
+ cells?: Array<Array<string | null>>;
42
+ }
43
+ export interface ExcelRead {
44
+ sheets: Array<{
45
+ name: string;
46
+ used: string;
47
+ charts?: number;
48
+ }>;
49
+ sheet: string;
50
+ range: string;
51
+ values: unknown[][];
52
+ formulas?: Array<{
53
+ cell: string;
54
+ formula: string;
55
+ }>;
56
+ clipped?: string;
57
+ }
58
+ export interface PptRead {
59
+ slides: number;
60
+ width: number;
61
+ height: number;
62
+ items: Array<{
63
+ slide: number;
64
+ layout?: string;
65
+ shapes: Array<{
66
+ name: string;
67
+ type: string;
68
+ box: number[];
69
+ text?: string;
70
+ }>;
71
+ }>;
72
+ }
73
+ export interface EditResult {
74
+ done: string[];
75
+ total: number;
76
+ failed?: {
77
+ index: number;
78
+ op: string;
79
+ error: string;
80
+ code: string;
81
+ };
82
+ }
83
+ export declare function formatStatus(apps: AppStatus[]): string;
84
+ export declare function formatOpened(doc: DocInfo): string;
85
+ export declare function formatWord(read: WordRead): string;
86
+ export declare function formatExcel(read: ExcelRead): string;
87
+ export declare function formatPpt(read: PptRead): string;
88
+ export declare function formatEdit(result: EditResult): string;
@@ -0,0 +1,31 @@
1
+ export interface HelperEvent {
2
+ event: string;
3
+ reason?: string;
4
+ }
5
+ export interface HelperLike {
6
+ call<T = unknown>(cmd: string, args?: Record<string, unknown>, timeoutMs?: number): Promise<T>;
7
+ onEvent(listener: (event: HelperEvent) => void): () => void;
8
+ dispose(): void;
9
+ }
10
+ /** A file in helper/ at the package root: one level up from lib/ or src/, two from lib/types/. */
11
+ export declare function helperAssetPath(name: string): string;
12
+ export declare function helperSourcePath(): string;
13
+ /** Compile the helper if this source version has no cached exe yet; returns the exe path. */
14
+ export declare function ensureHelperExe(source?: string): Promise<string>;
15
+ export declare class HelperClient implements HelperLike {
16
+ private readonly log;
17
+ private child;
18
+ private starting;
19
+ private readonly pending;
20
+ private readonly listeners;
21
+ private nextId;
22
+ private disposed;
23
+ constructor(log?: (message: string) => void);
24
+ onEvent(listener: (event: HelperEvent) => void): () => void;
25
+ call<T = unknown>(cmd: string, args?: Record<string, unknown>, timeoutMs?: number): Promise<T>;
26
+ dispose(): void;
27
+ private kill;
28
+ private ensure;
29
+ private start;
30
+ private handle;
31
+ }
@@ -0,0 +1,12 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import z from '@deepseek-ai/schemastery';
3
+ export { DEFAULTS, ENTRY_ID, resolveConfig } from './settings.js';
4
+ export declare const name = "@copylee/dsh-office";
5
+ export declare const inject: string[];
6
+ export declare const STATUS_ROUTE = "/api/dsh-office/status";
7
+ export interface Config {
8
+ showOnOpen?: boolean;
9
+ renderWidth?: number;
10
+ }
11
+ export declare const Config: z<Config>;
12
+ export declare function apply(ctx: Context, config?: Config): void;
@@ -0,0 +1,2 @@
1
+ /** System-prompt section: when to reach for the office_ tools, in as few tokens as will do. */
2
+ export declare function promptText(): string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Plugin settings shared by the host and the settings page: defaults and the
3
+ * tolerant reader for the Loader entry config. No schemastery here so the
4
+ * browser bundle stays small.
5
+ */
6
+ /** The Loader entry id (settings namespace); see cordis.patch.yml. */
7
+ export declare const ENTRY_ID = "copylee-office";
8
+ export interface Settings {
9
+ /** Bring the document's window to the front when office_open opens it. */
10
+ showOnOpen: boolean;
11
+ /** Width in pixels of the pictures office_render gives the model. */
12
+ renderWidth: number;
13
+ }
14
+ export declare const DEFAULTS: Settings;
15
+ /** Unwrap `.volatile()` refs (`{ get() }`) and fall back to defaults for bad values. */
16
+ export declare function resolveConfig(raw: unknown): Settings;
@@ -0,0 +1,24 @@
1
+ import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
2
+ import { defineTool } from '@deepseek-ai/dsh-tools';
3
+ import { type AppKind } from './format.js';
4
+ import type { HelperLike } from './helper-client.js';
5
+ import type { Settings } from './settings.js';
6
+ type ToolDefinition = ReturnType<typeof defineTool>;
7
+ export interface ToolHost {
8
+ helper: HelperLike;
9
+ settings(): Settings;
10
+ /** Persist a picture so the model can see it. */
11
+ saveImage(data: Buffer, name: string): Promise<ImageAttachmentRef>;
12
+ /** Whether the conversation model can look at images. */
13
+ vision(exec: unknown): Promise<boolean>;
14
+ }
15
+ interface Target {
16
+ app?: string;
17
+ doc?: string;
18
+ }
19
+ /** Which app a call is about: said outright, or told from the file name. */
20
+ export declare function appOf(args: Target & {
21
+ path?: string;
22
+ }): AppKind;
23
+ export declare function createTools(host: ToolHost): ToolDefinition[];
24
+ export {};
package/package.json ADDED
@@ -0,0 +1,122 @@
1
+ {
2
+ "name": "@copylee/dsh-office",
3
+ "version": "0.1.0",
4
+ "description": "DeepSeek Harness Office 插件(Windows):AI 直接在你打开的 Word / Excel / PowerPoint 里改文档,改动实时出现在窗口中,你可以同时编辑;读结构、批量编辑、渲染成图核对、保存。",
5
+ "type": "module",
6
+ "author": "copylee",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/copylee711/dsh-office.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/copylee711/dsh-office/issues"
13
+ },
14
+ "homepage": "https://github.com/copylee711/dsh-office#readme",
15
+ "main": "lib/index.js",
16
+ "types": "lib/types/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./lib/types/index.d.ts",
20
+ "default": "./lib/index.js"
21
+ },
22
+ "./client": {
23
+ "types": "./lib/types/client/index.d.ts",
24
+ "default": "./lib/client.js"
25
+ },
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "lib/index.js",
30
+ "lib/client.js",
31
+ "lib/types/**/*.d.ts",
32
+ "helper/OfficeHelper.cs",
33
+ "cordis.patch.yml",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "os": [
38
+ "win32"
39
+ ],
40
+ "dsh": {
41
+ "bundle": {
42
+ "patch": "./cordis.patch.yml"
43
+ },
44
+ "engines": {
45
+ "dsh": ">=0.2.0-rc.2"
46
+ },
47
+ "client": {
48
+ "inject": [
49
+ "@deepseek-ai/dsh-client-connection",
50
+ "@deepseek-ai/dsh-api-remotes",
51
+ "@deepseek-ai/dsh-client-ui-settings",
52
+ "@deepseek-ai/dsh-client-locale"
53
+ ],
54
+ "platform": "web"
55
+ }
56
+ },
57
+ "scripts": {
58
+ "typecheck": "tsc --noEmit",
59
+ "build": "tsc && tsdown",
60
+ "prepare": "pnpm run build",
61
+ "test": "vitest run",
62
+ "pack:check": "pnpm pack --dry-run",
63
+ "smoke": "node scripts/office-smoke.mjs"
64
+ },
65
+ "engines": {
66
+ "node": "^22.19.0 || >=24.0.0"
67
+ },
68
+ "license": "MIT",
69
+ "keywords": [
70
+ "deepseek",
71
+ "deepseek-harness",
72
+ "dsh",
73
+ "dsh-plugin",
74
+ "ai-agent",
75
+ "office",
76
+ "word",
77
+ "excel",
78
+ "powerpoint",
79
+ "docx",
80
+ "xlsx",
81
+ "pptx",
82
+ "com-automation",
83
+ "windows",
84
+ "cordis"
85
+ ],
86
+ "peerDependencies": {
87
+ "@deepseek-ai/cordis": ">=4.0.0 <5",
88
+ "@deepseek-ai/schemastery": ">=3.18.0 <4",
89
+ "react": ">=18.2.0 <19"
90
+ },
91
+ "peerDependenciesMeta": {
92
+ "@deepseek-ai/cordis": {
93
+ "optional": true
94
+ },
95
+ "@deepseek-ai/schemastery": {
96
+ "optional": true
97
+ },
98
+ "react": {
99
+ "optional": true
100
+ }
101
+ },
102
+ "devDependencies": {
103
+ "@deepseek-ai/cordis": "4.0.4",
104
+ "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
105
+ "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
106
+ "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
107
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
108
+ "@deepseek-ai/dsh-session": "0.2.0-rc.2",
109
+ "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
110
+ "@deepseek-ai/schemastery": "3.18.4",
111
+ "@types/node": "^22.20.0",
112
+ "@types/react": "~18.3.31",
113
+ "react": "^18.3.1",
114
+ "tsdown": "^0.22.2",
115
+ "typescript": "^6.0.3",
116
+ "vitest": "^4.1.8"
117
+ },
118
+ "packageManager": "pnpm@10.28.0",
119
+ "publishConfig": {
120
+ "access": "public"
121
+ }
122
+ }