@mmmbuto/nexuscrew 0.8.23 → 0.8.25
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/CHANGELOG.md +744 -0
- package/README.md +18 -6
- package/frontend/dist/assets/index-Dov6E_p3.js +91 -0
- package/frontend/dist/assets/index-DqG-mDnV.css +32 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/version.json +1 -1
- package/lib/cli/commands.js +16 -2
- package/lib/fleet/catalogs/alibaba-token-plan-qwen3.8.json +51 -0
- package/lib/fleet/managed.js +106 -10
- package/lib/fleet/routes.js +4 -1
- package/lib/nodes/store.js +40 -1
- package/lib/settings/pairing-coordinator.js +22 -1
- package/lib/settings/public-peering-routes.js +11 -2
- package/lib/settings/routes.js +14 -5
- package/package.json +3 -2
- package/skills/alibaba-token-media/SKILL.md +133 -0
- package/skills/alibaba-token-media/agents/openai.yaml +4 -0
- package/skills/alibaba-token-media/references/api-contract.md +97 -0
- package/skills/alibaba-token-media/scripts/alibaba_token_media.py +550 -0
- package/frontend/dist/assets/index-77r8nzQf.css +0 -32
- package/frontend/dist/assets/index-C0QL3gEp.js +0 -91
|
@@ -75,11 +75,20 @@ function publicPeeringRoutes(deps = {}) {
|
|
|
75
75
|
if (st.nodeId === b.instanceId || st.nodes.some((n) => n.nodeId === b.instanceId)
|
|
76
76
|
|| pending.some((row) => row.instanceId === b.instanceId)) return res.status(409).json({ error: 'peer duplicato' });
|
|
77
77
|
const reservedNames = new Set(pending.map((row) => row.name).filter(validPeerName));
|
|
78
|
-
|
|
78
|
+
// `localhost` e' sintatticamente uno slug valido, ma su Termux non e'
|
|
79
|
+
// un handle di route: piu device espongono lo stesso hostname. Trattalo
|
|
80
|
+
// come conflitto risolvibile anche quando e' il primo, senza consumare
|
|
81
|
+
// l'invite; il client puo' riprovare col suffisso stabile proposto.
|
|
82
|
+
if (b.name === 'localhost' || nodesStore.getNode(st, b.name) || reservedNames.has(b.name)) {
|
|
83
|
+
const usedNames = [...st.nodes.map((node) => node.name), ...reservedNames];
|
|
84
|
+
const suggestedName = nodesStore.deriveNodeHandle(
|
|
85
|
+
b.label || b.name, b.name, b.instanceId, usedNames,
|
|
86
|
+
);
|
|
79
87
|
return res.status(409).json({
|
|
80
88
|
error: `nome peer gia' in uso: ${b.name}`,
|
|
81
89
|
code: 'peer-name-conflict',
|
|
82
|
-
|
|
90
|
+
suggestedName,
|
|
91
|
+
hint: `usa l'handle proposto "${suggestedName}" e riprova con lo stesso invito`,
|
|
83
92
|
});
|
|
84
93
|
}
|
|
85
94
|
const name = b.name;
|
package/lib/settings/routes.js
CHANGED
|
@@ -71,8 +71,8 @@ const EDIT_KEYS = new Set(['label', 'ssh', 'sshPort', 'autostart', 'visibility',
|
|
|
71
71
|
// NON usa ciecamente hostname: se l'hostname e' vuoto o 'localhost' (tipico di
|
|
72
72
|
// chiavi di test / host dietro tunnel) propone un fallback neutro. L'utente puo'
|
|
73
73
|
// sempre sovrascriverlo: questo e' solo il valore iniziale del campo.
|
|
74
|
-
function defaultDeviceName() {
|
|
75
|
-
const h = String(
|
|
74
|
+
function defaultDeviceName(hostname = os.hostname()) {
|
|
75
|
+
const h = String(hostname || '').trim();
|
|
76
76
|
if (!h || /^localhost$/i.test(h)) return 'NexusCrew';
|
|
77
77
|
return h.slice(0, nodesStore.LABEL_MAX);
|
|
78
78
|
}
|
|
@@ -143,6 +143,7 @@ function readConfigFile(p) {
|
|
|
143
143
|
function settingsRoutes(deps = {}) {
|
|
144
144
|
const cfg = deps.cfg || {};
|
|
145
145
|
const seams = cfg.settingsSeams || {};
|
|
146
|
+
const localHostname = typeof seams.hostname === 'function' ? seams.hostname : os.hostname;
|
|
146
147
|
// tokenStore/closeSessions iniettati da server.js per la semantica di invalidazione
|
|
147
148
|
// live (audit F7 / §4b(3)). Assenti nei test unitari puri su routes -> la rotazione
|
|
148
149
|
// scrive solo il file (come prima), senza reload in-memory.
|
|
@@ -217,6 +218,10 @@ function settingsRoutes(deps = {}) {
|
|
|
217
218
|
boot: bootState({ platform, execImpl: seams.execImpl, uid: seams.uid, home }).enabled,
|
|
218
219
|
};
|
|
219
220
|
const updateStatus = updater && typeof updater.status === 'function' ? updater.status() : null;
|
|
221
|
+
const localStore = nodesStore.loadStore(nodesPath);
|
|
222
|
+
const localNodeId = localStore && nodesStore.NODE_ID_RE.test(localStore.nodeId)
|
|
223
|
+
? localStore.nodeId : null;
|
|
224
|
+
const deviceName = defaultDeviceName(localHostname());
|
|
220
225
|
const out = {
|
|
221
226
|
roles: readRoles(configPath),
|
|
222
227
|
firstRun,
|
|
@@ -227,7 +232,11 @@ function settingsRoutes(deps = {}) {
|
|
|
227
232
|
autoUpdate: updateStatus ? updateStatus.enabled : fileCfg.autoUpdate !== false,
|
|
228
233
|
// Nome dispositivo proposto per i form di pairing (etichetta umana, non
|
|
229
234
|
// lo slug). La UI lo precompila e lascia editing libero.
|
|
230
|
-
deviceName
|
|
235
|
+
deviceName,
|
|
236
|
+
nodeId: localNodeId,
|
|
237
|
+
localName: localNodeId
|
|
238
|
+
? nodesStore.deriveNodeHandle(deviceName, localHostname(), localNodeId)
|
|
239
|
+
: null,
|
|
231
240
|
};
|
|
232
241
|
if (updateStatus) out.update = updateStatus;
|
|
233
242
|
send(res, 200, out);
|
|
@@ -385,8 +394,8 @@ function settingsRoutes(deps = {}) {
|
|
|
385
394
|
// prima di paired:true. Qui resta solo la registrazione dietro mutGate;
|
|
386
395
|
// token/credenziali MAI nel payload (redazione nel handler estratto).
|
|
387
396
|
r.post('/nodes/pair', mutGate, createPairHandler({
|
|
388
|
-
send, validName, defaultDeviceName,
|
|
389
|
-
nodesPath, configPath, home, seams, runtimePort,
|
|
397
|
+
send, validName, defaultDeviceName: () => defaultDeviceName(localHostname()),
|
|
398
|
+
localHostname, nodesPath, configPath, home, seams, runtimePort,
|
|
390
399
|
}));
|
|
391
400
|
|
|
392
401
|
// --- POST /nodes — nodes add (riusa nodesCmds.nodesAdd) --------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmmbuto/nexuscrew",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.25",
|
|
4
4
|
"description": "Faithful browser tmux client — attach to live sessions over a real PTY, localhost-only, mobile-easy",
|
|
5
5
|
"main": "lib/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"skills/",
|
|
13
13
|
"frontend/dist/",
|
|
14
14
|
"frontend/index.html",
|
|
15
|
+
"CHANGELOG.md",
|
|
15
16
|
"LICENSE",
|
|
16
17
|
"README.md",
|
|
17
18
|
"NOTICE"
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
"scripts": {
|
|
20
21
|
"start": "node bin/nexuscrew.js serve",
|
|
21
22
|
"dev": "node bin/nexuscrew.js serve",
|
|
22
|
-
"build": "
|
|
23
|
+
"build": "npm --prefix frontend ci && npm --prefix frontend run build",
|
|
23
24
|
"test": "node tests/run-isolated.js && npm --prefix frontend test"
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: alibaba-token-media
|
|
3
|
+
description: "Generate or edit images with Wan 2.7 and create videos with HappyHorse through an Alibaba Qwen Cloud Token Plan Personal subscription. Use when the user asks Claude Code, Codex, Codex-VL, or Pi to draw, generate, or edit an image with wan2.7-image or wan2.7-image-pro; create text-to-video or image-to-video media with happyhorse-1.1-t2v or happyhorse-1.1-i2v; inspect a submitted HappyHorse task; or validate the Token Plan media setup. Do not use this skill for chat, visual understanding, batch automation, backend services, or pay-as-you-go DashScope APIs."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Alibaba Token Media
|
|
7
|
+
|
|
8
|
+
Use the bundled, dependency-free CLI to access only the media models included
|
|
9
|
+
with the Lite guardrails of Alibaba Token Plan Personal. The same skill and Python script work
|
|
10
|
+
with Claude Code, Codex, Codex-VL, and Pi. Invoke the script directly from Pi;
|
|
11
|
+
do not assume that Pi provides a native MCP client.
|
|
12
|
+
|
|
13
|
+
Keep this workflow interactive. Every generation consumes plan Credits, and
|
|
14
|
+
the plan is not for unattended batch or backend workloads.
|
|
15
|
+
|
|
16
|
+
## Safety contract
|
|
17
|
+
|
|
18
|
+
- Read the credential only from `ALIBABA_CODE_API_KEY`. Never request it in
|
|
19
|
+
chat, pass it on the command line, print it, persist it, or send it through
|
|
20
|
+
an agent bus.
|
|
21
|
+
- Use only the fixed Token Plan host and exact model allowlist implemented by
|
|
22
|
+
the script. Never fall back to DashScope pay-as-you-go endpoints.
|
|
23
|
+
- Run `status` and then a `--dry-run` before the first real request in a
|
|
24
|
+
session. Dry runs never require or reveal the key.
|
|
25
|
+
- Show the user the normalized model, operation, size or duration, resolution,
|
|
26
|
+
and confirmation flags before spending Credits. A real image request needs
|
|
27
|
+
`--confirm-credit-use`; a real video request additionally needs
|
|
28
|
+
`--confirm-expensive`.
|
|
29
|
+
- Require `--confirm-high-cost` for Wan Pro, 2K or 4K images, 1080P video, or
|
|
30
|
+
video longer than five seconds. Keep Lite defaults at one 1K base image or
|
|
31
|
+
one 720P three-second video.
|
|
32
|
+
- Never batch, enable sequential image sets, retry a generation POST
|
|
33
|
+
automatically, or run more than one generation submit concurrently. A task
|
|
34
|
+
status GET may be retried, but never with a tight polling loop.
|
|
35
|
+
- Accept local input only as a regular, non-symlink JPEG, PNG, BMP, or WEBP
|
|
36
|
+
file below 20 MiB and under the current user's home. Image-to-video accepts
|
|
37
|
+
JPEG, PNG, or WEBP first frames, not BMP. Accept remote input only
|
|
38
|
+
over public HTTPS; reject localhost, private IP literals, credentials, and
|
|
39
|
+
non-standard ports.
|
|
40
|
+
- Download signed result URLs immediately. Never paste signed URLs into chat,
|
|
41
|
+
logs, source files, or an agent bus; they expire after 24 hours.
|
|
42
|
+
|
|
43
|
+
## Locate the CLI
|
|
44
|
+
|
|
45
|
+
Resolve `scripts/alibaba_token_media.py` relative to this `SKILL.md`, then run
|
|
46
|
+
it with Python 3:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
python3 <skill-dir>/scripts/alibaba_token_media.py status
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Keep this packaged skill as the source of truth; do not copy the script into a
|
|
53
|
+
project before using it.
|
|
54
|
+
|
|
55
|
+
## Generate or edit an image
|
|
56
|
+
|
|
57
|
+
Start with a sanitized preview:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
python3 <skill-dir>/scripts/alibaba_token_media.py image \
|
|
61
|
+
--prompt "A quiet alpine lake at sunrise" \
|
|
62
|
+
--dry-run
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
After explicit approval, repeat without `--dry-run` and add
|
|
66
|
+
`--confirm-credit-use`. For an edit, add one to nine `--image` values. Each
|
|
67
|
+
value may be a permitted local image under the current user's home or a public
|
|
68
|
+
HTTPS URL:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python3 <skill-dir>/scripts/alibaba_token_media.py image \
|
|
72
|
+
--prompt "Preserve the subject; replace the background with a night city" \
|
|
73
|
+
--image /absolute/path/input.png \
|
|
74
|
+
--confirm-credit-use
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Use `--model wan2.7-image-pro --size 2K --confirm-high-cost` only after the
|
|
78
|
+
user selects that higher-cost variant. `4K` is allowed only for Pro
|
|
79
|
+
text-to-image with no input images. The script fixes `n=1` and disables image
|
|
80
|
+
sets. Image watermarking is off by default and enabled only with `--watermark`.
|
|
81
|
+
|
|
82
|
+
## Submit a video
|
|
83
|
+
|
|
84
|
+
Text-to-video Lite preview:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python3 <skill-dir>/scripts/alibaba_token_media.py video-submit \
|
|
88
|
+
--prompt "A paper model city slowly lights up at dusk" \
|
|
89
|
+
--dry-run
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
For a real request, add both `--confirm-credit-use` and
|
|
93
|
+
`--confirm-expensive`. For image-to-video, select `happyhorse-1.1-i2v` and
|
|
94
|
+
supply exactly one first-frame image with `--image`. Do not supply `--ratio`
|
|
95
|
+
for image-to-video; its aspect ratio follows the input. Video watermarking is
|
|
96
|
+
on by default and can be disabled explicitly with `--no-watermark`.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
python3 <skill-dir>/scripts/alibaba_token_media.py video-submit \
|
|
100
|
+
--model happyhorse-1.1-i2v \
|
|
101
|
+
--image /absolute/path/first-frame.png \
|
|
102
|
+
--prompt "The camera slowly moves forward" \
|
|
103
|
+
--confirm-credit-use --confirm-expensive
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Return the task ID after a real submit. Do not hide submission success behind
|
|
107
|
+
polling.
|
|
108
|
+
|
|
109
|
+
## Check and download a video
|
|
110
|
+
|
|
111
|
+
Query a task once:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python3 <skill-dir>/scripts/alibaba_token_media.py video-status <task-id>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
When it is `SUCCEEDED`, repeat with `--download`. If recurring monitoring is
|
|
118
|
+
requested and the client exposes a loop facility, use an interval of at least
|
|
119
|
+
five minutes. Do not implement a private tight polling loop.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
python3 <skill-dir>/scripts/alibaba_token_media.py video-status <task-id> \
|
|
123
|
+
--download
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Default downloads go to
|
|
127
|
+
`~/Downloads/alibaba-token-plan/YYYY-MM-DD/` with unique names and no
|
|
128
|
+
overwrite. An explicit `--output` must remain under the current user's home.
|
|
129
|
+
|
|
130
|
+
## Reference
|
|
131
|
+
|
|
132
|
+
Read [references/api-contract.md](references/api-contract.md) before changing
|
|
133
|
+
payloads, limits, endpoints, or model handling.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Alibaba Token Plan media API contract
|
|
2
|
+
|
|
3
|
+
Verified against the official Qwen Cloud documentation on 2026-07-19.
|
|
4
|
+
|
|
5
|
+
## Fixed subscription surface
|
|
6
|
+
|
|
7
|
+
- Host: `https://token-plan.ap-southeast-1.maas.aliyuncs.com`
|
|
8
|
+
- Credential environment variable: `ALIBABA_CODE_API_KEY`
|
|
9
|
+
- Authorization: `Bearer <token>`
|
|
10
|
+
- Supported invocation surfaces: Claude Code, Codex, Codex-VL, and Pi through
|
|
11
|
+
the packaged skill and CLI. Pi is not assumed to support MCP natively.
|
|
12
|
+
- Token Plan Personal Lite guardrails: interactive use only, one submit at a
|
|
13
|
+
time, no batch generation, and no automatic retry of a generation POST.
|
|
14
|
+
- Never substitute the DashScope pay-as-you-go host.
|
|
15
|
+
|
|
16
|
+
The Token Plan documentation says coding tools cannot configure media models
|
|
17
|
+
as chat models. They must use a skill, command, agent, or equivalent extension.
|
|
18
|
+
The Personal plan is intended for interactive programming and agent-tool
|
|
19
|
+
work, not unattended scripts, backend services, or batch processing.
|
|
20
|
+
|
|
21
|
+
## Wan 2.7 image generation and editing
|
|
22
|
+
|
|
23
|
+
Synchronous endpoint:
|
|
24
|
+
|
|
25
|
+
`POST /api/v1/services/aigc/multimodal-generation/generation`
|
|
26
|
+
|
|
27
|
+
Allowed plan models:
|
|
28
|
+
|
|
29
|
+
- `wan2.7-image`
|
|
30
|
+
- `wan2.7-image-pro`
|
|
31
|
+
|
|
32
|
+
Request shape:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"model": "wan2.7-image",
|
|
37
|
+
"input": {
|
|
38
|
+
"messages": [{
|
|
39
|
+
"role": "user",
|
|
40
|
+
"content": [{"text": "prompt"}, {"image": "data-or-https-url"}]
|
|
41
|
+
}]
|
|
42
|
+
},
|
|
43
|
+
"parameters": {
|
|
44
|
+
"n": 1,
|
|
45
|
+
"size": "1K",
|
|
46
|
+
"enable_sequential": false,
|
|
47
|
+
"watermark": false,
|
|
48
|
+
"thinking_mode": true
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The content array requires exactly one text object and accepts zero to nine
|
|
54
|
+
image objects. Prompt maximum is 5,000 characters. Inputs may be JPEG, PNG,
|
|
55
|
+
BMP, or WEBP and are limited to 20 MiB each. `wan2.7-image` supports 1K and 2K;
|
|
56
|
+
Pro also supports 4K for text-to-image only. The output image URL is under
|
|
57
|
+
`output.choices[*].message.content[*].image` and expires after 24 hours.
|
|
58
|
+
|
|
59
|
+
## HappyHorse video generation
|
|
60
|
+
|
|
61
|
+
Submit endpoint:
|
|
62
|
+
|
|
63
|
+
`POST /api/v1/services/aigc/video-generation/video-synthesis`
|
|
64
|
+
|
|
65
|
+
Required submission header: `X-DashScope-Async: enable`.
|
|
66
|
+
|
|
67
|
+
Allowed plan models:
|
|
68
|
+
|
|
69
|
+
- `happyhorse-1.1-t2v`
|
|
70
|
+
- `happyhorse-1.1-i2v`
|
|
71
|
+
|
|
72
|
+
Text-to-video input contains `prompt`; parameters contain `resolution`,
|
|
73
|
+
`ratio`, and `duration`. Image-to-video input additionally contains exactly one
|
|
74
|
+
`media` item with `type: first_frame` and an HTTPS or Base64 image in `url`;
|
|
75
|
+
its parameters omit `ratio` because output follows the first frame. The CLI
|
|
76
|
+
enables video watermarking by default; image watermarking is disabled by
|
|
77
|
+
default. Image-to-video first frames accept JPEG, PNG, or WEBP, not BMP.
|
|
78
|
+
|
|
79
|
+
Duration is an integer from 3 through 15 seconds. Resolution is 720P or 1080P.
|
|
80
|
+
Text-to-video ratios are 16:9, 9:16, 1:1, 4:3, 3:4, 4:5, 5:4, 9:21, or 21:9.
|
|
81
|
+
The response task ID is `output.task_id`.
|
|
82
|
+
|
|
83
|
+
Status endpoint:
|
|
84
|
+
|
|
85
|
+
`GET /api/v1/tasks/{task_id}`
|
|
86
|
+
|
|
87
|
+
Terminal states are `SUCCEEDED`, `FAILED`, `CANCELED`, and `UNKNOWN`. On
|
|
88
|
+
success, `output.video_url` is an H.264 MP4 signed URL valid for 24 hours.
|
|
89
|
+
|
|
90
|
+
## Official sources
|
|
91
|
+
|
|
92
|
+
- https://docs.qwencloud.com/token-plan/best-practices/integrate-multimodal-gen
|
|
93
|
+
- https://docs.qwencloud.com/token-plan/personal/token-plan-personal-overview
|
|
94
|
+
- https://docs.qwencloud.com/api-reference/image-generation/wan27-image-gen-edit/synchronous
|
|
95
|
+
- https://docs.qwencloud.com/api-reference/video-generation/happyhorse-text-to-video/create-task
|
|
96
|
+
- https://docs.qwencloud.com/api-reference/video-generation/happyhorse-image-to-video/create-task
|
|
97
|
+
- https://docs.qwencloud.com/api-reference/video-generation/happyhorse-text-to-video/query-result
|