@taloswork/ose-shared 0.1.4 → 0.1.6

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": "@taloswork/ose-shared",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Talos cross-OSE shared library — skills + MCP servers auto-loaded into every OSE (P93 2026-07-27).",
5
5
  "type": "module",
6
6
  "files": [
@@ -10,6 +10,6 @@
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
13
- "_talosPublishHash": "sha256-7or_mcdSHbKlZLnkDnnjf-71hdL7A895HrWVqUGALOo",
14
- "_talosPublishedAt": "2026-09-03T17:22:12.823Z"
13
+ "_talosPublishHash": "sha256-CLI4WbNNvJRRbZYx7qniS0d-cVs5XiJlcU904jHxDww",
14
+ "_talosPublishedAt": "2026-09-06T18:53:54.545Z"
15
15
  }
@@ -147660,6 +147660,7 @@ async function imagesToPptx(imagesBase64Png) {
147660
147660
  }
147661
147661
 
147662
147662
  // packages/core/dist/document/html-pptx-walker.js
147663
+ var DOCUMENT_CAPTURE_DENSITY = 2;
147663
147664
  var LAYOUT_WALKER_JS = `
147664
147665
  (() => {
147665
147666
  const sections = [...document.querySelectorAll("section.slide")];
@@ -148118,13 +148119,22 @@ async function withHeadlessPage(html, fn) {
148118
148119
  if (!chromium) return null;
148119
148120
  const browser2 = await chromium.launch();
148120
148121
  try {
148121
- const page = await browser2.newPage({ viewport: { width: 1280, height: 720 } });
148122
+ const page = await browser2.newPage({
148123
+ viewport: { width: 1280, height: 720 },
148124
+ deviceScaleFactor: DOCUMENT_CAPTURE_DENSITY
148125
+ });
148122
148126
  await page.setContent(html, { waitUntil: "load" });
148123
148127
  await page.evaluate("document.fonts && document.fonts.ready").catch(() => {
148124
148128
  });
148125
148129
  const adapter = {
148126
148130
  evaluate: (script) => page.evaluate(script),
148127
- screenshot: (clip) => page.screenshot({ clip })
148131
+ screenshot: async (clip) => {
148132
+ const png = await page.screenshot({ clip });
148133
+ const w = png.readUInt32BE(16), h = png.readUInt32BE(20);
148134
+ const want = [clip.width * DOCUMENT_CAPTURE_DENSITY, clip.height * DOCUMENT_CAPTURE_DENSITY];
148135
+ if (w !== want[0] || h !== want[1]) throw new Error(`captured ${w}x${h}, expected ${want[0]}x${want[1]}`);
148136
+ return png;
148137
+ }
148128
148138
  };
148129
148139
  return await fn(adapter);
148130
148140
  } finally {
@@ -0,0 +1,30 @@
1
+ © 2025 Anthropic, PBC. All rights reserved.
2
+
3
+ LICENSE: Use of these materials (including all code, prompts, assets, files,
4
+ and other components of this Skill) is governed by your agreement with
5
+ Anthropic regarding use of Anthropic's services. If no separate agreement
6
+ exists, use is governed by Anthropic's Consumer Terms of Service or
7
+ Commercial Terms of Service, as applicable:
8
+ https://www.anthropic.com/legal/consumer-terms
9
+ https://www.anthropic.com/legal/commercial-terms
10
+ Your applicable agreement is referred to as the "Agreement." "Services" are
11
+ as defined in the Agreement.
12
+
13
+ ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the
14
+ contrary, users may not:
15
+
16
+ - Extract these materials from the Services or retain copies of these
17
+ materials outside the Services
18
+ - Reproduce or copy these materials, except for temporary copies created
19
+ automatically during authorized use of the Services
20
+ - Create derivative works based on these materials
21
+ - Distribute, sublicense, or transfer these materials to any third party
22
+ - Make, offer to sell, sell, or import any inventions embodied in these
23
+ materials
24
+ - Reverse engineer, decompile, or disassemble these materials
25
+
26
+ The receipt, viewing, or possession of these materials does not convey or
27
+ imply any license or right beyond those expressly granted above.
28
+
29
+ Anthropic retains all right, title, and interest in these materials,
30
+ including all copyrights, patents, and other intellectual property rights.
@@ -0,0 +1,152 @@
1
+ ---
2
+ name: image-layout
3
+ description: "Use this skill whenever the user wants EXISTING images combined or altered as pixels — 拼图 / 拼接 / 拼一张 / 宫格 / 九宫格 / 长图 / 对比图 / 前后对比 / 加水印 / 加标签 / 加边框 / 裁剪 / 圆角 / 缩放, or stitch / combine / merge images / collage / contact sheet / side-by-side / before-after / watermark / label / crop / round corners / resize. Examples: 'combine these four into one picture', '把生成的几张图拼成九宫格', 'add a caption under each'. Method: HTML+CSS rendered by this skill's bin/html2img.cjs. The mechanical test: if the images do not exist as files yet, this is NOT the skill. Do NOT trigger for: generating NEW images, INCLUDING when the request names a layout word — '生成一张九宫格的图' asks for ONE generated picture whose COMPOSITION is a grid, not for tiling existing files; documents / PDF / PowerPoint (document skill); Xiaohongshu cards (talos-xhs-ops); or upscaling / background removal / content-aware fill, which need a model, not CSS."
4
+ license: Proprietary. LICENSE.txt has complete terms
5
+ ---
6
+
7
+ # Image layout
8
+
9
+ Combining or restyling images that **already exist** — as files on disk. You
10
+ write a layout in HTML+CSS; Chromium renders it to one PNG.
11
+
12
+ **Check first: do the image files exist?** If they do not, stop — the request is
13
+ for generation, not composition, and this skill cannot make imagery. A layout
14
+ word in the request does not change that: "生成一张九宫格的图" asks for ONE
15
+ generated picture whose composition happens to be a grid, the same way "three
16
+ cats" describes a scene rather than an output count. Compose only files you can
17
+ name a path for.
18
+
19
+ There is no ImageMagick / sharp / PIL / ffmpeg to call here, and no `npm i`.
20
+ Do not go looking for one, and do not ask a generation model to "draw a 2×2
21
+ grid" — it redraws the subjects instead of tiling the images you have.
22
+
23
+ ## The one command
24
+
25
+ ```
26
+ bin/html2img.cjs <in.html> <out.png> [--width N] [--max-height N]
27
+ ```
28
+
29
+ - `--width` (default 1280) is the canvas width in CSS pixels. **You declare it**;
30
+ it must equal the sum of your columns plus gaps and padding.
31
+ - The **height is measured** from the rendered page — never compute it. Captions
32
+ of any length, wrapped titles, variable rows all just work.
33
+ - Local images referenced by `<img src>` and CSS `url()` are collected
34
+ automatically and allowlisted; relative paths resolve against the HTML file's
35
+ own directory, and any filename works, spaces and `~ % [ ]` included.
36
+ **Remote URLs are blocked**, and any image that fails to decode fails the whole
37
+ render — you never get a "finished" sheet with a blank cell in it.
38
+ - Needs the Talos app (Chromium loopback). Headless it exits 69 with a clear
39
+ message; do not try to work around that.
40
+ - Bounds: width 160–4096, rendered height ≤ `--max-height` (default 8192, refused
41
+ not cropped), ≤64 local images per render.
42
+ - Output is always PNG.
43
+
44
+ Then deliver the result with `present_file` — one call, the composed image.
45
+
46
+ ## Where the images are
47
+
48
+ Pictures the user attached to the turn are **already on disk**: in `attachments/`
49
+ relative to your working directory (the session workspace), named `image_0.png`,
50
+ `image_1.png`, … — or by their original filename when they had one. The prompt
51
+ also carries an `[Attachment on disk: <path>]` marker for each.
52
+
53
+ ```
54
+ ls attachments/
55
+ ```
56
+
57
+ Start there. **Do not scan the filesystem for them** (`find / -name "*.png"` and
58
+ friends): it is slow, it burns the turn, and the answer was one `ls` away.
59
+ Write your HTML in that same directory so the `<img src>` paths stay short.
60
+
61
+ ## Rules that keep the output right
62
+
63
+ - **Give every cell an explicit pixel size.** The canvas width is arithmetic, and
64
+ arithmetic needs numbers: `cols × cell + (cols−1) × gap + 2 × padding`.
65
+ - **CJK text must lead its font stack with `TalosCJK`**, e.g.
66
+ `font-family: TalosCJK, system-ui, sans-serif`. The renderer injects that face
67
+ when the HTML contains CJK; without it Chinese labels come out blank.
68
+ - **Set an explicit `background`** on `body`. An unstyled page captures as white,
69
+ which is rarely what a dark sheet wants.
70
+ - Write the HTML **next to the images**, or reference them by absolute path.
71
+ - Source images keep their own resolution — a 1024px image in a 512px cell is
72
+ downscaled by the browser. Size cells for the output you want.
73
+
74
+ ## Recipes
75
+
76
+ Each is a starting point; the layout is ordinary CSS, so change it freely.
77
+
78
+ ### Contact sheet / 宫格
79
+
80
+ `cols × cell + (cols−1) × gap + 2 × pad`. Below: 2 × 512 + 20 + 48 = **1092**.
81
+
82
+ ```html
83
+ <!doctype html><html><head><meta charset="utf-8"><style>
84
+ html,body{margin:0;background:#0d0d10}
85
+ body{font-family:TalosCJK,system-ui,-apple-system,sans-serif;color:#e8e8ea}
86
+ h1{margin:0;padding:24px 24px 8px;font-size:34px}
87
+ .sheet{display:grid;grid-template-columns:repeat(2,512px);gap:20px;padding:0 24px 24px}
88
+ figure{margin:0;position:relative}
89
+ figure img{width:512px;height:512px;object-fit:cover;display:block;border-radius:14px}
90
+ .tag{position:absolute;top:14px;left:14px;padding:6px 14px;border-radius:999px;
91
+ background:rgba(0,0,0,.55);font-size:20px;font-weight:600}
92
+ figcaption{padding-top:10px;font-size:20px;color:#c9c9d1;text-align:center}
93
+ </style></head><body>
94
+ <h1>四个方案对比</h1>
95
+ <div class="sheet">
96
+ <figure><img src="a.png"><span class="tag">1</span><figcaption>方案 A</figcaption></figure>
97
+ <figure><img src="b.png"><span class="tag">2</span><figcaption>方案 B</figcaption></figure>
98
+ </div>
99
+ </body></html>
100
+ ```
101
+
102
+ ### Long strip / 长图
103
+
104
+ One column; the height takes care of itself.
105
+
106
+ ```css
107
+ .strip{display:grid;grid-template-columns:1080px;gap:0;padding:0}
108
+ .strip img{width:1080px;height:auto;display:block}
109
+ ```
110
+
111
+ ### Before / after
112
+
113
+ Two cells with labels, or one cell with the "after" clipped to half:
114
+
115
+ ```css
116
+ .pair{display:grid;grid-template-columns:repeat(2,640px);gap:2px}
117
+ /* single-frame variant */
118
+ .overlay{position:relative;width:1280px;height:720px}
119
+ .overlay img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
120
+ .overlay .after{clip-path:inset(0 0 0 50%)}
121
+ .overlay .seam{position:absolute;left:50%;top:0;bottom:0;width:2px;background:#fff}
122
+ ```
123
+
124
+ ### Watermark / label / border
125
+
126
+ ```css
127
+ .mark{position:absolute;right:20px;bottom:20px;font-size:22px;opacity:.75}
128
+ .framed{border:8px solid #fff;box-shadow:0 8px 30px rgba(0,0,0,.35)}
129
+ ```
130
+
131
+ ### Crop, scale, round, filter
132
+
133
+ Cropping and scaling are `object-fit` on a sized box; there is no separate crop
134
+ tool.
135
+
136
+ ```css
137
+ .crop{width:512px;height:512px;object-fit:cover;object-position:center} /* fill + centre-crop */
138
+ .fit {width:512px;height:512px;object-fit:contain;background:#111} /* letterbox, no crop */
139
+ .round{border-radius:24px}
140
+ .grey{filter:grayscale(1) contrast(1.05)}
141
+ ```
142
+
143
+ ## When this is the wrong skill
144
+
145
+ - **Generating a new image** → the media-generation tools. This skill never
146
+ creates imagery, it only arranges what exists.
147
+ - **A PDF / Word / PowerPoint deliverable** → the document skill (`doc-gen`,
148
+ `html2pptx`). Do not build a "poster" here and call it a document.
149
+ - **Xiaohongshu campaign cards** → `talos-xhs-ops` (`render-cards`), which owns
150
+ the 1080×1440 card format and its QA.
151
+ - **Pixel-level algorithms** — background removal, super-resolution,
152
+ content-aware fill. CSS cannot do these; they need a model.
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // apps/admin/data/skills.lib/image-layout/bin-src/html2img.mjs
5
+ var import_promises = require("node:fs/promises");
6
+ var import_node_path2 = require("node:path");
7
+
8
+ // packages/core/dist/document/html-local-assets.js
9
+ var import_node_fs = require("node:fs");
10
+ var import_node_path = require("node:path");
11
+ var import_node_url = require("node:url");
12
+ var IMG_SRC = /(<img\b[^>]*?\bsrc\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
13
+ var CSS_URL = /(url\(\s*)(?:"([^"]*)"|'([^']*)'|([^)\s]+))(\s*\))/gi;
14
+ function isNonLocal(ref) {
15
+ return ref.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(ref) && !ref.toLowerCase().startsWith("file:");
16
+ }
17
+ function resolveLocalAssets(html, baseDir) {
18
+ const found = /* @__PURE__ */ new Set();
19
+ const rewrite = (ref) => {
20
+ const url = ref === void 0 ? void 0 : localFileUrl(ref.trim(), baseDir);
21
+ if (url)
22
+ found.add(url);
23
+ return url;
24
+ };
25
+ const withCss = html.replace(CSS_URL, (match, prefix, dq, sq, bare, suffix) => {
26
+ const url = rewrite(dq ?? sq ?? bare);
27
+ return url ? `${prefix}"${url}"${suffix}` : match;
28
+ });
29
+ const rewritten = withCss.replace(IMG_SRC, (match, prefix, dq, sq, bare) => {
30
+ const url = rewrite(dq ?? sq ?? bare);
31
+ return url ? `${prefix}"${url}"` : match;
32
+ });
33
+ return { html: rewritten, assetFileUrls: [...found] };
34
+ }
35
+ function localFileUrl(ref, baseDir) {
36
+ if (!ref)
37
+ return void 0;
38
+ let path;
39
+ if (ref.toLowerCase().startsWith("file:")) {
40
+ try {
41
+ path = (0, import_node_url.fileURLToPath)(ref);
42
+ } catch {
43
+ return void 0;
44
+ }
45
+ } else if (isNonLocal(ref)) {
46
+ return void 0;
47
+ } else {
48
+ const bare = ref.split(/[?#]/)[0];
49
+ if (!bare)
50
+ return void 0;
51
+ path = (0, import_node_path.isAbsolute)(bare) ? bare : (0, import_node_path.resolve)(baseDir, bare);
52
+ }
53
+ return (0, import_node_fs.existsSync)(path) ? (0, import_node_url.pathToFileURL)(path).href : void 0;
54
+ }
55
+
56
+ // apps/admin/data/skills.lib/image-layout/bin-src/html2img.mjs
57
+ function die(msg, code = 1) {
58
+ process.stderr.write(`html2img: ${msg}
59
+ `);
60
+ process.exit(code);
61
+ }
62
+ function intArg(flag, fallback) {
63
+ const i = process.argv.indexOf(flag);
64
+ if (i < 0) return fallback;
65
+ const value = Number(process.argv[i + 1]);
66
+ if (!Number.isInteger(value)) die(`${flag} needs an integer`, 64);
67
+ return value;
68
+ }
69
+ async function main() {
70
+ const input = process.argv[2];
71
+ const output = process.argv[3];
72
+ if (!input || !output || input.startsWith("--") || output.startsWith("--")) {
73
+ die("usage: html2img <in.html> <out.png> [--width N] [--max-height N]", 64);
74
+ }
75
+ const width = intArg("--width", 1280);
76
+ const maxHeight = intArg("--max-height", void 0);
77
+ const url = process.env.TALOS_PDF_URL;
78
+ if (!url) {
79
+ die("rendering needs the Talos app's Chromium service (TALOS_PDF_URL is not set). Run inside the Talos app.", 69);
80
+ }
81
+ let source;
82
+ try {
83
+ source = await (0, import_promises.readFile)(input, "utf8");
84
+ } catch (e) {
85
+ die(`cannot read ${input}: ${e.message}`, 66);
86
+ }
87
+ const { html, assetFileUrls } = resolveLocalAssets(source, (0, import_node_path2.dirname)((0, import_node_path2.resolve)(input)));
88
+ let res;
89
+ try {
90
+ res = await fetch(url.replace(/\/pdf$/, "/img"), {
91
+ method: "POST",
92
+ headers: {
93
+ "content-type": "application/json",
94
+ ...process.env.TALOS_PDF_TOKEN ? { authorization: `Bearer ${process.env.TALOS_PDF_TOKEN}` } : {}
95
+ },
96
+ body: JSON.stringify({ html, width, ...maxHeight === void 0 ? {} : { maxHeight }, assetFileUrls })
97
+ });
98
+ } catch (e) {
99
+ die(`render request failed: ${e.message}`, 69);
100
+ }
101
+ const text = await res.text();
102
+ if (!res.ok) {
103
+ let detail = text;
104
+ try {
105
+ detail = JSON.parse(text).error ?? text;
106
+ } catch {
107
+ }
108
+ die(`render failed (${res.status}): ${detail}`);
109
+ }
110
+ let image;
111
+ try {
112
+ image = JSON.parse(text).image;
113
+ } catch {
114
+ die("render returned malformed JSON");
115
+ }
116
+ if (typeof image !== "string" || !image) die("render returned no image");
117
+ const dest = (0, import_node_path2.resolve)(output);
118
+ await (0, import_promises.writeFile)(dest, Buffer.from(image, "base64"));
119
+ process.stdout.write(`${JSON.stringify({ path: dest, width, assets: assetFileUrls.length })}
120
+ `);
121
+ }
122
+ main().catch((e) => die(e?.message ?? String(e)));