@assethub/cli 0.1.33 → 0.1.36

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,71 @@
1
+ import { type UpdateDeps } from './update.js';
2
+ export declare const CHECK_INTERVAL_MS: number;
3
+ export declare const REFRESH_CHILD_ENV = "ASSETHUB_UPDATE_REFRESH_CHILD";
4
+ /** What the last background install did; announced once, then marked. */
5
+ export type AutoUpdateResult = {
6
+ from: string;
7
+ to: string;
8
+ at: string;
9
+ announced?: boolean;
10
+ } | {
11
+ from: string;
12
+ error: string;
13
+ at: string;
14
+ announced?: boolean;
15
+ };
16
+ export type UpdateCheckCache = {
17
+ checkedAt: string;
18
+ latest: string;
19
+ autoUpdate?: AutoUpdateResult;
20
+ /** When the `is available` line was last shown: at most once a day. */
21
+ noticedAt?: string;
22
+ };
23
+ export declare const updateCheckPath: (configPath: string) => string;
24
+ export declare const readUpdateCheck: (path: string) => Promise<UpdateCheckCache | undefined>;
25
+ export declare const writeUpdateCheck: (path: string, latest: string, now?: Date, autoUpdate?: AutoUpdateResult) => Promise<void>;
26
+ /** Rewrites the cache with some fields changed (announcement bookkeeping by the foreground command). */
27
+ export declare const patchUpdateCheck: (path: string, cache: UpdateCheckCache, patch: Partial<UpdateCheckCache>) => Promise<void>;
28
+ export declare const ATTEMPT_INTERVAL_MS: number;
29
+ export declare const attemptPath: (cachePath: string) => string;
30
+ /** Whether a refresh was started recently enough not to start another; records this attempt when not. */
31
+ export declare const claimRefreshAttempt: (cachePath: string, now?: Date) => Promise<boolean>;
32
+ export declare const settingsPath: (configPath: string) => string;
33
+ /** The saved choice: true or false, or undefined when never set (which means on). */
34
+ export declare const readAutoUpdateSetting: (path: string) => Promise<boolean | undefined>;
35
+ export declare const writeAutoUpdateSetting: (path: string, autoUpdate: boolean) => Promise<void>;
36
+ /** Auto-update is on by default; the saved setting or ASSETHUB_NO_AUTO_UPDATE turns it off. */
37
+ export declare const autoUpdateEnabled: (setting: boolean | undefined, env: NodeJS.ProcessEnv) => boolean;
38
+ /**
39
+ * Only a global install can be upgraded by `assethub update`: a project
40
+ * dependency, an npx run or a source checkout has nothing to announce. Same
41
+ * layouts as update.ts's detectInstall, without running `npm root --global`
42
+ * on every command.
43
+ */
44
+ export declare const isInstalledPackage: (cliPath: string) => boolean;
45
+ /** Opted out, or running somewhere nobody reads a hint (CI). */
46
+ export declare const updateCheckDisabled: (env: NodeJS.ProcessEnv) => boolean;
47
+ export type NoticeDecision = {
48
+ notices: string[];
49
+ refresh: boolean;
50
+ markAnnounced: boolean;
51
+ markNoticed: boolean;
52
+ };
53
+ /** What to print now, whether to mark a background install as announced, and whether to refresh. */
54
+ export declare const decideUpdateNotice: (current: string, cache: UpdateCheckCache | undefined, now: Date) => NoticeDecision;
55
+ /** One install at a time, background or `assethub update`: a second one leaves it alone. */
56
+ export declare const takeLock: (path: string) => Promise<boolean>;
57
+ /**
58
+ * Reads the latest version and, when auto-update is on and it is newer,
59
+ * installs it through `runUpdate` (same installer, same login check as
60
+ * `assethub update --yes`). Records the outcome for the next command to announce.
61
+ */
62
+ export declare const refreshUpdateCheck: (input: {
63
+ cachePath: string;
64
+ lockPath: string;
65
+ autoUpdate: boolean;
66
+ deps: UpdateDeps;
67
+ now?: () => Date;
68
+ }) => Promise<UpdateCheckCache | undefined>;
69
+ /** Starts the background refresh and returns at once; the child outlives this process. */
70
+ export declare const refreshInBackground: (node: string, cli: string, configPath: string) => void;
71
+ //# sourceMappingURL=updateNotice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updateNotice.d.ts","sourceRoot":"","sources":["../src/updateNotice.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,189 @@
1
+ // Keeping the CLI current. Once a day a detached `assethub update --refresh-check`
2
+ // process reads the latest version and, with auto-update on (the default),
3
+ // installs it the way `assethub update` would. No command ever waits on the
4
+ // network or the install: each one only reads the small cache file the
5
+ // background process leaves behind, announces what happened once, and moves on.
6
+ import { spawn } from 'node:child_process';
7
+ import { mkdir, open, readFile, rm, stat, writeFile } from 'node:fs/promises';
8
+ import { dirname, join } from 'node:path';
9
+ import { compareVersions, PACKAGE_NAME, runUpdate } from './update.js';
10
+ export const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
11
+ export const REFRESH_CHILD_ENV = 'ASSETHUB_UPDATE_REFRESH_CHILD';
12
+ export const updateCheckPath = (configPath) => join(dirname(configPath), 'update-check.json');
13
+ export const readUpdateCheck = async (path) => {
14
+ try {
15
+ const cache = JSON.parse(await readFile(path, 'utf8'));
16
+ return typeof cache.checkedAt === 'string' && typeof cache.latest === 'string'
17
+ ? {
18
+ checkedAt: cache.checkedAt,
19
+ latest: cache.latest,
20
+ ...(cache.autoUpdate && typeof cache.autoUpdate === 'object' ? { autoUpdate: cache.autoUpdate } : {}),
21
+ ...(typeof cache.noticedAt === 'string' ? { noticedAt: cache.noticedAt } : {}),
22
+ }
23
+ : undefined;
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ };
29
+ export const writeUpdateCheck = async (path, latest, now = new Date(), autoUpdate) => {
30
+ await mkdir(dirname(path), { recursive: true });
31
+ const cache = { checkedAt: now.toISOString(), latest, ...(autoUpdate ? { autoUpdate } : {}) };
32
+ await writeFile(path, `${JSON.stringify(cache)}\n`);
33
+ };
34
+ /** Rewrites the cache with some fields changed (announcement bookkeeping by the foreground command). */
35
+ export const patchUpdateCheck = async (path, cache, patch) => {
36
+ await writeFile(path, `${JSON.stringify({ ...cache, ...patch })}\n`);
37
+ };
38
+ // A failed or slow refresh leaves the cache stale; this marker keeps every
39
+ // command from starting another one. Retried at most once an hour.
40
+ export const ATTEMPT_INTERVAL_MS = 60 * 60 * 1000;
41
+ export const attemptPath = (cachePath) => `${cachePath}.attempt`;
42
+ /** Whether a refresh was started recently enough not to start another; records this attempt when not. */
43
+ export const claimRefreshAttempt = async (cachePath, now = new Date()) => {
44
+ const path = attemptPath(cachePath);
45
+ const last = await readFile(path, 'utf8').then(text => Date.parse(text.trim()), () => Number.NaN);
46
+ if (Number.isFinite(last) && now.getTime() - last < ATTEMPT_INTERVAL_MS && last <= now.getTime())
47
+ return false;
48
+ await mkdir(dirname(path), { recursive: true });
49
+ await writeFile(path, `${now.toISOString()}\n`);
50
+ return true;
51
+ };
52
+ // ---- the auto-update setting (on unless turned off) ----
53
+ export const settingsPath = (configPath) => join(dirname(configPath), 'settings.json');
54
+ /** The saved choice: true or false, or undefined when never set (which means on). */
55
+ export const readAutoUpdateSetting = async (path) => {
56
+ try {
57
+ const value = JSON.parse(await readFile(path, 'utf8')).autoUpdate;
58
+ return typeof value === 'boolean' ? value : undefined;
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ };
64
+ export const writeAutoUpdateSetting = async (path, autoUpdate) => {
65
+ let settings = {};
66
+ try {
67
+ const parsed = JSON.parse(await readFile(path, 'utf8'));
68
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
69
+ settings = parsed;
70
+ }
71
+ catch {
72
+ // missing or unreadable: start fresh
73
+ }
74
+ await mkdir(dirname(path), { recursive: true });
75
+ await writeFile(path, `${JSON.stringify({ ...settings, autoUpdate }, null, 2)}\n`);
76
+ };
77
+ const truthy = (value) => ['1', 'true', 'yes'].includes((value ?? '').toLowerCase());
78
+ /** Auto-update is on by default; the saved setting or ASSETHUB_NO_AUTO_UPDATE turns it off. */
79
+ export const autoUpdateEnabled = (setting, env) => setting !== false && !truthy(env.ASSETHUB_NO_AUTO_UPDATE) && !updateCheckDisabled(env);
80
+ /**
81
+ * Only a global install can be upgraded by `assethub update`: a project
82
+ * dependency, an npx run or a source checkout has nothing to announce. Same
83
+ * layouts as update.ts's detectInstall, without running `npm root --global`
84
+ * on every command.
85
+ */
86
+ export const isInstalledPackage = (cliPath) => {
87
+ const path = cliPath.replaceAll('\\', '/');
88
+ if (!path.includes(`/node_modules/${PACKAGE_NAME}/`) || path.includes('/_npx/'))
89
+ return false;
90
+ return [
91
+ `/lib/node_modules/${PACKAGE_NAME}/`, // npm, nvm, Homebrew node, a custom npm prefix
92
+ `/npm/node_modules/${PACKAGE_NAME}/`, // npm on Windows (%AppData%/npm)
93
+ '/.volta/',
94
+ '/.bun/install/global/',
95
+ '/pnpm/global/',
96
+ '/yarn/global/',
97
+ ].some(segment => path.includes(segment));
98
+ };
99
+ /** Opted out, or running somewhere nobody reads a hint (CI). */
100
+ export const updateCheckDisabled = (env) => truthy(env.ASSETHUB_NO_UPDATE_CHECK) || Boolean(env.CI);
101
+ const TURN_OFF = 'turn off with `assethub setup --no-auto-update`';
102
+ /** What to print now, whether to mark a background install as announced, and whether to refresh. */
103
+ export const decideUpdateNotice = (current, cache, now) => {
104
+ const checked = cache ? Date.parse(cache.checkedAt) : Number.NaN;
105
+ const refresh = !Number.isFinite(checked) || now.getTime() - checked >= CHECK_INTERVAL_MS || checked > now.getTime();
106
+ const notices = [];
107
+ const result = cache?.autoUpdate;
108
+ const announce = result !== undefined && !result.announced;
109
+ if (announce && 'to' in result && result.to === current)
110
+ notices.push(`${PACKAGE_NAME} updated itself from ${result.from} to ${result.to} (auto-update; ${TURN_OFF}).`);
111
+ if (announce && 'error' in result)
112
+ notices.push(`${PACKAGE_NAME} could not update itself to ${cache?.latest}: ${result.error}`);
113
+ const noticed = cache?.noticedAt ? Date.parse(cache.noticedAt) : Number.NaN;
114
+ const noticeDue = !Number.isFinite(noticed) || now.getTime() - noticed >= CHECK_INTERVAL_MS || noticed > now.getTime();
115
+ // A failed install is reported together with the release it could not install.
116
+ const available = cache !== undefined && compareVersions(cache.latest, current) > 0 && (noticeDue || (announce && 'error' in result));
117
+ if (available)
118
+ notices.push(`${PACKAGE_NAME} ${cache.latest} is available (you have ${current}). Run \`assethub update\` to upgrade; your login is kept.`);
119
+ return { notices, refresh, markAnnounced: announce, markNoticed: available };
120
+ };
121
+ // ---- the background refresh ----
122
+ const LOCK_STALE_MS = 15 * 60 * 1000;
123
+ /** One install at a time, background or `assethub update`: a second one leaves it alone. */
124
+ export const takeLock = async (path) => {
125
+ await mkdir(dirname(path), { recursive: true });
126
+ try {
127
+ await (await open(path, 'wx')).close();
128
+ return true;
129
+ }
130
+ catch {
131
+ const age = await stat(path).then(info => Date.now() - info.mtimeMs, () => 0);
132
+ if (age < LOCK_STALE_MS)
133
+ return false;
134
+ await rm(path, { force: true });
135
+ try {
136
+ await (await open(path, 'wx')).close();
137
+ return true;
138
+ }
139
+ catch {
140
+ return false;
141
+ }
142
+ }
143
+ };
144
+ /**
145
+ * Reads the latest version and, when auto-update is on and it is newer,
146
+ * installs it through `runUpdate` (same installer, same login check as
147
+ * `assethub update --yes`). Records the outcome for the next command to announce.
148
+ */
149
+ export const refreshUpdateCheck = async (input) => {
150
+ const now = input.now ?? (() => new Date());
151
+ if (!(await takeLock(input.lockPath)))
152
+ return undefined;
153
+ try {
154
+ const [current, latest] = await Promise.all([input.deps.currentVersion(), input.deps.latestVersion()]);
155
+ let autoUpdate;
156
+ if (input.autoUpdate && compareVersions(latest, current) > 0) {
157
+ try {
158
+ const report = await runUpdate({ yes: true }, { ...input.deps, latestVersion: async () => latest, interactive: false });
159
+ if (report.status === 'updated')
160
+ autoUpdate = { from: report.from, to: report.to, at: now().toISOString() };
161
+ }
162
+ catch (error) {
163
+ autoUpdate = { from: current, error: error instanceof Error ? error.message : String(error), at: now().toISOString() };
164
+ }
165
+ }
166
+ await writeUpdateCheck(input.cachePath, latest, now(), autoUpdate);
167
+ return { checkedAt: now().toISOString(), latest, ...(autoUpdate ? { autoUpdate } : {}) };
168
+ }
169
+ finally {
170
+ await rm(input.lockPath, { force: true });
171
+ }
172
+ };
173
+ /** Starts the background refresh and returns at once; the child outlives this process. */
174
+ export const refreshInBackground = (node, cli, configPath) => {
175
+ try {
176
+ const child = spawn(node, [cli, 'update', '--refresh-check', '--config', configPath], {
177
+ detached: true,
178
+ stdio: 'ignore',
179
+ // Marks the child (and the `assethub doctor` it runs to verify an install) so it
180
+ // neither prints notices nor starts another refresh.
181
+ env: { ...process.env, [REFRESH_CHILD_ENV]: '1' },
182
+ });
183
+ child.on('error', () => { });
184
+ child.unref();
185
+ }
186
+ catch {
187
+ // best effort: a missing hint must never fail the user's command
188
+ }
189
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assethub/cli",
3
- "version": "0.1.33",
3
+ "version": "0.1.36",
4
4
  "description": "Command line interface for AssetHub.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,7 @@ description: |
12
12
  - Recording whether a generated asset is acceptable
13
13
 
14
14
  DO NOT TRIGGER for:
15
- - Installing or configuring the CLI (use `assethub init`, `assethub auth login`)
15
+ - Installing or configuring the CLI (use `assethub setup`, `assethub doctor --setup`)
16
16
  - Managing workspace members (use `assethub workspace ...`)
17
17
  ---
18
18
 
@@ -31,6 +31,23 @@ description: |
31
31
 
32
32
  Always in this order. Skipping a step is how credits get wasted.
33
33
 
34
+ ### 0. Make sure the CLI is current (once per session)
35
+
36
+ ```bash
37
+ assethub update --check
38
+ ```
39
+
40
+ Operations, models and these rules change between releases, and an old CLI can reject flags the server now expects or miss a safety fix. Run this once at the start of a session, before the first paid call. It is free.
41
+
42
+ Auto-update is on by default: once a day the CLI installs a newer release by itself in the background, and the next command says so on stderr (`updated itself from … to …`). So this check usually reports `up_to_date`. It is still needed: auto-update may be off (`assethub setup --no-auto-update`, `ASSETHUB_NO_AUTO_UPDATE=1`, CI), may have failed, or may not have run yet today.
43
+
44
+ - `"status": "up_to_date"`: carry on.
45
+ - `"status": "update_available"`: run `assethub update --yes`. It upgrades the global install with the package manager that installed it and keeps the saved login. Tell the user which version you moved from and to (`from`, `to`). The next `assethub` command refreshes this skill automatically, so re-read it after that command; its rules may have changed.
46
+ - `update` refuses (an `npx` or project-local install, or a source checkout) or fails (no network): continue on the current version and tell the user once. Do not retry it in a loop.
47
+ - Do not update while another `assethub` command is still running (a `--wait` in another terminal). Runs on the server are not affected by an update; resume them afterwards with their `runId`.
48
+
49
+ The CLI also prints a one-line `is available` or `could not update itself` notice on stderr. Treat it the same way. When you see `updated itself`, re-read this skill after that command, as above. Never turn auto-update off for the user.
50
+
34
51
  ### 1. Check what this key can do
35
52
 
36
53
  ```bash
@@ -115,7 +132,7 @@ assethub evaluations submit --canvas <canvas-id> --artifact <asset-id> \
115
132
 
116
133
  - `production run` defaults to `full_auto`, which never pauses for a human. **Always pass `--max-cost-credits <n>`.** The server caps `--max-iterations` at 5.
117
134
  - Over MCP the same rule applies: always set `maxCostCredits` on `production_run`.
118
- - `assethub account get` shows the balance. Check it before a batch.
135
+ - Before a batch, run `production run --image <file> --estimate` to see the cost. The CLI has no balance command; find the credit routes with `assethub api search credit`.
119
136
  - Prefer a dedicated, scoped API key for agent work rather than a full-access one.
120
137
 
121
138
  ## Rules learned the hard way
@@ -124,7 +141,7 @@ assethub evaluations submit --canvas <canvas-id> --artifact <asset-id> \
124
141
  - Part extractor names are the public product names: `V1.5`, `V2.0 alpha`, `V2.1 alpha`. Internal IDs are not accepted.
125
142
  - `parts split` and `parts compare` with `--preprocess-prompt` create a second production order; use `--all-ready`, not `--task-id`.
126
143
  - Every generation should land on a canvas. Pass `--canvas <id>` to keep a job's steps together; without it the CLI makes one canvas per working directory. `assethub canvas open <id>` shows the user what happened.
127
- - A complaint like "the mesh has extra limbs" is usually an input problem: stray lines, shadows, or inconsistent views. Clean the image (`image edit`) before switching models.
144
+ - A complaint like "the mesh has extra limbs" is usually an input problem: stray lines, shadows, or inconsistent views. Clean the image before switching models: `image generate --file <image> --prompt "remove stray lines and shadows, plain background"`, or `parts compare --preprocess-prompt <text>`.
128
145
  - Input can come from a file, a URL, stdin bytes, base64, a data URI, an OpenAI- or Anthropic-style JSON attachment (`--stdin-json`), or the clipboard. You never need to write a temporary file first.
129
146
  - Large or complex requests: `--input-json @request.json` with the schema from `api describe`.
130
147