makaron-persona-look-cli 0.5.5 → 0.5.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/README.md
CHANGED
|
@@ -37,7 +37,7 @@ personlib remote sync --library ./private-library --dry-run --json
|
|
|
37
37
|
personlib remote sync --library ./private-library --json
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
`remote sync` remains owner-only. Open enrollment authorizes Agents to receive their own credentials, but does not expose an anonymous catalog or direct R2 object URLs.
|
|
40
|
+
`remote sync` remains owner-only. It uploads private asset bytes in controlled batches after its manifest write, so a partial network failure can be safely rerun without exposing sources. Open enrollment authorizes Agents to receive their own credentials, but does not expose an anonymous catalog or direct R2 object URLs.
|
|
41
41
|
|
|
42
42
|
## Agent natural-language route
|
|
43
43
|
|
|
@@ -64,6 +64,8 @@ The third route is a paid external generation action. `render --brief` first cre
|
|
|
64
64
|
|
|
65
65
|
`都市`, `urban`, `city`, `city chic`, and `downtown` all retrieve the urban/commute/editorial Look family; users do not need to rewrite `都市风格` as `都市通勤`.
|
|
66
66
|
|
|
67
|
+
`性感`, `sexy`, `sensual`, `夜生活`, `夜店`, `nightlife`, and `约会夜` retrieve owner-reviewed adult evening Looks only; they never use a Look source face as the rendered identity.
|
|
68
|
+
|
|
67
69
|
For an explicit free Persona + Look source-reference pack, retain `preview`:
|
|
68
70
|
|
|
69
71
|
```bash
|
package/bin/personlib.mjs
CHANGED
|
@@ -367,6 +367,7 @@ function scoreLookForBrief(lookRecord, brief) {
|
|
|
367
367
|
addSignals(/晚宴|gala|black-tie|formal/, [["gala", 4], ["evening", 3], ["formal", 3], ["gown", 2], ["soiree", 2]]);
|
|
368
368
|
addSignals(/红毯|red carpet/, [["red carpet", 6], ["gala", 3], ["carpet", 2], ["train", 2], ["gown", 2]]);
|
|
369
369
|
addSignals(/高级派对|party|cocktail|after-party/, [["party", 4], ["cocktail", 3], ["soiree", 3], ["crystal", 1], ["sequin", 1], ["gown", 1]]);
|
|
370
|
+
addSignals(/性感|sexy|sensual|夜生活|夜店|nightlife|club|约会夜|date night/, [["sensual", 6], ["nightlife", 5], ["club", 4], ["bodycon", 4], ["cutout", 3], ["halter", 3], ["ruched", 2], ["sheer", 2], ["satin", 2], ["fitted", 1]]);
|
|
370
371
|
addSignals(/运动休闲|athleisure|运动|sport|sportswear|健身|球类|篮球|网球|跑步|track|训练/, [["athleisure", 5], ["sport", 4], ["track", 3], ["running", 2], ["tennis", 2], ["basketball", 2], ["technical", 1], ["fleece", 1], ["hoodie", 1]]);
|
|
371
372
|
addSignals(/度假|海岛|resort|beach|holiday|pool|yacht|sailing|夏日/, [["resort", 5], ["beach", 5], ["island", 4], ["pool", 4], ["sailing", 4], ["summer", 3], ["linen", 2], ["swim", 2], ["nautical", 2]]);
|
|
372
373
|
return score;
|
|
@@ -156,6 +156,7 @@ function scoreLook(record, brief) {
|
|
|
156
156
|
signals(/晚宴|gala|black-tie|formal/, [["gala", 4], ["evening", 3], ["formal", 3], ["gown", 2], ["soiree", 2]]);
|
|
157
157
|
signals(/红毯|red carpet/, [["red carpet", 6], ["gala", 3], ["carpet", 2], ["train", 2], ["gown", 2]]);
|
|
158
158
|
signals(/高级派对|party|cocktail|after-party/, [["party", 4], ["cocktail", 3], ["soiree", 3], ["crystal", 1], ["sequin", 1], ["gown", 1]]);
|
|
159
|
+
signals(/性感|sexy|sensual|夜生活|夜店|nightlife|club|约会夜|date night/, [["sensual", 6], ["nightlife", 5], ["club", 4], ["bodycon", 4], ["cutout", 3], ["halter", 3], ["ruched", 2], ["sheer", 2], ["satin", 2], ["fitted", 1]]);
|
|
159
160
|
signals(/运动休闲|athleisure|运动|sport|sportswear|健身|球类|篮球|网球|跑步|track|训练/, [["athleisure", 5], ["sport", 4], ["track", 3], ["running", 2], ["tennis", 2], ["basketball", 2], ["technical", 1], ["fleece", 1], ["hoodie", 1]]);
|
|
160
161
|
return score;
|
|
161
162
|
}
|
|
@@ -92,6 +92,7 @@ test("owner sync, self-registration, recommendation, compose, and private previe
|
|
|
92
92
|
{ id: "L-115", display_name: "Hip-Hop Rap", status: "private-staged", look: { family: "hip-hop rap y2k", silhouette: "baggy", garments: "unbranded jersey and cargo trousers", palette: "red black", materials: "denim", accessories: "chain", scene: "neutral studio" }, source_asset_id: "L-115:source" },
|
|
93
93
|
{ id: "L-116", display_name: "Y2K Indigo", status: "private-staged", look: { family: "y2k urban", silhouette: "low rise", garments: "unbranded denim set", palette: "blue silver", materials: "denim", accessories: "chain", scene: "city studio" }, source_asset_id: "L-116:source" },
|
|
94
94
|
{ id: "L-117", display_name: "Y2K Chrome", status: "private-staged", look: { family: "y2k city chic", silhouette: "wide leg", garments: "unbranded chrome jacket", palette: "black silver", materials: "nylon", accessories: "belt", scene: "downtown studio" }, source_asset_id: "L-117:source" },
|
|
95
|
+
{ id: "L-118", display_name: "Adult Sensual Night", status: "private-staged", look: { family: "adult sensual nightlife", silhouette: "fitted bodycon mini", garments: "unbranded black cutout dress", palette: "black", materials: "satin jersey", accessories: "minimal chain", scene: "adult nightlife studio" }, source_asset_id: "L-118:source" },
|
|
95
96
|
{ id: "L-132", display_name: "Metro Leather Transit", status: "private-staged", look: { family: "urban transit", silhouette: "moto", garments: "leather jacket", palette: "black grey", materials: "leather", accessories: "minimal", scene: "metro commuter" }, source_asset_id: "L-132:source" }
|
|
96
97
|
]
|
|
97
98
|
};
|
|
@@ -103,6 +104,7 @@ test("owner sync, self-registration, recommendation, compose, and private previe
|
|
|
103
104
|
{ id: "L-115:source", object_key: "looks/L-115/source.png", sha256: lookHash, mime_type: "image/png" },
|
|
104
105
|
{ id: "L-116:source", object_key: "looks/L-116/source.png", sha256: lookHash, mime_type: "image/png" },
|
|
105
106
|
{ id: "L-117:source", object_key: "looks/L-117/source.png", sha256: lookHash, mime_type: "image/png" },
|
|
107
|
+
{ id: "L-118:source", object_key: "looks/L-118/source.png", sha256: lookHash, mime_type: "image/png" },
|
|
106
108
|
{ id: "L-132:source", object_key: "looks/L-132/source.png", sha256: lookHash, mime_type: "image/png" }
|
|
107
109
|
];
|
|
108
110
|
let response = await worker.fetch(request("/v1/owner/sync/manifest", { method: "POST", headers: { authorization: "Bearer owner-token", "content-type": "application/json" }, body: JSON.stringify({ manifest, assets }) }), env);
|
|
@@ -119,7 +121,7 @@ test("owner sync, self-registration, recommendation, compose, and private previe
|
|
|
119
121
|
assert.equal(agent.enrollment, "open-self-registration");
|
|
120
122
|
const headers = { authorization: `Bearer ${agent.agent_token}`, "content-type": "application/json" };
|
|
121
123
|
response = await worker.fetch(request("/v1/doctor", { headers }), env);
|
|
122
|
-
assert.deepEqual(await response.json(), { ok: true, service: "personlib-worker", version: "1", agent_id: agent.agent_id, d1: "ok", r2_binding: true, look_records:
|
|
124
|
+
assert.deepEqual(await response.json(), { ok: true, service: "personlib-worker", version: "1", agent_id: agent.agent_id, d1: "ok", r2_binding: true, look_records: 5 });
|
|
123
125
|
response = await worker.fetch(request("/v1/recommend", { method: "POST", headers, body: JSON.stringify({ brief: "15 秒成年女性地铁皮夹克通勤广告" }) }), env);
|
|
124
126
|
const recommendation = await response.json();
|
|
125
127
|
assert.equal(response.status, 200);
|
|
@@ -174,6 +176,11 @@ test("owner sync, self-registration, recommendation, compose, and private previe
|
|
|
174
176
|
assert.equal(lookFind.requested_count, 1);
|
|
175
177
|
assert.equal(lookFind.available_count, 1);
|
|
176
178
|
assert.deepEqual(lookFind.assets, [{ role: "look", asset_id: "L-132:source", url: "/v1/assets/L-132%3Asource" }]);
|
|
179
|
+
response = await worker.fetch(request("/v1/find", { method: "POST", headers, body: JSON.stringify({ kind: "look", brief: "性感夜生活成人妆造" }) }), env);
|
|
180
|
+
const sensualLookFind = await response.json();
|
|
181
|
+
assert.equal(response.status, 200);
|
|
182
|
+
assert.equal(sensualLookFind.returned_count, 1);
|
|
183
|
+
assert.deepEqual(sensualLookFind.assets, [{ role: "look", asset_id: "L-118:source", url: "/v1/assets/L-118%3Asource" }]);
|
|
177
184
|
response = await worker.fetch(request("/v1/find", { method: "POST", headers, body: JSON.stringify({ kind: "persona", brief: "3个长相高冷的亚洲女生", count: 3 }) }), env);
|
|
178
185
|
const personaBatch = await response.json();
|
|
179
186
|
assert.equal(response.status, 200);
|
package/lib/remote-client.mjs
CHANGED
|
@@ -637,13 +637,14 @@ async function sync(args, output, { readManifest }) {
|
|
|
637
637
|
const ownerToken = process.env.PERSONLIB_OWNER_SYNC_TOKEN;
|
|
638
638
|
if (!ownerToken) throw new Error("PERSONLIB_OWNER_SYNC_TOKEN is required for owner sync");
|
|
639
639
|
await request(endpoint, "/v1/owner/sync/manifest", { token: ownerToken, method: "POST", json: { manifest: { schema_version: payload.schema_version, personas: payload.personas, looks: payload.looks }, assets: payload.assets.map(({ local_path, ...asset }) => asset) } });
|
|
640
|
-
|
|
641
|
-
|
|
640
|
+
const uploadBatchSize = 8;
|
|
641
|
+
for (let offset = 0; offset < payload.assets.length; offset += uploadBatchSize) {
|
|
642
|
+
await Promise.all(payload.assets.slice(offset, offset + uploadBatchSize).map((asset) => request(endpoint, `/v1/owner/assets/${encodeURIComponent(asset.id)}`, {
|
|
642
643
|
token: ownerToken,
|
|
643
644
|
method: "PUT",
|
|
644
645
|
body: readFileSync(asset.local_path),
|
|
645
646
|
headers: { "content-type": asset.mime_type, "x-personlib-sha256": asset.sha256 }
|
|
646
|
-
});
|
|
647
|
+
})));
|
|
647
648
|
}
|
|
648
649
|
jsonOutput({ ok: true, synced: { endpoint, personas: payload.personas.length, looks: payload.looks.length, assets: payload.assets.length } }, output, args);
|
|
649
650
|
}
|
package/package.json
CHANGED
|
@@ -19,7 +19,7 @@ Persona is the stable identity reference: face structure, natural skin traits, s
|
|
|
19
19
|
- “找一个人物” and “为广告找一个人物” are Persona-only searches: use `personlib remote find --kind persona --brief TEXT --output-dir PATH --json`, then return only the Persona reference image. Do not expose the selected ID or create a temporary Look.
|
|
20
20
|
- A request that combines a person and a style—such as “我要一个嘻哈风格的亚洲女生”—is an image request. Run `personlib remote render --brief TEXT --output-dir PATH --json`; it creates and resolves a locked internal selection before submitting, so it never reranks after selection. Never expose the internal selection ID to the user. For `3个都市风格的亚洲女生`, pass `--count 3`: dry-run must return three independent plans, and a real run submits exactly three Makaron jobs—one front/right-side/back sheet per distinct Persona. If the library has fewer than the requested number, return `BLOCKED`; do not silently deliver fewer images or submit any job. A pre-confirmed single selection may use `remote render --selection`. Explicit wording such as “生成三视图”, “出图”, “生成这个人物”, or “做视频出图” follows the same route. The default engine is Makaron and each output is exactly one full-body front/right-side/back turnaround sheet of one adult Persona in one unchanged Look. In the owner-approved direct-delivery mode, that request authorizes exactly one paid submission per selected result: do not pause to display Persona/Look or request a second confirmation. Return the finished turnaround and QC result directly. Use `--dry-run` only when the user asks to preview the locked selection(s) without generating.
|
|
21
21
|
- “只看看匹配的参考” should use `personlib remote preview --brief TEXT --output-dir PATH --json`. This is free retrieval of the private Persona + Look reference pack, not a synthesized image.
|
|
22
|
-
- “都市 / urban / city / city chic / downtown / 都市通勤 / 职场 / 杂志感”, “晚宴 / 红毯 / 高级派对”, and “运动休闲时尚” are Look-only searches: use `personlib remote find --kind look --brief TEXT --output-dir PATH --json`, then return only the Look reference image. Do not generate a person.
|
|
22
|
+
- “都市 / urban / city / city chic / downtown / 都市通勤 / 职场 / 杂志感”, “晚宴 / 红毯 / 高级派对”, “性感 / sexy / sensual / 夜生活 / 约会夜”, and “运动休闲时尚” are Look-only searches: use `personlib remote find --kind look --brief TEXT --output-dir PATH --json`, then return only the Look reference image. Do not generate a person.
|
|
23
23
|
- “给这个人物换妆造” and “用这个人物生成” are combined Persona + Look requests: create a locked internal selection with `remote recommend` or `remote compose`, then use `remote render --selection`. Return only the finished turnaround to the user; never expose internal IDs.
|
|
24
24
|
|
|
25
25
|
## Hard Persona conditions
|