@assethub/cli 0.1.25 → 0.1.28
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 +104 -2
- package/dist/agentSetup.d.ts +69 -0
- package/dist/agentSetup.d.ts.map +1 -0
- package/dist/agentSetup.js +311 -0
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +19 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +255 -32
- package/dist/productionOneShot.d.ts +195 -0
- package/dist/productionOneShot.d.ts.map +1 -0
- package/dist/productionOneShot.js +307 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +1 -1
- package/package.json +3 -2
- package/skills/assethub/SKILL.md +151 -0
package/README.md
CHANGED
|
@@ -67,6 +67,8 @@ authentication. Existing workspace keys and user-token login remain supported.
|
|
|
67
67
|
```sh
|
|
68
68
|
assethub --version
|
|
69
69
|
assethub doctor --mcp --profile my-workspace
|
|
70
|
+
assethub init --dry-run
|
|
71
|
+
assethub init
|
|
70
72
|
assethub mcp config --client cursor
|
|
71
73
|
assethub mcp config --client codex
|
|
72
74
|
```
|
|
@@ -87,6 +89,19 @@ Use the same key as your CLI profile. Personal profiles include the selected
|
|
|
87
89
|
For a different API origin, pass
|
|
88
90
|
`--base-url https://your-host/prefix` (HTTP is accepted only for localhost).
|
|
89
91
|
|
|
92
|
+
`init` sets up every supported coding agent found on this machine — Claude Code,
|
|
93
|
+
Codex, and Cursor — in one step. It writes the hosted MCP entry into each agent's
|
|
94
|
+
own configuration (merging with what is already there), installs the bundled
|
|
95
|
+
`assethub` skill under `~/.agents/skills/assethub`, and links it into each agent's
|
|
96
|
+
skill directory so the agent knows the correct command sequence, cost rules, and
|
|
97
|
+
recovery steps without being told. Pass `--agent <name>` to choose agents
|
|
98
|
+
explicitly, `--project` to write project-level MCP files into the current
|
|
99
|
+
directory instead of your home, and `--dry-run` to see the plan without writing.
|
|
100
|
+
`init` never stores a key: the MCP entries read `ASSETHUB_API_KEY` from the
|
|
101
|
+
agent's environment. Directories that `init` did not create are left untouched.
|
|
102
|
+
After a CLI upgrade, the next command refreshes the installed skill automatically.
|
|
103
|
+
Set `ASSETHUB_CLI_HOME` to redirect all of these writes, for example in tests.
|
|
104
|
+
|
|
90
105
|
Explicit `--profile` uses that profile's key and origin, ahead of environment
|
|
91
106
|
variables, including user tokens and workspace MFA proofs. A missing profile fails.
|
|
92
107
|
Without an explicit profile, a personal profile or a workspace selected with
|
|
@@ -247,8 +262,10 @@ has its own saved operation ID, with the execution linked to the analysis run.
|
|
|
247
262
|
For Internal `V3.6.1` (`V3.6.1 Primary Images First`), `V3.6.3`
|
|
248
263
|
(`V3.6.3 Fast Analysis`, CLI 0.1.16+), `V3.6.4`
|
|
249
264
|
(`V3.6.4 Fast Analysis`, CLI 0.1.17+), `V3.6.5 Primary Images` and
|
|
250
|
-
`Chibi Character (Pluffy) v3.1` (both CLI 0.1.20+),
|
|
251
|
-
|
|
265
|
+
`Chibi Character (Pluffy) v3.1` (both CLI 0.1.20+), `V3.7 Artist Skills`,
|
|
266
|
+
`V3.7.1 Building Modules` (both CLI 0.1.23+/0.1.24+), `V3.0.9 Garment
|
|
267
|
+
Boundaries`, and `Humanoid Assembly (internal)` (both CLI 0.1.26+), analysis
|
|
268
|
+
runs the complete Artifact Graph split. `--all-ready` waits for that one receipt and does not start a
|
|
252
269
|
second production execution. Resume an interrupted graph split with
|
|
253
270
|
`runs resume <operation-id>`; `--task-id`, `--mission-id`,
|
|
254
271
|
`--part-extraction-mode`, and classic `--order-id` continuation are unsupported.
|
|
@@ -259,6 +276,79 @@ or an `imageUrl` to import), `config`, and optional `maxCostCredits`.
|
|
|
259
276
|
An uncertain Trigger acknowledgement remains recoverable as `needs_review` with
|
|
260
277
|
pending history; retrying the same operation never silently launches another run.
|
|
261
278
|
|
|
279
|
+
### One-shot: image to finished asset
|
|
280
|
+
|
|
281
|
+
`production run --image <file|asset-id>` is the fastest path from a single image to
|
|
282
|
+
a finished, composed asset. It is a thin wrapper around `production automation`
|
|
283
|
+
(split, mesh generation per part, and — by default — server-side compose, all in
|
|
284
|
+
one call) plus waiting, cost estimation and downloads. It never re-runs the manual
|
|
285
|
+
split/mesh/compose sequence itself.
|
|
286
|
+
|
|
287
|
+
```sh
|
|
288
|
+
# Estimate cost first; nothing is submitted, nothing is billed.
|
|
289
|
+
assethub production run --image ./character.png --estimate
|
|
290
|
+
|
|
291
|
+
# Run it, wait for every part and the compose stage, download the result.
|
|
292
|
+
assethub production run --image ./character.png --wait --download --out-dir ./out/asset
|
|
293
|
+
|
|
294
|
+
# An existing asset id works the same way as a local file.
|
|
295
|
+
assethub production run --image "$IMAGE_ASSET_ID" --canvas "$CANVAS_ID" --wait
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`--image` auto-detects its argument: an existing local path is uploaded like
|
|
299
|
+
`--file`; anything else is treated like `--source-id`. Do not combine `--image`
|
|
300
|
+
with `--file`/`--source-id`/other source flags — it already resolves one.
|
|
301
|
+
|
|
302
|
+
`--compose` controls the compose stage:
|
|
303
|
+
|
|
304
|
+
- Omitted: automation's own server-side default applies, untouched.
|
|
305
|
+
- `--compose none`: disables automation's compose stage (`config.autoCompose:
|
|
306
|
+
false`); the run stops once every part's mesh is generated.
|
|
307
|
+
- `--compose v6`: requests an explicit Composer V6 pass via
|
|
308
|
+
`config.partComposerAgentVersion: "part_composer_v6_auto_assemble"`
|
|
309
|
+
(`autoCompose` is left at its default of `true` — sending both together is
|
|
310
|
+
rejected by the server). A server that has shipped this (assethub-web PR
|
|
311
|
+
#8179) does the V6 compose itself, in the same automation call, pairing
|
|
312
|
+
each part's mesh with its source image automatically. Against an older,
|
|
313
|
+
currently-deployed server (detected by the automation response lacking
|
|
314
|
+
`estimatedCostBreakdown`), the CLI instead reads the batch's own
|
|
315
|
+
`mesh.generate` executions itself, pairs each produced mesh with the part
|
|
316
|
+
image it was generated from, and runs an explicit `composer run` — same
|
|
317
|
+
end result, either way you never pair mesh and image assets by hand.
|
|
318
|
+
|
|
319
|
+
`--mesh-model <id>` and `--max-parts <n>` (default 24, matching the server's
|
|
320
|
+
own conservative constant for this math) only affect `--estimate`'s math (mesh
|
|
321
|
+
unit cost x an assumed worst-case part count — the CLI cannot know the real
|
|
322
|
+
part count before splitting runs); they do not cap or select anything on the
|
|
323
|
+
real run. `--max-cost <credits>` refuses to run at all, before anything is
|
|
324
|
+
submitted, once the best-effort estimate exceeds it, and is also sent to the
|
|
325
|
+
server as `maxCostCredits` on the real run as a backstop. `--estimate`'s numbers
|
|
326
|
+
come only from side-effect-free catalog reads (mesh model credit cost, compose
|
|
327
|
+
quotes) shaped like the server's own `split`/`meshGeneration`/`compose`
|
|
328
|
+
breakdown; part-extraction/split has no dry-run price today, so that line is
|
|
329
|
+
reported as unknown rather than guessed. On a real (non-`--estimate`) run
|
|
330
|
+
against a server that has shipped #8179, the printed output additionally
|
|
331
|
+
includes the server's own `estimatedTotalCredits`/`estimatedCostBreakdown`
|
|
332
|
+
in place of the CLI's own preflight guess.
|
|
333
|
+
|
|
334
|
+
This depends on assethub-web PR #8179 for the `partComposerAgentVersion` field
|
|
335
|
+
and the richer cost breakdown; it is not merged/deployed as of this writing.
|
|
336
|
+
The CLI degrades gracefully against the currently-deployed API (see above).
|
|
337
|
+
|
|
338
|
+
`runs get <run-id> --summary` prints a handful of plain-text lines (status, stage,
|
|
339
|
+
credits, output asset, error) instead of the full JSON execution. `runs wait
|
|
340
|
+
<run-id>` is `runs watch` under a name that matches a long-running wait; both
|
|
341
|
+
accept `--interval-ms`/`--timeout-ms` and retry rate limits the same way every
|
|
342
|
+
other poll loop in the CLI does.
|
|
343
|
+
|
|
344
|
+
The one-shot command's stdout is always a single JSON object (`batchId`,
|
|
345
|
+
`estimate`, `batchStatus`, `compose`, `execution`, and `downloads` when
|
|
346
|
+
requested) — the same convention every other command in this CLI already
|
|
347
|
+
follows. `--json` is accepted for compatibility with that convention but has
|
|
348
|
+
no effect, since there is no alternate plain-text mode to opt out of (`runs
|
|
349
|
+
get --summary` is the one deliberate exception, and it is opt-in by its own
|
|
350
|
+
flag).
|
|
351
|
+
|
|
262
352
|
`evaluate list` discovers server evaluators. Server evaluation dispatch is currently
|
|
263
353
|
unavailable. Graph exports are bounded to 100 runs/evaluations and 1,000 nodes;
|
|
264
354
|
truncated exports require `--allow-truncated`. Other commands, including production run/intervene, rig and retopology,
|
|
@@ -522,6 +612,13 @@ Part extraction uses the same names shown in the AssetHub product: `V1.5`,
|
|
|
522
612
|
`V3.6.4` (`V3.6.4 Fast Analysis`), `V3.6.5` (`V3.6.5 Primary Images`),
|
|
523
613
|
and `pluffy` (`Chibi Character (Pluffy) v3.1`). CLI 0.1.20 adds the latter two;
|
|
524
614
|
`3.6.5`, `pluffy v3.1`, and `pluffy 3.1` are also accepted aliases.
|
|
615
|
+
`V3.7 Artist Skills` (`v3.7`; also known as `V3.0.7 Artist Skills`) and
|
|
616
|
+
`V3.7.1 Building Modules` (`v3.7.1`; also known as `V3.0.8 Building Modules`)
|
|
617
|
+
support `--skill-mode`. `V3.0.9 Garment Boundaries` (`v3.0.9`) and
|
|
618
|
+
`Humanoid Assembly (internal)` (`humanoid assembly`, `humanoid-assembly`,
|
|
619
|
+
`humanoid_assembly_auto`; CLI 0.1.26+) are additional Internal-only graph
|
|
620
|
+
extractors; use the exact quoted public name or one of its aliases with
|
|
621
|
+
`--part-extractor`.
|
|
525
622
|
Run `production agents` to check availability and `supportedOnGraphEndpoint`
|
|
526
623
|
before selecting a graph version. These aliases do not change the V1.5 default
|
|
527
624
|
or grant access to Internal models; the server still authorizes the account.
|
|
@@ -545,6 +642,11 @@ assethub canvas list
|
|
|
545
642
|
assethub canvas use <canvas-id>
|
|
546
643
|
assethub composer models
|
|
547
644
|
assethub composer run --part <mesh-id> --part <mesh-id> --reference <image-id> --wait
|
|
645
|
+
# Composer V6 (auto-assemble) requires each part's reference image; attach it
|
|
646
|
+
# with `<mesh-id>:<part-image-asset-id>` (the image that mesh was generated
|
|
647
|
+
# from) when the mesh has no other lineage the run can resolve it from.
|
|
648
|
+
assethub composer run --part <mesh-id>:<part-image-id> --part <mesh-id>:<part-image-id> \
|
|
649
|
+
--reference <image-id> --model part_composer_v6_auto_assemble --wait
|
|
548
650
|
assethub composer run --from-run <run-id> --transforms-json @transforms.json --wait
|
|
549
651
|
assethub composer refine --list-modes
|
|
550
652
|
assethub composer refine --from-run <run-id> --instruction "align the feet" \
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export declare const AGENT_IDS: readonly ["claude-code", "codex", "cursor"];
|
|
2
|
+
export type AgentId = (typeof AGENT_IDS)[number];
|
|
3
|
+
type AgentSpec = {
|
|
4
|
+
id: AgentId;
|
|
5
|
+
name: string;
|
|
6
|
+
/** Presence of this directory under home means the agent is installed. */
|
|
7
|
+
detectDir: string;
|
|
8
|
+
globalMcpPath: string;
|
|
9
|
+
projectMcpPath: string;
|
|
10
|
+
skillDir: string;
|
|
11
|
+
format: 'claude' | 'cursor' | 'codex';
|
|
12
|
+
};
|
|
13
|
+
/** `ASSETHUB_CLI_HOME` keeps tests and sandboxes from writing into the real home directory. */
|
|
14
|
+
export declare const cliHome: () => string;
|
|
15
|
+
export declare const packagedSkillDir: () => string;
|
|
16
|
+
export declare const canonicalSkillDir: (home: string) => string;
|
|
17
|
+
export declare const mcpServerEntry: (format: AgentSpec["format"], baseUrl: string, workspaceId?: string) => Record<string, unknown>;
|
|
18
|
+
type WriteStatus = 'created' | 'updated' | 'unchanged';
|
|
19
|
+
type SkillStatus = 'installed' | 'updated' | 'unchanged' | 'kept-existing';
|
|
20
|
+
export declare const installSkill: ({ home, version, dryRun, }: {
|
|
21
|
+
home: string;
|
|
22
|
+
version: string;
|
|
23
|
+
dryRun: boolean;
|
|
24
|
+
}) => Promise<{
|
|
25
|
+
path: string;
|
|
26
|
+
status: SkillStatus;
|
|
27
|
+
version?: string;
|
|
28
|
+
}>;
|
|
29
|
+
type LinkStatus = 'linked' | 'relinked' | 'unchanged' | 'kept-existing';
|
|
30
|
+
/**
|
|
31
|
+
* Refresh an installed skill after a CLI upgrade. Runs before every command;
|
|
32
|
+
* the steady-state cost is one small file read. Never throws — a read-only or
|
|
33
|
+
* missing home must not fail the user's actual command.
|
|
34
|
+
*/
|
|
35
|
+
export declare const syncInstalledSkill: () => Promise<void>;
|
|
36
|
+
export type InitOptions = {
|
|
37
|
+
baseUrl: string;
|
|
38
|
+
workspaceId?: string;
|
|
39
|
+
/** Agent ids to configure; defaults to every agent detected under home. */
|
|
40
|
+
agents?: string[];
|
|
41
|
+
/** Write project-level MCP config into the working directory instead of the user's home. */
|
|
42
|
+
project?: boolean;
|
|
43
|
+
dryRun?: boolean;
|
|
44
|
+
cwd?: string;
|
|
45
|
+
home?: string;
|
|
46
|
+
};
|
|
47
|
+
export type InitReport = {
|
|
48
|
+
version: string;
|
|
49
|
+
dryRun: boolean;
|
|
50
|
+
scope: 'global' | 'project';
|
|
51
|
+
skill: Awaited<ReturnType<typeof installSkill>>;
|
|
52
|
+
agents: {
|
|
53
|
+
id: AgentId;
|
|
54
|
+
name: string;
|
|
55
|
+
detected: boolean;
|
|
56
|
+
mcp: {
|
|
57
|
+
path: string;
|
|
58
|
+
status: WriteStatus;
|
|
59
|
+
};
|
|
60
|
+
skill: {
|
|
61
|
+
path: string;
|
|
62
|
+
status: LinkStatus;
|
|
63
|
+
};
|
|
64
|
+
}[];
|
|
65
|
+
next: string[];
|
|
66
|
+
};
|
|
67
|
+
export declare const initAgents: (options: InitOptions) => Promise<InitReport>;
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=agentSetup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentSetup.d.ts","sourceRoot":"","sources":["../src/agentSetup.ts"],"names":[],"mappings":"AAgCA,eAAO,MAAM,SAAS,6CAA8C,CAAA;AACpE,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAA;AAEhD,KAAK,SAAS,GAAG;IACf,EAAE,EAAE,OAAO,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAA;CACtC,CAAA;AAoCD,+FAA+F;AAC/F,eAAO,MAAM,OAAO,QAAO,MAA4C,CAAA;AAEvE,eAAO,MAAM,gBAAgB,QAAO,MACiC,CAAA;AAErE,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,KAAG,MACJ,CAAA;AA2B7C,eAAO,MAAM,cAAc,GACzB,QAAQ,SAAS,CAAC,QAAQ,CAAC,EAC3B,SAAS,MAAM,EACf,cAAc,MAAM,KACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAYxB,CAAA;AAED,KAAK,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAA;AA2EtD,KAAK,WAAW,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,eAAe,CAAA;AAmC1E,eAAO,MAAM,YAAY,GAAU,4BAIhC;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;CAChB,KAAG,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,CAwBhE,CAAA;AAED,KAAK,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,eAAe,CAAA;AAkCvE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,IAAI,CAevD,CAAA;AAID,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,4FAA4F;IAC5F,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAA;IAC/C,MAAM,EAAE;QACN,EAAE,EAAE,OAAO,CAAA;QACX,IAAI,EAAE,MAAM,CAAA;QACZ,QAAQ,EAAE,OAAO,CAAA;QACjB,GAAG,EAAE;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,WAAW,CAAA;SAAC,CAAA;QACxC,KAAK,EAAE;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,UAAU,CAAA;SAAC,CAAA;KAC1C,EAAE,CAAA;IACH,IAAI,EAAE,MAAM,EAAE,CAAA;CACf,CAAA;AAED,eAAO,MAAM,UAAU,GAAU,SAAS,WAAW,KAAG,OAAO,CAAC,UAAU,CA4EzE,CAAA"}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `assethub init` and the skill sync that keeps its output current.
|
|
3
|
+
*
|
|
4
|
+
* Coding agents learn a tool from two local files: an MCP server entry in their
|
|
5
|
+
* own config, and a skill directory they read on demand. `init` writes both for
|
|
6
|
+
* every supported agent found on this machine, merging into existing config
|
|
7
|
+
* rather than replacing it, and never touching credentials — the MCP entries
|
|
8
|
+
* reference `ASSETHUB_API_KEY` from the agent's environment.
|
|
9
|
+
*
|
|
10
|
+
* The skill is copied once to `~/.agents/skills/assethub` and symlinked into
|
|
11
|
+
* each agent's skill directory. A version marker lets every later command
|
|
12
|
+
* refresh the copy when the CLI has been upgraded, so agents never read rules
|
|
13
|
+
* written for a version they are no longer running.
|
|
14
|
+
*/
|
|
15
|
+
import { cp, lstat, mkdir, readFile, readlink, rename, rm, stat, symlink, writeFile, } from 'node:fs/promises';
|
|
16
|
+
import { homedir } from 'node:os';
|
|
17
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
18
|
+
import { cwd as processCwd, env, pid } from 'node:process';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { cliVersion, mcpConfig, validatedBaseUrl } from './setup.js';
|
|
21
|
+
export const AGENT_IDS = ['claude-code', 'codex', 'cursor'];
|
|
22
|
+
const AGENTS = [
|
|
23
|
+
{
|
|
24
|
+
id: 'claude-code',
|
|
25
|
+
name: 'Claude Code',
|
|
26
|
+
detectDir: '.claude',
|
|
27
|
+
globalMcpPath: '.claude.json',
|
|
28
|
+
projectMcpPath: '.mcp.json',
|
|
29
|
+
skillDir: '.claude/skills',
|
|
30
|
+
format: 'claude',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'codex',
|
|
34
|
+
name: 'Codex',
|
|
35
|
+
detectDir: '.codex',
|
|
36
|
+
globalMcpPath: '.codex/config.toml',
|
|
37
|
+
projectMcpPath: '.codex/config.toml',
|
|
38
|
+
skillDir: '.codex/skills',
|
|
39
|
+
format: 'codex',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'cursor',
|
|
43
|
+
name: 'Cursor',
|
|
44
|
+
detectDir: '.cursor',
|
|
45
|
+
globalMcpPath: '.cursor/mcp.json',
|
|
46
|
+
projectMcpPath: '.cursor/mcp.json',
|
|
47
|
+
skillDir: '.cursor/skills',
|
|
48
|
+
format: 'cursor',
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
const SERVER_NAME = 'assethub';
|
|
52
|
+
const SKILL_NAME = 'assethub';
|
|
53
|
+
const VERSION_MARKER = '.assethub-cli-version';
|
|
54
|
+
/** `ASSETHUB_CLI_HOME` keeps tests and sandboxes from writing into the real home directory. */
|
|
55
|
+
export const cliHome = () => env.ASSETHUB_CLI_HOME || homedir();
|
|
56
|
+
export const packagedSkillDir = () => fileURLToPath(new URL(`../skills/${SKILL_NAME}/`, import.meta.url));
|
|
57
|
+
export const canonicalSkillDir = (home) => join(home, '.agents', 'skills', SKILL_NAME);
|
|
58
|
+
const isEnoent = (error) => typeof error === 'object' &&
|
|
59
|
+
error !== null &&
|
|
60
|
+
error.code === 'ENOENT';
|
|
61
|
+
const readText = async (path) => {
|
|
62
|
+
try {
|
|
63
|
+
return await readFile(path, 'utf8');
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (isEnoent(error))
|
|
67
|
+
return undefined;
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const isDirectory = async (path) => {
|
|
72
|
+
try {
|
|
73
|
+
return (await stat(path)).isDirectory();
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (isEnoent(error))
|
|
77
|
+
return false;
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
// ── MCP configuration ────────────────────────────────────────────────
|
|
82
|
+
export const mcpServerEntry = (format, baseUrl, workspaceId) => {
|
|
83
|
+
const url = `${validatedBaseUrl(baseUrl)}/api/mcp`;
|
|
84
|
+
// Cursor expands `${env:NAME}`; Claude Code expands `${NAME}`. Neither file ever holds the key.
|
|
85
|
+
const key = format === 'cursor' ? '${env:ASSETHUB_API_KEY}' : '${ASSETHUB_API_KEY}';
|
|
86
|
+
return {
|
|
87
|
+
...(format === 'claude' ? { type: 'http' } : {}),
|
|
88
|
+
url,
|
|
89
|
+
headers: {
|
|
90
|
+
Authorization: `Bearer ${key}`,
|
|
91
|
+
...(workspaceId ? { 'X-AssetHub-Workspace': workspaceId } : {}),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
const writeJsonServer = async (path, entry, dryRun) => {
|
|
96
|
+
const text = await readText(path);
|
|
97
|
+
let document = {};
|
|
98
|
+
if (text !== undefined) {
|
|
99
|
+
let parsed;
|
|
100
|
+
try {
|
|
101
|
+
parsed = JSON.parse(text);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
throw new Error(`${path} is not valid JSON. Fix or move it, then rerun init.`);
|
|
105
|
+
}
|
|
106
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
107
|
+
throw new Error(`${path} must contain a JSON object.`);
|
|
108
|
+
document = parsed;
|
|
109
|
+
}
|
|
110
|
+
// A malformed `mcpServers` is the user's data too; refuse rather than replace it.
|
|
111
|
+
if (document.mcpServers !== undefined &&
|
|
112
|
+
(typeof document.mcpServers !== 'object' ||
|
|
113
|
+
document.mcpServers === null ||
|
|
114
|
+
Array.isArray(document.mcpServers)))
|
|
115
|
+
throw new Error(`${path} has a non-object "mcpServers" value. Fix or move it, then rerun init.`);
|
|
116
|
+
const servers = (document.mcpServers ?? {});
|
|
117
|
+
if (JSON.stringify(servers[SERVER_NAME]) === JSON.stringify(entry))
|
|
118
|
+
return 'unchanged';
|
|
119
|
+
const next = { ...document, mcpServers: { ...servers, [SERVER_NAME]: entry } };
|
|
120
|
+
if (!dryRun) {
|
|
121
|
+
await mkdir(dirname(path), { recursive: true });
|
|
122
|
+
await writeFile(path, `${JSON.stringify(next, null, 2)}\n`);
|
|
123
|
+
}
|
|
124
|
+
return text === undefined ? 'created' : 'updated';
|
|
125
|
+
};
|
|
126
|
+
/** Replaces or appends one `[mcp_servers.assethub]` table; every other line is kept verbatim. */
|
|
127
|
+
const writeTomlSection = async (path, section, dryRun) => {
|
|
128
|
+
const header = `[mcp_servers.${SERVER_NAME}]`;
|
|
129
|
+
const body = section.trimEnd();
|
|
130
|
+
const text = await readText(path);
|
|
131
|
+
const lines = (text ?? '').split('\n');
|
|
132
|
+
const start = lines.findIndex(line => line.trim() === header);
|
|
133
|
+
let next;
|
|
134
|
+
if (start === -1) {
|
|
135
|
+
const base = (text ?? '').trimEnd();
|
|
136
|
+
next = `${base ? `${base}\n\n` : ''}${body}\n`;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const endOffset = lines
|
|
140
|
+
.slice(start + 1)
|
|
141
|
+
.findIndex(line => /^\s*\[/.test(line));
|
|
142
|
+
const end = endOffset === -1 ? lines.length : start + 1 + endOffset;
|
|
143
|
+
if (lines.slice(start, end).join('\n').trimEnd() === body)
|
|
144
|
+
return 'unchanged';
|
|
145
|
+
const before = lines.slice(0, start).join('\n').trimEnd();
|
|
146
|
+
const after = lines.slice(end).join('\n').trim();
|
|
147
|
+
next = `${before ? `${before}\n\n` : ''}${body}\n${after ? `\n${after}\n` : ''}`;
|
|
148
|
+
}
|
|
149
|
+
if (!dryRun) {
|
|
150
|
+
await mkdir(dirname(path), { recursive: true });
|
|
151
|
+
await writeFile(path, next);
|
|
152
|
+
}
|
|
153
|
+
return text === undefined ? 'created' : 'updated';
|
|
154
|
+
};
|
|
155
|
+
const readMarker = async (dir) => (await readText(join(dir, VERSION_MARKER)))?.trim();
|
|
156
|
+
/**
|
|
157
|
+
* Stage the new copy beside the target, then swap with two renames, so the
|
|
158
|
+
* installed skill is never deleted before its replacement is complete. A
|
|
159
|
+
* failed swap puts the previous copy back. Two CLIs syncing at the same
|
|
160
|
+
* moment use distinct staging names; whichever copy lands is complete, and
|
|
161
|
+
* the loser's error is swallowed by the caller.
|
|
162
|
+
*/
|
|
163
|
+
const replaceSkillDir = async (source, target, version) => {
|
|
164
|
+
const staging = `${target}.next-${pid}`;
|
|
165
|
+
const previous = `${target}.prev-${pid}`;
|
|
166
|
+
await rm(staging, { recursive: true, force: true });
|
|
167
|
+
await mkdir(dirname(target), { recursive: true });
|
|
168
|
+
await cp(source, staging, { recursive: true });
|
|
169
|
+
await writeFile(join(staging, VERSION_MARKER), `${version}\n`);
|
|
170
|
+
const hadTarget = await isDirectory(target);
|
|
171
|
+
if (hadTarget)
|
|
172
|
+
await rename(target, previous);
|
|
173
|
+
try {
|
|
174
|
+
await rename(staging, target);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (hadTarget)
|
|
178
|
+
await rename(previous, target).catch(() => undefined);
|
|
179
|
+
await rm(staging, { recursive: true, force: true });
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
if (hadTarget)
|
|
183
|
+
await rm(previous, { recursive: true, force: true });
|
|
184
|
+
};
|
|
185
|
+
export const installSkill = async ({ home, version, dryRun, }) => {
|
|
186
|
+
const source = packagedSkillDir();
|
|
187
|
+
if (!(await isDirectory(source)))
|
|
188
|
+
throw new Error('This CLI installation does not include the packaged agent skill.');
|
|
189
|
+
const target = canonicalSkillDir(home);
|
|
190
|
+
const marker = await readMarker(target);
|
|
191
|
+
// A directory without our marker is the user's own; leave it alone.
|
|
192
|
+
const status = marker === undefined
|
|
193
|
+
? (await isDirectory(target))
|
|
194
|
+
? 'kept-existing'
|
|
195
|
+
: 'installed'
|
|
196
|
+
: marker === version
|
|
197
|
+
? 'unchanged'
|
|
198
|
+
: 'updated';
|
|
199
|
+
if (!dryRun && (status === 'installed' || status === 'updated'))
|
|
200
|
+
await replaceSkillDir(source, target, version);
|
|
201
|
+
return {
|
|
202
|
+
path: target,
|
|
203
|
+
status,
|
|
204
|
+
...(status === 'kept-existing' ? {} : { version }),
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
const linkSkill = async (agentSkillsDir, canonical, dryRun) => {
|
|
208
|
+
const link = join(agentSkillsDir, SKILL_NAME);
|
|
209
|
+
const relativeTarget = relative(agentSkillsDir, canonical);
|
|
210
|
+
let existing;
|
|
211
|
+
try {
|
|
212
|
+
existing = await lstat(link);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (!isEnoent(error))
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
if (existing?.isSymbolicLink()) {
|
|
219
|
+
if (resolve(agentSkillsDir, await readlink(link)) === canonical)
|
|
220
|
+
return { path: link, status: 'unchanged' };
|
|
221
|
+
if (!dryRun) {
|
|
222
|
+
await rm(link);
|
|
223
|
+
await symlink(relativeTarget, link, 'dir');
|
|
224
|
+
}
|
|
225
|
+
return { path: link, status: 'relinked' };
|
|
226
|
+
}
|
|
227
|
+
if (existing)
|
|
228
|
+
return { path: link, status: 'kept-existing' };
|
|
229
|
+
if (!dryRun) {
|
|
230
|
+
await mkdir(agentSkillsDir, { recursive: true });
|
|
231
|
+
await symlink(relativeTarget, link, 'dir');
|
|
232
|
+
}
|
|
233
|
+
return { path: link, status: 'linked' };
|
|
234
|
+
};
|
|
235
|
+
let synced = false;
|
|
236
|
+
/**
|
|
237
|
+
* Refresh an installed skill after a CLI upgrade. Runs before every command;
|
|
238
|
+
* the steady-state cost is one small file read. Never throws — a read-only or
|
|
239
|
+
* missing home must not fail the user's actual command.
|
|
240
|
+
*/
|
|
241
|
+
export const syncInstalledSkill = async () => {
|
|
242
|
+
if (synced)
|
|
243
|
+
return;
|
|
244
|
+
synced = true;
|
|
245
|
+
try {
|
|
246
|
+
const target = canonicalSkillDir(cliHome());
|
|
247
|
+
const installed = await readMarker(target);
|
|
248
|
+
if (installed === undefined)
|
|
249
|
+
return;
|
|
250
|
+
const version = await cliVersion();
|
|
251
|
+
if (installed === version)
|
|
252
|
+
return;
|
|
253
|
+
const source = packagedSkillDir();
|
|
254
|
+
if (!(await isDirectory(source)))
|
|
255
|
+
return;
|
|
256
|
+
await replaceSkillDir(source, target, version);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// best effort
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
export const initAgents = async (options) => {
|
|
263
|
+
const home = options.home ?? cliHome();
|
|
264
|
+
const cwd = options.cwd ?? processCwd();
|
|
265
|
+
const dryRun = options.dryRun ?? false;
|
|
266
|
+
const version = await cliVersion();
|
|
267
|
+
const requested = options.agents ?? [];
|
|
268
|
+
const unknown = requested.filter(id => !AGENT_IDS.includes(id));
|
|
269
|
+
if (unknown.length > 0)
|
|
270
|
+
throw new Error(`Unknown agent: ${unknown.join(', ')}. Use --agent ${AGENT_IDS.join('|')}.`);
|
|
271
|
+
const detected = new Map(await Promise.all(AGENTS.map(async (agent) => [agent.id, await isDirectory(join(home, agent.detectDir))])));
|
|
272
|
+
const selected = requested.length > 0
|
|
273
|
+
? AGENTS.filter(agent => requested.includes(agent.id))
|
|
274
|
+
: AGENTS.filter(agent => detected.get(agent.id));
|
|
275
|
+
if (selected.length === 0)
|
|
276
|
+
throw new Error(`No supported coding agent found under ${home}. Pass --agent ${AGENT_IDS.join('|')} to choose one.`);
|
|
277
|
+
const skill = await installSkill({ home, version, dryRun });
|
|
278
|
+
const agents = [];
|
|
279
|
+
for (const spec of selected) {
|
|
280
|
+
const mcpPath = options.project
|
|
281
|
+
? join(cwd, spec.projectMcpPath)
|
|
282
|
+
: join(home, spec.globalMcpPath);
|
|
283
|
+
const mcp = spec.format === 'codex'
|
|
284
|
+
? await writeTomlSection(mcpPath, mcpConfig('codex', options.baseUrl, false, options.workspaceId), dryRun)
|
|
285
|
+
: await writeJsonServer(mcpPath, mcpServerEntry(spec.format, options.baseUrl, options.workspaceId), dryRun);
|
|
286
|
+
const link = await linkSkill(join(home, spec.skillDir), skill.path, dryRun);
|
|
287
|
+
agents.push({
|
|
288
|
+
id: spec.id,
|
|
289
|
+
name: spec.name,
|
|
290
|
+
detected: detected.get(spec.id) ?? false,
|
|
291
|
+
mcp: { path: mcpPath, status: mcp },
|
|
292
|
+
skill: link,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
version,
|
|
297
|
+
dryRun,
|
|
298
|
+
scope: options.project ? 'project' : 'global',
|
|
299
|
+
skill,
|
|
300
|
+
agents,
|
|
301
|
+
next: [
|
|
302
|
+
...(env.ASSETHUB_API_KEY
|
|
303
|
+
? []
|
|
304
|
+
: [
|
|
305
|
+
"Provide ASSETHUB_API_KEY in each agent's environment; the MCP entries read it from there and never store it.",
|
|
306
|
+
]),
|
|
307
|
+
'Restart the agent so it reloads MCP servers and skills.',
|
|
308
|
+
'Run `assethub doctor --mcp` to verify the connection.',
|
|
309
|
+
],
|
|
310
|
+
};
|
|
311
|
+
};
|
package/dist/execution.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execution.d.ts","sourceRoot":"","sources":["../src/execution.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EACjC,MAAM,sBAAsB,CAAA;AAG7B,QAAA,MAAM,UAAU,kJAQN,CAAA;AACV,KAAK,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAC5C,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,cAAc,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AACD,eAAO,MAAM,oBAAoB,GAAU,SAAS,cAAc;;;;;YAPxD,cAAc;cACZ,MAAM;SACX,MAAM;eACA,MAAM;gBACL,SAAS;aACZ,OAAO;EAoBjB,CAAA;AACD,KAAK,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAA;AAC/D,KAAK,WAAW,GACZ,sBAAsB,GACtB,qBAAqB,GACrB,IAAI,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,GAC5C,IAAI,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,GAC3C,wBAAwB,GACxB,wBAAwB,GACxB,2BAA2B,CAAA;AAU/B,eAAO,MAAM,oBAAoB,GAC/B,UAAU,MAAM,EAChB,aAAa,MAAM,qBACyD,CAAA;AAC9E,KAAK,aAAa,GAAG;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IACnC,WAAW,CAAC,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IAC7C,UAAU,CAAC,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;CAC5C,CAAA;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAGxC,QAAQ,CAAC,QAAQ;IACjB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,eAAe;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS;gBAJlC,OAAO,EAAE,MAAM,EACN,QAAQ,SAAI,EACZ,WAAW,CAAC,EAAE,MAAM,YAAA,EACpB,SAAS,CAAC,EAAE,eAAe,YAAA,EAC3B,KAAK,GAAE,MAAM,GAAG,SAA4B;CAIxD;
|
|
1
|
+
{"version":3,"file":"execution.d.ts","sourceRoot":"","sources":["../src/execution.ts"],"names":[],"mappings":"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"}
|
package/dist/execution.js
CHANGED
|
@@ -43,6 +43,9 @@ export class CliExecutionError extends Error {
|
|
|
43
43
|
this.runId = runId;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
/** A floor under `--interval-ms` for `waitForExecution`'s own poll cadence. */
|
|
47
|
+
const MIN_POLL_INTERVAL_MS = 1_000;
|
|
48
|
+
const POLL_JITTER_MS = 250;
|
|
46
49
|
/** Kept only for SIGINT reporting; interrupting the client never cancels server work. */
|
|
47
50
|
export const activeExecution = {};
|
|
48
51
|
export const childOperationId = (parent, step) => {
|
|
@@ -247,6 +250,11 @@ export const waitForExecution = async (client, runId, options = {}) => {
|
|
|
247
250
|
activeExecution.runId = runId;
|
|
248
251
|
const deadline = Date.now() + (options.timeoutMs ?? 900_000);
|
|
249
252
|
const timeout = () => new CliExecutionError(`Wait timed out; server execution continues. Use runs watch ${runId}.`, 3, activeExecution.operationId, activeExecution.execution, runId);
|
|
253
|
+
// A floor under --interval-ms and a touch of jitter on every poll: several
|
|
254
|
+
// `--wait` invocations started together (e.g. parallel `mesh generate`)
|
|
255
|
+
// otherwise poll in lockstep and collide on the same rate-limit window.
|
|
256
|
+
const pollIntervalMs = () => Math.max(options.intervalMs ?? 5_000, MIN_POLL_INTERVAL_MS) +
|
|
257
|
+
Math.floor(Math.random() * POLL_JITTER_MS);
|
|
250
258
|
while (true) {
|
|
251
259
|
const signal = AbortSignal.timeout(Math.max(1, deadline - Date.now()));
|
|
252
260
|
let execution;
|
|
@@ -256,6 +264,16 @@ export const waitForExecution = async (client, runId, options = {}) => {
|
|
|
256
264
|
catch (error) {
|
|
257
265
|
if (signal.aborted)
|
|
258
266
|
throw timeout();
|
|
267
|
+
// The client already retries a 429 internally (Retry-After/backoff);
|
|
268
|
+
// one only reaches here once that budget is exhausted. Server work
|
|
269
|
+
// keeps running regardless, so a rate limit is never a wait failure —
|
|
270
|
+
// treat it like "no update yet" and keep polling within the deadline.
|
|
271
|
+
if (error instanceof AssetHubApiError && error.status === 429) {
|
|
272
|
+
if (Date.now() >= deadline)
|
|
273
|
+
throw timeout();
|
|
274
|
+
await delay(Math.min(pollIntervalMs(), Math.max(0, deadline - Date.now())));
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
259
277
|
throw new CliExecutionError(error instanceof Error ? error.message : String(error), error instanceof AssetHubApiError && error.status < 500 ? 2 : 1, activeExecution.operationId, activeExecution.execution, runId);
|
|
260
278
|
}
|
|
261
279
|
activeExecution.execution = execution;
|
|
@@ -267,6 +285,6 @@ export const waitForExecution = async (client, runId, options = {}) => {
|
|
|
267
285
|
return execution;
|
|
268
286
|
if (Date.now() >= deadline)
|
|
269
287
|
throw timeout();
|
|
270
|
-
await delay(Math.min(
|
|
288
|
+
await delay(Math.min(pollIntervalMs(), Math.max(0, deadline - Date.now())));
|
|
271
289
|
}
|
|
272
290
|
};
|