@ai-substrate/engineering-harness 0.2.0-canary.45
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/LICENSE +21 -0
- package/README.md +119 -0
- package/harness/cli/bin/harness.js +12 -0
- package/harness/cli/dist/acts/docs.d.ts +17 -0
- package/harness/cli/dist/acts/docs.js +73 -0
- package/harness/cli/dist/acts/docs.js.map +1 -0
- package/harness/cli/dist/acts/doctor.d.ts +14 -0
- package/harness/cli/dist/acts/doctor.js +43 -0
- package/harness/cli/dist/acts/doctor.js.map +1 -0
- package/harness/cli/dist/acts/help.d.ts +14 -0
- package/harness/cli/dist/acts/help.js +29 -0
- package/harness/cli/dist/acts/help.js.map +1 -0
- package/harness/cli/dist/acts/init.d.ts +22 -0
- package/harness/cli/dist/acts/init.js +61 -0
- package/harness/cli/dist/acts/init.js.map +1 -0
- package/harness/cli/dist/acts/instructions.d.ts +21 -0
- package/harness/cli/dist/acts/instructions.js +75 -0
- package/harness/cli/dist/acts/instructions.js.map +1 -0
- package/harness/cli/dist/acts/new.d.ts +19 -0
- package/harness/cli/dist/acts/new.js +66 -0
- package/harness/cli/dist/acts/new.js.map +1 -0
- package/harness/cli/dist/acts/observe.d.ts +23 -0
- package/harness/cli/dist/acts/observe.js +129 -0
- package/harness/cli/dist/acts/observe.js.map +1 -0
- package/harness/cli/dist/acts/record.d.ts +24 -0
- package/harness/cli/dist/acts/record.js +93 -0
- package/harness/cli/dist/acts/record.js.map +1 -0
- package/harness/cli/dist/acts/skills.d.ts +32 -0
- package/harness/cli/dist/acts/skills.js +256 -0
- package/harness/cli/dist/acts/skills.js.map +1 -0
- package/harness/cli/dist/acts/update.d.ts +23 -0
- package/harness/cli/dist/acts/update.js +297 -0
- package/harness/cli/dist/acts/update.js.map +1 -0
- package/harness/cli/dist/acts/verb.d.ts +27 -0
- package/harness/cli/dist/acts/verb.js +56 -0
- package/harness/cli/dist/acts/verb.js.map +1 -0
- package/harness/cli/dist/adapters/clock/clock-port.d.ts +10 -0
- package/harness/cli/dist/adapters/clock/clock-port.js +2 -0
- package/harness/cli/dist/adapters/clock/clock-port.js.map +1 -0
- package/harness/cli/dist/adapters/clock/fake-clock.d.ts +15 -0
- package/harness/cli/dist/adapters/clock/fake-clock.js +25 -0
- package/harness/cli/dist/adapters/clock/fake-clock.js.map +1 -0
- package/harness/cli/dist/adapters/clock/system-clock.d.ts +5 -0
- package/harness/cli/dist/adapters/clock/system-clock.js +7 -0
- package/harness/cli/dist/adapters/clock/system-clock.js.map +1 -0
- package/harness/cli/dist/adapters/env/env-port.d.ts +17 -0
- package/harness/cli/dist/adapters/env/env-port.js +2 -0
- package/harness/cli/dist/adapters/env/env-port.js.map +1 -0
- package/harness/cli/dist/adapters/env/fake-env.d.ts +15 -0
- package/harness/cli/dist/adapters/env/fake-env.js +24 -0
- package/harness/cli/dist/adapters/env/fake-env.js.map +1 -0
- package/harness/cli/dist/adapters/env/node-env.d.ts +6 -0
- package/harness/cli/dist/adapters/env/node-env.js +16 -0
- package/harness/cli/dist/adapters/env/node-env.js.map +1 -0
- package/harness/cli/dist/adapters/exec/exec-port.d.ts +22 -0
- package/harness/cli/dist/adapters/exec/exec-port.js +2 -0
- package/harness/cli/dist/adapters/exec/exec-port.js.map +1 -0
- package/harness/cli/dist/adapters/exec/fake-exec.d.ts +25 -0
- package/harness/cli/dist/adapters/exec/fake-exec.js +25 -0
- package/harness/cli/dist/adapters/exec/fake-exec.js.map +1 -0
- package/harness/cli/dist/adapters/exec/node-exec.d.ts +14 -0
- package/harness/cli/dist/adapters/exec/node-exec.js +38 -0
- package/harness/cli/dist/adapters/exec/node-exec.js.map +1 -0
- package/harness/cli/dist/adapters/fs/fake-fs.d.ts +22 -0
- package/harness/cli/dist/adapters/fs/fake-fs.js +63 -0
- package/harness/cli/dist/adapters/fs/fake-fs.js.map +1 -0
- package/harness/cli/dist/adapters/fs/fs-port.d.ts +20 -0
- package/harness/cli/dist/adapters/fs/fs-port.js +2 -0
- package/harness/cli/dist/adapters/fs/fs-port.js.map +1 -0
- package/harness/cli/dist/adapters/fs/node-fs.d.ts +9 -0
- package/harness/cli/dist/adapters/fs/node-fs.js +30 -0
- package/harness/cli/dist/adapters/fs/node-fs.js.map +1 -0
- package/harness/cli/dist/adapters/git/exec-git.d.ts +6 -0
- package/harness/cli/dist/adapters/git/exec-git.js +21 -0
- package/harness/cli/dist/adapters/git/exec-git.js.map +1 -0
- package/harness/cli/dist/adapters/git/fake-git.d.ts +15 -0
- package/harness/cli/dist/adapters/git/fake-git.js +20 -0
- package/harness/cli/dist/adapters/git/fake-git.js.map +1 -0
- package/harness/cli/dist/adapters/git/git-port.d.ts +12 -0
- package/harness/cli/dist/adapters/git/git-port.js +2 -0
- package/harness/cli/dist/adapters/git/git-port.js.map +1 -0
- package/harness/cli/dist/adapters/loader/fake-loader.d.ts +13 -0
- package/harness/cli/dist/adapters/loader/fake-loader.js +25 -0
- package/harness/cli/dist/adapters/loader/fake-loader.js.map +1 -0
- package/harness/cli/dist/adapters/loader/jiti-loader.d.ts +15 -0
- package/harness/cli/dist/adapters/loader/jiti-loader.js +29 -0
- package/harness/cli/dist/adapters/loader/jiti-loader.js.map +1 -0
- package/harness/cli/dist/adapters/loader/module-loader-port.d.ts +12 -0
- package/harness/cli/dist/adapters/loader/module-loader-port.js +2 -0
- package/harness/cli/dist/adapters/loader/module-loader-port.js.map +1 -0
- package/harness/cli/dist/adapters/process/fake-process.d.ts +13 -0
- package/harness/cli/dist/adapters/process/fake-process.js +21 -0
- package/harness/cli/dist/adapters/process/fake-process.js.map +1 -0
- package/harness/cli/dist/adapters/process/node-process.d.ts +6 -0
- package/harness/cli/dist/adapters/process/node-process.js +17 -0
- package/harness/cli/dist/adapters/process/node-process.js.map +1 -0
- package/harness/cli/dist/adapters/process/process-port.d.ts +13 -0
- package/harness/cli/dist/adapters/process/process-port.js +2 -0
- package/harness/cli/dist/adapters/process/process-port.js.map +1 -0
- package/harness/cli/dist/adapters/version-lookup/fake-version-lookup.d.ts +13 -0
- package/harness/cli/dist/adapters/version-lookup/fake-version-lookup.js +21 -0
- package/harness/cli/dist/adapters/version-lookup/fake-version-lookup.js.map +1 -0
- package/harness/cli/dist/adapters/version-lookup/node-version-lookup.d.ts +18 -0
- package/harness/cli/dist/adapters/version-lookup/node-version-lookup.js +51 -0
- package/harness/cli/dist/adapters/version-lookup/node-version-lookup.js.map +1 -0
- package/harness/cli/dist/adapters/version-lookup/version-lookup-port.d.ts +19 -0
- package/harness/cli/dist/adapters/version-lookup/version-lookup-port.js +2 -0
- package/harness/cli/dist/adapters/version-lookup/version-lookup-port.js.map +1 -0
- package/harness/cli/dist/app.d.ts +70 -0
- package/harness/cli/dist/app.js +221 -0
- package/harness/cli/dist/app.js.map +1 -0
- package/harness/cli/dist/index.d.ts +2 -0
- package/harness/cli/dist/index.js +26 -0
- package/harness/cli/dist/index.js.map +1 -0
- package/harness/cli/dist/output/envelope.d.ts +68 -0
- package/harness/cli/dist/output/envelope.js +56 -0
- package/harness/cli/dist/output/envelope.js.map +1 -0
- package/harness/cli/dist/output/error-codes.d.ts +57 -0
- package/harness/cli/dist/output/error-codes.js +57 -0
- package/harness/cli/dist/output/error-codes.js.map +1 -0
- package/harness/cli/dist/output/exit.d.ts +29 -0
- package/harness/cli/dist/output/exit.js +36 -0
- package/harness/cli/dist/output/exit.js.map +1 -0
- package/harness/cli/dist/output/output-port.d.ts +54 -0
- package/harness/cli/dist/output/output-port.js +55 -0
- package/harness/cli/dist/output/output-port.js.map +1 -0
- package/harness/cli/dist/output/style.d.ts +33 -0
- package/harness/cli/dist/output/style.js +68 -0
- package/harness/cli/dist/output/style.js.map +1 -0
- package/harness/cli/dist/services/config/load-config.d.ts +27 -0
- package/harness/cli/dist/services/config/load-config.js +114 -0
- package/harness/cli/dist/services/config/load-config.js.map +1 -0
- package/harness/cli/dist/services/docs/contract.d.ts +41 -0
- package/harness/cli/dist/services/docs/contract.js +14 -0
- package/harness/cli/dist/services/docs/contract.js.map +1 -0
- package/harness/cli/dist/services/docs/docs-content.d.ts +37 -0
- package/harness/cli/dist/services/docs/docs-content.js +48 -0
- package/harness/cli/dist/services/docs/docs-content.js.map +1 -0
- package/harness/cli/dist/services/docs/docs-service.d.ts +26 -0
- package/harness/cli/dist/services/docs/docs-service.js +25 -0
- package/harness/cli/dist/services/docs/docs-service.js.map +1 -0
- package/harness/cli/dist/services/doctor/doctor-service.d.ts +69 -0
- package/harness/cli/dist/services/doctor/doctor-service.js +237 -0
- package/harness/cli/dist/services/doctor/doctor-service.js.map +1 -0
- package/harness/cli/dist/services/extensions/contract.d.ts +138 -0
- package/harness/cli/dist/services/extensions/contract.js +17 -0
- package/harness/cli/dist/services/extensions/contract.js.map +1 -0
- package/harness/cli/dist/services/extensions/discovery.d.ts +53 -0
- package/harness/cli/dist/services/extensions/discovery.js +116 -0
- package/harness/cli/dist/services/extensions/discovery.js.map +1 -0
- package/harness/cli/dist/services/extensions/registry.d.ts +63 -0
- package/harness/cli/dist/services/extensions/registry.js +165 -0
- package/harness/cli/dist/services/extensions/registry.js.map +1 -0
- package/harness/cli/dist/services/extensions/verb-context.d.ts +44 -0
- package/harness/cli/dist/services/extensions/verb-context.js +97 -0
- package/harness/cli/dist/services/extensions/verb-context.js.map +1 -0
- package/harness/cli/dist/services/help/help-service.d.ts +42 -0
- package/harness/cli/dist/services/help/help-service.js +108 -0
- package/harness/cli/dist/services/help/help-service.js.map +1 -0
- package/harness/cli/dist/services/init/governance-template.d.ts +27 -0
- package/harness/cli/dist/services/init/governance-template.js +72 -0
- package/harness/cli/dist/services/init/governance-template.js.map +1 -0
- package/harness/cli/dist/services/init/init-service.d.ts +38 -0
- package/harness/cli/dist/services/init/init-service.js +44 -0
- package/harness/cli/dist/services/init/init-service.js.map +1 -0
- package/harness/cli/dist/services/instructions/core-instructions.d.ts +11 -0
- package/harness/cli/dist/services/instructions/core-instructions.js +80 -0
- package/harness/cli/dist/services/instructions/core-instructions.js.map +1 -0
- package/harness/cli/dist/services/instructions/instructions-service.d.ts +52 -0
- package/harness/cli/dist/services/instructions/instructions-service.js +53 -0
- package/harness/cli/dist/services/instructions/instructions-service.js.map +1 -0
- package/harness/cli/dist/services/observe/buffer-codec.d.ts +51 -0
- package/harness/cli/dist/services/observe/buffer-codec.js +139 -0
- package/harness/cli/dist/services/observe/buffer-codec.js.map +1 -0
- package/harness/cli/dist/services/observe/observe-service.d.ts +87 -0
- package/harness/cli/dist/services/observe/observe-service.js +221 -0
- package/harness/cli/dist/services/observe/observe-service.js.map +1 -0
- package/harness/cli/dist/services/record/contract.d.ts +32 -0
- package/harness/cli/dist/services/record/contract.js +17 -0
- package/harness/cli/dist/services/record/contract.js.map +1 -0
- package/harness/cli/dist/services/record/core-types/retro.d.ts +20 -0
- package/harness/cli/dist/services/record/core-types/retro.js +55 -0
- package/harness/cli/dist/services/record/core-types/retro.js.map +1 -0
- package/harness/cli/dist/services/record/record-service.d.ts +38 -0
- package/harness/cli/dist/services/record/record-service.js +144 -0
- package/harness/cli/dist/services/record/record-service.js.map +1 -0
- package/harness/cli/dist/services/record/registry.d.ts +46 -0
- package/harness/cli/dist/services/record/registry.js +71 -0
- package/harness/cli/dist/services/record/registry.js.map +1 -0
- package/harness/cli/dist/services/scaffold/scaffold-service.d.ts +29 -0
- package/harness/cli/dist/services/scaffold/scaffold-service.js +88 -0
- package/harness/cli/dist/services/scaffold/scaffold-service.js.map +1 -0
- package/harness/cli/dist/services/scaffold/templates.d.ts +42 -0
- package/harness/cli/dist/services/scaffold/templates.js +178 -0
- package/harness/cli/dist/services/scaffold/templates.js.map +1 -0
- package/harness/cli/dist/services/shared/posix-path.d.ts +54 -0
- package/harness/cli/dist/services/shared/posix-path.js +94 -0
- package/harness/cli/dist/services/shared/posix-path.js.map +1 -0
- package/harness/cli/dist/services/shared/temp.d.ts +24 -0
- package/harness/cli/dist/services/shared/temp.js +29 -0
- package/harness/cli/dist/services/shared/temp.js.map +1 -0
- package/harness/cli/dist/services/skills/contract.d.ts +52 -0
- package/harness/cli/dist/services/skills/contract.js +55 -0
- package/harness/cli/dist/services/skills/contract.js.map +1 -0
- package/harness/cli/dist/services/skills/skills-service.d.ts +73 -0
- package/harness/cli/dist/services/skills/skills-service.js +132 -0
- package/harness/cli/dist/services/skills/skills-service.js.map +1 -0
- package/harness/cli/dist/services/update/banner.d.ts +26 -0
- package/harness/cli/dist/services/update/banner.js +28 -0
- package/harness/cli/dist/services/update/banner.js.map +1 -0
- package/harness/cli/dist/services/update/cache.d.ts +21 -0
- package/harness/cli/dist/services/update/cache.js +61 -0
- package/harness/cli/dist/services/update/cache.js.map +1 -0
- package/harness/cli/dist/services/update/constants.d.ts +9 -0
- package/harness/cli/dist/services/update/constants.js +10 -0
- package/harness/cli/dist/services/update/constants.js.map +1 -0
- package/harness/cli/dist/services/update/install.d.ts +26 -0
- package/harness/cli/dist/services/update/install.js +78 -0
- package/harness/cli/dist/services/update/install.js.map +1 -0
- package/harness/cli/dist/services/update/semver.d.ts +16 -0
- package/harness/cli/dist/services/update/semver.js +108 -0
- package/harness/cli/dist/services/update/semver.js.map +1 -0
- package/harness/cli/dist/services/update/update-service.d.ts +46 -0
- package/harness/cli/dist/services/update/update-service.js +91 -0
- package/harness/cli/dist/services/update/update-service.js.map +1 -0
- package/harness/cli/dist/version.d.ts +8 -0
- package/harness/cli/dist/version.js +15 -0
- package/harness/cli/dist/version.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/** GitHub `owner/repo` shorthand, optionally with a `/subdir` tail. */
|
|
2
|
+
const GH_SHORTHAND = /^([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)(?:\/(.+))?$/;
|
|
3
|
+
/** A bare GitHub repo URL (`https://github.com/owner/repo`, optional `.git` / trailing slash) — no further path. */
|
|
4
|
+
const GH_URL = /^https?:\/\/github\.com\/([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/;
|
|
5
|
+
/**
|
|
6
|
+
* Translate a friendly `--source` (+ optional `--branch`, or a `#ref` suffix on
|
|
7
|
+
* the source) into the specifier the Vercel installer truly accepts.
|
|
8
|
+
*
|
|
9
|
+
* Why this exists: `npx skills add` has NO `--branch`/`--ref` flag and NO
|
|
10
|
+
* `owner/repo#ref` shorthand (vercel-labs/skills#42 is an open request for it).
|
|
11
|
+
* Its ONLY documented branch mechanism is a GitHub *tree URL*:
|
|
12
|
+
* `https://github.com/<owner>/<repo>/tree/<ref>[/<subdir>]`. So when the user
|
|
13
|
+
* names a branch, we rewrite the GitHub shorthand into that URL form.
|
|
14
|
+
*
|
|
15
|
+
* Invariants:
|
|
16
|
+
* - **No ref → verbatim pass-through.** The source is returned unchanged (the
|
|
17
|
+
* default `owner/repo` shorthand installs the default branch, exactly as
|
|
18
|
+
* before this flag existed) — so every existing call site is untouched.
|
|
19
|
+
* - **Ref precedence**: an explicit `branch` arg wins over a `#ref` suffix.
|
|
20
|
+
* - **Slashed refs are rejected** (`ok:false`). The installer reads the first
|
|
21
|
+
* path segment after `/tree/` as the *entire* ref, so `feat/x` would silently
|
|
22
|
+
* mis-resolve to branch `feat` + subdir `x`. Failing fast (deterministic
|
|
23
|
+
* backpressure) beats handing the installer a URL it will mis-parse.
|
|
24
|
+
* - **Branch only applies to GitHub sources** (`owner/repo[/subdir]` or
|
|
25
|
+
* `https://github.com/owner/repo`). A branch against a local path / GitLab /
|
|
26
|
+
* generic git URL is rejected with guidance rather than ignored.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveSkillsSource(rawSource, branch) {
|
|
29
|
+
const hashIdx = rawSource.indexOf('#');
|
|
30
|
+
const base = hashIdx === -1 ? rawSource : rawSource.slice(0, hashIdx);
|
|
31
|
+
const refFromHash = hashIdx === -1 ? undefined : rawSource.slice(hashIdx + 1);
|
|
32
|
+
const ref = (branch ?? refFromHash)?.trim() || undefined;
|
|
33
|
+
// No ref → preserve today's verbatim pass-through (installs the default branch).
|
|
34
|
+
if (!ref) {
|
|
35
|
+
return { ok: true, source: base };
|
|
36
|
+
}
|
|
37
|
+
if (ref.includes('/')) {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
reason: `branch '${ref}' contains a '/': the \`npx skills add\` GitHub tree-URL form cannot ` +
|
|
41
|
+
'express a slashed branch name (it reads the first path segment after /tree/ as the ' +
|
|
42
|
+
'whole ref). Use a single-segment branch, merge the skills to the default branch, or ' +
|
|
43
|
+
'pass a local --source path.',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Local path or non-GitHub URL → branch translation doesn't apply.
|
|
47
|
+
const looksLocalOrUrl = base.startsWith('.') || base.startsWith('/') || (base.includes('://') && !GH_URL.test(base));
|
|
48
|
+
const shorthand = looksLocalOrUrl ? null : base.match(GH_SHORTHAND);
|
|
49
|
+
if (shorthand) {
|
|
50
|
+
const [, owner, repo, subdir] = shorthand;
|
|
51
|
+
const tail = subdir ? `/${subdir}` : '';
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
source: `https://github.com/${owner}/${repo}/tree/${ref}${tail}`,
|
|
55
|
+
branch: ref,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const url = base.match(GH_URL);
|
|
59
|
+
if (url) {
|
|
60
|
+
const [, owner, repo] = url;
|
|
61
|
+
return { ok: true, source: `https://github.com/${owner}/${repo}/tree/${ref}`, branch: ref };
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
reason: `--branch only applies to a GitHub \`owner/repo\` (optionally \`owner/repo/subdir\`) or ` +
|
|
66
|
+
`\`https://github.com/owner/repo\` source; '${base}' isn't one. Drop --branch, or point ` +
|
|
67
|
+
'--source at a GitHub repo.',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Pure `npx skills add …` argv builder — the one place the Vercel `skills` flag
|
|
72
|
+
* surface is encoded (Principle 8: wrap, don't rebuild). No I/O, no child spawn:
|
|
73
|
+
* the act feeds the returned argv to the injected `ExecPort`, and tests assert
|
|
74
|
+
* the exact array (Principle 3). The returned array is the args AFTER `npx`, i.e.
|
|
75
|
+
* `['skills@latest', 'add', <source>, '-a', <t>, …, '-g'?, '-s', <slug>, …, '-y']`.
|
|
76
|
+
*
|
|
77
|
+
* Invariants (AC3/AC4):
|
|
78
|
+
* - always pins `skills@latest` and always appends `-y` (UNCONDITIONALLY — the
|
|
79
|
+
* builder makes it impossible to construct a blocking invocation, so the
|
|
80
|
+
* interactive picker never appears regardless of caller input);
|
|
81
|
+
* - each target fans out as a repeated `-a <target>`;
|
|
82
|
+
* - `-g` is present iff `global` is true.
|
|
83
|
+
*
|
|
84
|
+
* Precondition: `targets` is non-empty — enforced by the act (missing `--target`
|
|
85
|
+
* → `E108`) before this is ever called.
|
|
86
|
+
*/
|
|
87
|
+
export function buildInstallArgv(opts) {
|
|
88
|
+
const argv = ['skills@latest', 'add', opts.source];
|
|
89
|
+
for (const target of opts.targets) {
|
|
90
|
+
argv.push('-a', target);
|
|
91
|
+
}
|
|
92
|
+
if (opts.global) {
|
|
93
|
+
argv.push('-g');
|
|
94
|
+
}
|
|
95
|
+
for (const slug of opts.skills ?? []) {
|
|
96
|
+
argv.push('-s', slug);
|
|
97
|
+
}
|
|
98
|
+
// Unconditional: never let the Vercel interactive picker block (AC3).
|
|
99
|
+
argv.push('-y');
|
|
100
|
+
return argv;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Pure `npx skills remove …` argv builder — the PRUNE half of `harness skills
|
|
104
|
+
* update` (Principle 8: wrap, don't rebuild). No I/O. Renamed/removed skills are
|
|
105
|
+
* passed as POSITIONAL slugs; `npx skills remove` no-ops (exit 0) on any slug that
|
|
106
|
+
* isn't installed, so it is safe to call with the full legacy list every time.
|
|
107
|
+
* Like the install builder it fans out `-a <target>`, adds `-g` iff global, and
|
|
108
|
+
* always appends `-y` (never blocks). Returns the args AFTER `npx`:
|
|
109
|
+
* `['skills@latest', 'remove', <slug>, …, '-a', <t>, …, '-g'?, '-y']`.
|
|
110
|
+
*
|
|
111
|
+
* Precondition: `slugs` and `targets` are both non-empty — enforced by the act.
|
|
112
|
+
*/
|
|
113
|
+
export function buildRemoveArgv(opts) {
|
|
114
|
+
const argv = ['skills@latest', 'remove', ...opts.slugs];
|
|
115
|
+
for (const target of opts.targets) {
|
|
116
|
+
argv.push('-a', target);
|
|
117
|
+
}
|
|
118
|
+
if (opts.global) {
|
|
119
|
+
argv.push('-g');
|
|
120
|
+
}
|
|
121
|
+
// Unconditional: never let the Vercel interactive picker block (mirrors install).
|
|
122
|
+
argv.push('-y');
|
|
123
|
+
return argv;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The human-readable command line we announce before running (and echo in the JSON
|
|
127
|
+
* envelope). Generic over any `npx skills …` argv — used for both `add` and `remove`.
|
|
128
|
+
*/
|
|
129
|
+
export function formatInstallCommand(argv) {
|
|
130
|
+
return `npx ${argv.join(' ')}`;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=skills-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills-service.js","sourceRoot":"","sources":["../../../src/services/skills/skills-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { EnvPort } from '../../adapters/env/env-port.js';
|
|
2
|
+
import type { FsPort } from '../../adapters/fs/fs-port.js';
|
|
3
|
+
import type { Envelope, UpdateAvailable } from '../../output/envelope.js';
|
|
4
|
+
import type { OutputMode, Writers } from '../../output/output-port.js';
|
|
5
|
+
/**
|
|
6
|
+
* The single human-mode stderr notice (AC8), newline-terminated. Sourced from
|
|
7
|
+
* the field so the command shown always matches `update_available.command`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatUpdateBanner(ua: UpdateAvailable): string;
|
|
10
|
+
export interface BannerDecoratorDeps {
|
|
11
|
+
fs: FsPort;
|
|
12
|
+
env: EnvPort;
|
|
13
|
+
/** Installed version (readVersion()) compared against the cached latest. */
|
|
14
|
+
installed: string;
|
|
15
|
+
mode: OutputMode;
|
|
16
|
+
writers: Writers;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build the exit-chokepoint banner decorator (wired via `setBannerDecorator` in
|
|
20
|
+
* T007). On each exit it does a SINGLE sync cache read — no network on the hot
|
|
21
|
+
* path (AC9). If a newer version is known it sets the additive `update_available`
|
|
22
|
+
* field (serialized by the JSON renderer on EVERY command, AC7) and, in human
|
|
23
|
+
* mode only, writes exactly one stderr line (AC8). Returns the structural
|
|
24
|
+
* `(env) => void` so the exit kernel never imports the service layer.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildBannerDecorator(deps: BannerDecoratorDeps): (env: Envelope) => void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { bannerFromCache } from './update-service.js';
|
|
2
|
+
/**
|
|
3
|
+
* The single human-mode stderr notice (AC8), newline-terminated. Sourced from
|
|
4
|
+
* the field so the command shown always matches `update_available.command`.
|
|
5
|
+
*/
|
|
6
|
+
export function formatUpdateBanner(ua) {
|
|
7
|
+
return `update available to ${ua.latest} from ${ua.installed} — run: ${ua.command}\n`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Build the exit-chokepoint banner decorator (wired via `setBannerDecorator` in
|
|
11
|
+
* T007). On each exit it does a SINGLE sync cache read — no network on the hot
|
|
12
|
+
* path (AC9). If a newer version is known it sets the additive `update_available`
|
|
13
|
+
* field (serialized by the JSON renderer on EVERY command, AC7) and, in human
|
|
14
|
+
* mode only, writes exactly one stderr line (AC8). Returns the structural
|
|
15
|
+
* `(env) => void` so the exit kernel never imports the service layer.
|
|
16
|
+
*/
|
|
17
|
+
export function buildBannerDecorator(deps) {
|
|
18
|
+
return (env) => {
|
|
19
|
+
const ua = bannerFromCache(deps.fs, deps.env, deps.installed);
|
|
20
|
+
if (!ua)
|
|
21
|
+
return;
|
|
22
|
+
env.update_available = ua;
|
|
23
|
+
if (deps.mode === 'human') {
|
|
24
|
+
deps.writers.err(formatUpdateBanner(ua));
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=banner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"banner.js","sourceRoot":"","sources":["../../../src/services/update/banner.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAmB;IACpD,OAAO,uBAAuB,EAAE,CAAC,MAAM,SAAS,EAAE,CAAC,SAAS,WAAW,EAAE,CAAC,OAAO,IAAI,CAAC;AACxF,CAAC;AAWD;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAyB;IAC5D,OAAO,CAAC,GAAa,EAAE,EAAE;QACvB,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9D,IAAI,CAAC,EAAE;YAAE,OAAO;QAChB,GAAG,CAAC,gBAAgB,GAAG,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { EnvPort } from '../../adapters/env/env-port.js';
|
|
2
|
+
import type { FsPort } from '../../adapters/fs/fs-port.js';
|
|
3
|
+
export interface UpdateCheckCache {
|
|
4
|
+
/** ISO-8601 instant of the last SUCCESSFUL registry lookup. */
|
|
5
|
+
last_success_iso: string;
|
|
6
|
+
/** Latest version seen at that lookup, or null if the registry returned none. */
|
|
7
|
+
latest: string | null;
|
|
8
|
+
}
|
|
9
|
+
/** Absolute logical path to the cache file, or null if home is unresolved. */
|
|
10
|
+
export declare function cachePath(env: EnvPort): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Read + validate the cache. Returns null on any of: home unresolved, file
|
|
13
|
+
* missing/unreadable, non-JSON, or wrong shape — the caller treats null as
|
|
14
|
+
* "no cached info". Never throws.
|
|
15
|
+
*/
|
|
16
|
+
export declare function readCache(fs: FsPort, env: EnvPort): UpdateCheckCache | null;
|
|
17
|
+
/**
|
|
18
|
+
* Write the cache under `~/.harness/` (creating the dir). No-op when home is
|
|
19
|
+
* unresolved — best-effort, the check still works off live lookups.
|
|
20
|
+
*/
|
|
21
|
+
export declare function writeCache(fs: FsPort, env: EnvPort, cache: UpdateCheckCache): void;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { posixJoin } from '../shared/posix-path.js';
|
|
2
|
+
import { HARNESS_DIR } from '../shared/temp.js';
|
|
3
|
+
/**
|
|
4
|
+
* User-global update-check cache: a tiny record under `~/.harness/` (NOT the
|
|
5
|
+
* repo's `.harness/`) remembering when the registry was last successfully
|
|
6
|
+
* queried and what it returned. Lets the check throttle to once / 24h and lets
|
|
7
|
+
* a known update keep showing through later lookup failures (AC5/AC6/AC9).
|
|
8
|
+
*
|
|
9
|
+
* All I/O is via `FsPort`; the home directory via `EnvPort.home()` — no `node:*`
|
|
10
|
+
* (P2). Reads are total: anything wrong ⇒ null, never a throw.
|
|
11
|
+
*/
|
|
12
|
+
const CACHE_FILE = 'update-check.json';
|
|
13
|
+
/** `<home>/.harness`, or null when the home directory can't be resolved. */
|
|
14
|
+
function harnessDir(env) {
|
|
15
|
+
const home = env.home();
|
|
16
|
+
return home ? posixJoin(home, HARNESS_DIR) : null;
|
|
17
|
+
}
|
|
18
|
+
/** Absolute logical path to the cache file, or null if home is unresolved. */
|
|
19
|
+
export function cachePath(env) {
|
|
20
|
+
const dir = harnessDir(env);
|
|
21
|
+
return dir ? posixJoin(dir, CACHE_FILE) : null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read + validate the cache. Returns null on any of: home unresolved, file
|
|
25
|
+
* missing/unreadable, non-JSON, or wrong shape — the caller treats null as
|
|
26
|
+
* "no cached info". Never throws.
|
|
27
|
+
*/
|
|
28
|
+
export function readCache(fs, env) {
|
|
29
|
+
const path = cachePath(env);
|
|
30
|
+
if (!path)
|
|
31
|
+
return null;
|
|
32
|
+
const raw = fs.readText(path);
|
|
33
|
+
if (raw === null)
|
|
34
|
+
return null;
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
if (!parsed || typeof parsed !== 'object')
|
|
38
|
+
return null;
|
|
39
|
+
const obj = parsed;
|
|
40
|
+
if (typeof obj.last_success_iso !== 'string')
|
|
41
|
+
return null;
|
|
42
|
+
if (obj.latest !== null && typeof obj.latest !== 'string')
|
|
43
|
+
return null;
|
|
44
|
+
return { last_success_iso: obj.last_success_iso, latest: obj.latest ?? null };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Write the cache under `~/.harness/` (creating the dir). No-op when home is
|
|
52
|
+
* unresolved — best-effort, the check still works off live lookups.
|
|
53
|
+
*/
|
|
54
|
+
export function writeCache(fs, env, cache) {
|
|
55
|
+
const dir = harnessDir(env);
|
|
56
|
+
if (!dir)
|
|
57
|
+
return;
|
|
58
|
+
fs.mkdirp(dir);
|
|
59
|
+
fs.writeText(posixJoin(dir, CACHE_FILE), `${JSON.stringify(cache, null, 2)}\n`);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/services/update/cache.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD;;;;;;;;GAQG;AAEH,MAAM,UAAU,GAAG,mBAAmB,CAAC;AASvC,4EAA4E;AAC5E,SAAS,UAAU,CAAC,GAAY;IAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACxB,OAAO,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACpD,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,SAAS,CAAC,GAAY;IACpC,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,EAAU,EAAE,GAAY;IAChD,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACvD,MAAM,GAAG,GAAG,MAAiC,CAAC;QAC9C,IAAI,OAAO,GAAG,CAAC,gBAAgB,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC1D,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACvE,OAAO,EAAE,gBAAgB,EAAE,GAAG,CAAC,gBAAgB,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;IAChF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,EAAU,EAAE,GAAY,EAAE,KAAuB;IAC1E,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACf,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAClF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update-domain constants. Plain strings, no I/O — safe to import from services,
|
|
3
|
+
* acts, and the composition root alike (the Node adapters receive these by
|
|
4
|
+
* injection, never importing the service layer themselves).
|
|
5
|
+
*/
|
|
6
|
+
/** The registry package the harness CLI is published as (plan 018). */
|
|
7
|
+
export declare const PACKAGE_NAME = "@ai-substrate/engineering-harness";
|
|
8
|
+
/** The exact command the update banner / next_action tells users to run. */
|
|
9
|
+
export declare const UPDATE_COMMAND = "harness update";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update-domain constants. Plain strings, no I/O — safe to import from services,
|
|
3
|
+
* acts, and the composition root alike (the Node adapters receive these by
|
|
4
|
+
* injection, never importing the service layer themselves).
|
|
5
|
+
*/
|
|
6
|
+
/** The registry package the harness CLI is published as (plan 018). */
|
|
7
|
+
export const PACKAGE_NAME = '@ai-substrate/engineering-harness';
|
|
8
|
+
/** The exact command the update banner / next_action tells users to run. */
|
|
9
|
+
export const UPDATE_COMMAND = 'harness update';
|
|
10
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/services/update/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,uEAAuE;AACvE,MAAM,CAAC,MAAM,YAAY,GAAG,mCAAmC,CAAC;AAEhE,4EAA4E;AAC5E,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ExecResult } from '../../adapters/exec/exec-port.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pure helpers for the global install path (`harness update` / `self-install`).
|
|
4
|
+
* Argv construction + failure classification live here (I/O-free, unit-testable);
|
|
5
|
+
* the act owns the announce-then-run + envelope rendering, shelling only through
|
|
6
|
+
* the injected `ExecPort` (the install itself is a `npm i -g` pass-through, P8).
|
|
7
|
+
*/
|
|
8
|
+
/** npm argv to install the harness globally at `spec` (a dist-tag like `latest`, or a concrete version). */
|
|
9
|
+
export declare function npmInstallArgv(spec: string): string[];
|
|
10
|
+
/** Render an npm argv as the exact command string (for the announce line + next_action). */
|
|
11
|
+
export declare function formatNpmCommand(argv: string[]): string;
|
|
12
|
+
/** Normalise a pin like `v0.3.0` → `0.3.0` (npm specs carry no leading `v`). */
|
|
13
|
+
export declare function normalizePin(version: string): string;
|
|
14
|
+
export interface InstallFailure {
|
|
15
|
+
code: string;
|
|
16
|
+
message: string;
|
|
17
|
+
next_action: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Classify a failed `npm i -g` result into an actionable error (AC10). Order is
|
|
21
|
+
* deliberate: npm-absent → auth → (pinned) version-not-found → permission →
|
|
22
|
+
* generic. `command` is echoed into the next_action so the user can re-run.
|
|
23
|
+
*/
|
|
24
|
+
export declare function classifyInstallFailure(result: ExecResult, command: string, opts?: {
|
|
25
|
+
pinned?: string;
|
|
26
|
+
}): InstallFailure;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ErrorCodes } from '../../output/error-codes.js';
|
|
2
|
+
import { PACKAGE_NAME } from './constants.js';
|
|
3
|
+
/**
|
|
4
|
+
* Pure helpers for the global install path (`harness update` / `self-install`).
|
|
5
|
+
* Argv construction + failure classification live here (I/O-free, unit-testable);
|
|
6
|
+
* the act owns the announce-then-run + envelope rendering, shelling only through
|
|
7
|
+
* the injected `ExecPort` (the install itself is a `npm i -g` pass-through, P8).
|
|
8
|
+
*/
|
|
9
|
+
/** npm argv to install the harness globally at `spec` (a dist-tag like `latest`, or a concrete version). */
|
|
10
|
+
export function npmInstallArgv(spec) {
|
|
11
|
+
return ['i', '-g', `${PACKAGE_NAME}@${spec}`];
|
|
12
|
+
}
|
|
13
|
+
/** Render an npm argv as the exact command string (for the announce line + next_action). */
|
|
14
|
+
export function formatNpmCommand(argv) {
|
|
15
|
+
return `npm ${argv.join(' ')}`;
|
|
16
|
+
}
|
|
17
|
+
/** Normalise a pin like `v0.3.0` → `0.3.0` (npm specs carry no leading `v`). */
|
|
18
|
+
export function normalizePin(version) {
|
|
19
|
+
const v = version.trim();
|
|
20
|
+
return v.startsWith('v') || v.startsWith('V') ? v.slice(1) : v;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Classify a failed `npm i -g` result into an actionable error (AC10). Order is
|
|
24
|
+
* deliberate: npm-absent → auth → (pinned) version-not-found → permission →
|
|
25
|
+
* generic. `command` is echoed into the next_action so the user can re-run.
|
|
26
|
+
*/
|
|
27
|
+
export function classifyInstallFailure(result, command, opts) {
|
|
28
|
+
const text = `${result.stderr}\n${result.stdout}`.toLowerCase();
|
|
29
|
+
if (result.code === 127 || /command not found|not recognized|spawn npm/.test(text)) {
|
|
30
|
+
return {
|
|
31
|
+
code: ErrorCodes.UPDATE_NPM_MISSING,
|
|
32
|
+
message: 'npm was not found on PATH.',
|
|
33
|
+
next_action: `Install Node.js + npm (https://nodejs.org), then re-run: ${command}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// A PINNED version that 404s genuinely doesn't exist — but ONLY when the npm
|
|
37
|
+
// text is VERSION-specific. A generic 404/"not found" on a pinned install is a
|
|
38
|
+
// registry / not-yet-published issue (not "that version is absent"), so it
|
|
39
|
+
// falls through to the registry branch below (companion F006).
|
|
40
|
+
if (opts?.pinned && /no matching version|no such version|notarget/.test(text)) {
|
|
41
|
+
return {
|
|
42
|
+
code: ErrorCodes.UPDATE_VERSION_NOT_FOUND,
|
|
43
|
+
message: `version ${opts.pinned} was not found in the registry.`,
|
|
44
|
+
next_action: 'Pick a published version (`harness update --check` shows the latest), then re-run with `--pin <version>`.',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Registry trouble. For a PUBLIC npm package install needs no auth, so a 401/403
|
|
48
|
+
// almost always means npm is pointed at the wrong registry or carries a stale
|
|
49
|
+
// login; a NON-pinned (or generic) 404 means the package isn't published yet or
|
|
50
|
+
// the registry is unreachable. Neither is a token problem (FX001 — public npm).
|
|
51
|
+
{
|
|
52
|
+
const looksAuth = /\b(e?401|e?403)\b|unauthorized|forbidden|authentication|auth.*requir|need.*auth/.test(text);
|
|
53
|
+
const looks404 = /e?404|not found|no matching version|notarget|no such version/.test(text);
|
|
54
|
+
if (looksAuth || looks404) {
|
|
55
|
+
return {
|
|
56
|
+
code: ErrorCodes.UPDATE_AUTH_FAILED,
|
|
57
|
+
message: looksAuth
|
|
58
|
+
? `the npm registry rejected the install — ${PACKAGE_NAME} is public and needs no auth, so npm is likely pointed at the wrong registry or has a stale login.`
|
|
59
|
+
: `${PACKAGE_NAME} could not be resolved — it may not be published yet, or the npm registry is unreachable.`,
|
|
60
|
+
next_action: `Confirm npm uses the public registry (\`npm config get registry\` → https://registry.npmjs.org) ` +
|
|
61
|
+
`and that ${PACKAGE_NAME} is published on npmjs.com, then re-run: ${command}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (/eacces|eperm|permission denied|access is denied/.test(text)) {
|
|
66
|
+
return {
|
|
67
|
+
code: ErrorCodes.UPDATE_PERMISSION_DENIED,
|
|
68
|
+
message: 'the global npm install was denied (permissions).',
|
|
69
|
+
next_action: `Use a Node version manager (nvm/Volta) or a prefix-writable/elevated npm, then re-run: ${command}`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
code: ErrorCodes.UPDATE_FAILED,
|
|
74
|
+
message: `update failed (exit ${result.code}).`,
|
|
75
|
+
next_action: `Inspect the npm output above, then re-run: ${command}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/services/update/install.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;;;;GAKG;AAEH,4GAA4G;AAC5G,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,YAAY,IAAI,IAAI,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,gBAAgB,CAAC,IAAc;IAC7C,OAAO,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AACjC,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IACzB,OAAO,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AAQD;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAkB,EAClB,OAAe,EACf,IAA0B;IAE1B,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC;IAEhE,IAAI,MAAM,CAAC,IAAI,KAAK,GAAG,IAAI,4CAA4C,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnF,OAAO;YACL,IAAI,EAAE,UAAU,CAAC,kBAAkB;YACnC,OAAO,EAAE,4BAA4B;YACrC,WAAW,EAAE,4DAA4D,OAAO,EAAE;SACnF,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,+EAA+E;IAC/E,2EAA2E;IAC3E,+DAA+D;IAC/D,IAAI,IAAI,EAAE,MAAM,IAAI,8CAA8C,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9E,OAAO;YACL,IAAI,EAAE,UAAU,CAAC,wBAAwB;YACzC,OAAO,EAAE,WAAW,IAAI,CAAC,MAAM,iCAAiC;YAChE,WAAW,EACT,2GAA2G;SAC9G,CAAC;IACJ,CAAC;IAED,iFAAiF;IACjF,8EAA8E;IAC9E,gFAAgF;IAChF,gFAAgF;IAChF,CAAC;QACC,MAAM,SAAS,GACb,iFAAiF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/F,MAAM,QAAQ,GAAG,8DAA8D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3F,IAAI,SAAS,IAAI,QAAQ,EAAE,CAAC;YAC1B,OAAO;gBACL,IAAI,EAAE,UAAU,CAAC,kBAAkB;gBACnC,OAAO,EAAE,SAAS;oBAChB,CAAC,CAAC,2CAA2C,YAAY,oGAAoG;oBAC7J,CAAC,CAAC,GAAG,YAAY,2FAA2F;gBAC9G,WAAW,EACT,kGAAkG;oBAClG,YAAY,YAAY,4CAA4C,OAAO,EAAE;aAChF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,iDAAiD,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACjE,OAAO;YACL,IAAI,EAAE,UAAU,CAAC,wBAAwB;YACzC,OAAO,EAAE,kDAAkD;YAC3D,WAAW,EAAE,0FAA0F,OAAO,EAAE;SACjH,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,UAAU,CAAC,aAAa;QAC9B,OAAO,EAAE,uBAAuB,MAAM,CAAC,IAAI,IAAI;QAC/C,WAAW,EAAE,8CAA8C,OAAO,EAAE;KACrE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, pure SemVer "is-newer" comparison for the update check.
|
|
3
|
+
*
|
|
4
|
+
* Intentionally tiny — no dependency, no validation surface beyond what the
|
|
5
|
+
* update decision needs: is `latest` strictly newer than `installed`? It is
|
|
6
|
+
* pre-release aware (so a `-canary.N` build sorts below its release, per the
|
|
7
|
+
* SemVer spec) and **safe by default**: if either side is malformed it returns
|
|
8
|
+
* false — an unparseable version never produces a phantom "update available".
|
|
9
|
+
*/
|
|
10
|
+
/** True iff `v` is a valid, concrete SemVer version (used to reject dist-tag pins — companion F005). */
|
|
11
|
+
export declare function isValidVersion(v: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* True iff `latest` is strictly newer than `installed`. Malformed input on
|
|
14
|
+
* either side ⇒ false (no update). Tolerates a leading `v`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isNewer(latest: string, installed: string): boolean;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, pure SemVer "is-newer" comparison for the update check.
|
|
3
|
+
*
|
|
4
|
+
* Intentionally tiny — no dependency, no validation surface beyond what the
|
|
5
|
+
* update decision needs: is `latest` strictly newer than `installed`? It is
|
|
6
|
+
* pre-release aware (so a `-canary.N` build sorts below its release, per the
|
|
7
|
+
* SemVer spec) and **safe by default**: if either side is malformed it returns
|
|
8
|
+
* false — an unparseable version never produces a phantom "update available".
|
|
9
|
+
*/
|
|
10
|
+
const NUMERIC = /^\d+$/;
|
|
11
|
+
/** A numeric identifier with no leading zero (SemVer §9 — `0` ok, `01` not). */
|
|
12
|
+
const NUMERIC_NOLEAD = /^(0|[1-9]\d*)$/;
|
|
13
|
+
/** A pre-release identifier's legal alphabet (SemVer §9). */
|
|
14
|
+
const PRERELEASE_ID = /^[0-9A-Za-z-]+$/;
|
|
15
|
+
/**
|
|
16
|
+
* Parse `vX.Y.Z[-pre][+build]` → Parsed, or null if it isn't a clean triple.
|
|
17
|
+
* Strict per SemVer §9 (companion F002): core/numeric-prerelease identifiers
|
|
18
|
+
* reject leading zeroes; pre-release identifiers reject illegal characters — so
|
|
19
|
+
* malformed input can never slip through and compare as "newer".
|
|
20
|
+
*/
|
|
21
|
+
function parse(raw) {
|
|
22
|
+
if (typeof raw !== 'string')
|
|
23
|
+
return null;
|
|
24
|
+
let v = raw.trim();
|
|
25
|
+
if (v === '')
|
|
26
|
+
return null;
|
|
27
|
+
if (v[0] === 'v' || v[0] === 'V')
|
|
28
|
+
v = v.slice(1);
|
|
29
|
+
// Build metadata (+…) is ignored for precedence (SemVer §10).
|
|
30
|
+
const plus = v.indexOf('+');
|
|
31
|
+
if (plus !== -1)
|
|
32
|
+
v = v.slice(0, plus);
|
|
33
|
+
const dash = v.indexOf('-');
|
|
34
|
+
const core = dash === -1 ? v : v.slice(0, dash);
|
|
35
|
+
const pre = dash === -1 ? '' : v.slice(dash + 1);
|
|
36
|
+
const parts = core.split('.');
|
|
37
|
+
if (parts.length !== 3)
|
|
38
|
+
return null;
|
|
39
|
+
if (!parts.every((p) => NUMERIC_NOLEAD.test(p)))
|
|
40
|
+
return null; // reject leading zeroes / non-numeric
|
|
41
|
+
const prerelease = pre === '' ? [] : pre.split('.');
|
|
42
|
+
for (const id of prerelease) {
|
|
43
|
+
if (!PRERELEASE_ID.test(id))
|
|
44
|
+
return null; // empty or illegal character
|
|
45
|
+
if (NUMERIC.test(id) && !NUMERIC_NOLEAD.test(id))
|
|
46
|
+
return null; // numeric with a leading zero
|
|
47
|
+
}
|
|
48
|
+
return { major: Number(parts[0]), minor: Number(parts[1]), patch: Number(parts[2]), prerelease };
|
|
49
|
+
}
|
|
50
|
+
/** True iff `v` is a valid, concrete SemVer version (used to reject dist-tag pins — companion F005). */
|
|
51
|
+
export function isValidVersion(v) {
|
|
52
|
+
return parse(v) !== null;
|
|
53
|
+
}
|
|
54
|
+
function compareCore(a, b) {
|
|
55
|
+
if (a.major !== b.major)
|
|
56
|
+
return a.major < b.major ? -1 : 1;
|
|
57
|
+
if (a.minor !== b.minor)
|
|
58
|
+
return a.minor < b.minor ? -1 : 1;
|
|
59
|
+
if (a.patch !== b.patch)
|
|
60
|
+
return a.patch < b.patch ? -1 : 1;
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
/** SemVer §11.4 pre-release precedence. A release (no pre-release) outranks any. */
|
|
64
|
+
function comparePrerelease(a, b) {
|
|
65
|
+
if (a.length === 0 && b.length === 0)
|
|
66
|
+
return 0;
|
|
67
|
+
if (a.length === 0)
|
|
68
|
+
return 1; // a is a release, b is a pre-release ⇒ a newer
|
|
69
|
+
if (b.length === 0)
|
|
70
|
+
return -1;
|
|
71
|
+
const len = Math.min(a.length, b.length);
|
|
72
|
+
for (let i = 0; i < len; i++) {
|
|
73
|
+
const ai = a[i];
|
|
74
|
+
const bi = b[i];
|
|
75
|
+
const an = NUMERIC.test(ai);
|
|
76
|
+
const bn = NUMERIC.test(bi);
|
|
77
|
+
if (an && bn) {
|
|
78
|
+
const x = Number(ai);
|
|
79
|
+
const y = Number(bi);
|
|
80
|
+
if (x !== y)
|
|
81
|
+
return x < y ? -1 : 1;
|
|
82
|
+
}
|
|
83
|
+
else if (an !== bn) {
|
|
84
|
+
return an ? -1 : 1; // numeric identifiers rank lower than alphanumeric
|
|
85
|
+
}
|
|
86
|
+
else if (ai !== bi) {
|
|
87
|
+
return ai < bi ? -1 : 1; // lexical ASCII order
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (a.length !== b.length)
|
|
91
|
+
return a.length < b.length ? -1 : 1; // more fields win
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* True iff `latest` is strictly newer than `installed`. Malformed input on
|
|
96
|
+
* either side ⇒ false (no update). Tolerates a leading `v`.
|
|
97
|
+
*/
|
|
98
|
+
export function isNewer(latest, installed) {
|
|
99
|
+
const l = parse(latest);
|
|
100
|
+
const i = parse(installed);
|
|
101
|
+
if (!l || !i)
|
|
102
|
+
return false;
|
|
103
|
+
const core = compareCore(l, i);
|
|
104
|
+
if (core !== 0)
|
|
105
|
+
return core > 0;
|
|
106
|
+
return comparePrerelease(l.prerelease, i.prerelease) > 0;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=semver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semver.js","sourceRoot":"","sources":["../../../src/services/update/semver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,MAAM,OAAO,GAAG,OAAO,CAAC;AACxB,gFAAgF;AAChF,MAAM,cAAc,GAAG,gBAAgB,CAAC;AACxC,6DAA6D;AAC7D,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAExC;;;;;GAKG;AACH,SAAS,KAAK,CAAC,GAAW;IACxB,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,IAAI,CAAC,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEjD,8DAA8D;IAC9D,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAEtC,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAChD,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IAEjD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,sCAAsC;IAEpG,MAAM,UAAU,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpD,KAAK,MAAM,EAAE,IAAI,UAAU,EAAE,CAAC;QAC5B,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,6BAA6B;QACvE,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,8BAA8B;IAC/F,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC;AACnG,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;AAC3B,CAAC;AAED,SAAS,WAAW,CAAC,CAAS,EAAE,CAAS;IACvC,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,CAAC;AACX,CAAC;AAED,oFAAoF;AACpF,SAAS,iBAAiB,CAAC,CAAW,EAAE,CAAW;IACjD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAC/C,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC,+CAA+C;IAC7E,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC;IAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAChB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAChB,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5B,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;YACrB,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;YACrB,IAAI,CAAC,KAAK,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,CAAC;aAAM,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACrB,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,mDAAmD;QACzE,CAAC;aAAM,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACrB,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;QACjD,CAAC;IACH,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB;IAClF,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,MAAc,EAAE,SAAiB;IACvD,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IACxB,MAAM,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IAC3B,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3B,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/B,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,GAAG,CAAC,CAAC;IAChC,OAAO,iBAAiB,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Clock } from '../../adapters/clock/clock-port.js';
|
|
2
|
+
import type { EnvPort } from '../../adapters/env/env-port.js';
|
|
3
|
+
import type { FsPort } from '../../adapters/fs/fs-port.js';
|
|
4
|
+
import type { VersionLookupPort } from '../../adapters/version-lookup/version-lookup-port.js';
|
|
5
|
+
import type { UpdateAvailable } from '../../output/envelope.js';
|
|
6
|
+
/** The throttle window: at most one registry lookup per 24h. */
|
|
7
|
+
export declare const CHECK_WINDOW_MS: number;
|
|
8
|
+
/**
|
|
9
|
+
* Build the `update_available` field iff `latest` is strictly newer than
|
|
10
|
+
* `installed`. The command is pinned to "harness update" (AC7).
|
|
11
|
+
*/
|
|
12
|
+
export declare function toUpdateAvailable(installed: string, latest: string | null): UpdateAvailable | null;
|
|
13
|
+
/**
|
|
14
|
+
* SYNC banner source for the hot path (T007): read the cache and, if its
|
|
15
|
+
* last-known latest is newer than the installed version, return the notice.
|
|
16
|
+
* No network, a single cache read — safe to call on every command exit.
|
|
17
|
+
*/
|
|
18
|
+
export declare function bannerFromCache(fs: FsPort, env: EnvPort, installed: string): UpdateAvailable | null;
|
|
19
|
+
/** Whether the cache is stale enough to warrant a fresh lookup. */
|
|
20
|
+
export declare function isDue(now: string, lastSuccessIso: string, windowMs?: number): boolean;
|
|
21
|
+
export interface CheckResult {
|
|
22
|
+
installed: string;
|
|
23
|
+
/** Latest known — a fresh lookup, or last-known cache on skip/failure. */
|
|
24
|
+
latest: string | null;
|
|
25
|
+
update_available: UpdateAvailable | null;
|
|
26
|
+
/** True if a registry lookup actually ran this call. */
|
|
27
|
+
checked: boolean;
|
|
28
|
+
/** True if a lookup ran AND succeeded (cache advanced). */
|
|
29
|
+
refreshed: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface CheckDeps {
|
|
32
|
+
fs: FsPort;
|
|
33
|
+
env: EnvPort;
|
|
34
|
+
clock: Clock;
|
|
35
|
+
lookup: VersionLookupPort;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Throttled update check. Reads the cache; if `force` or the 24h window has
|
|
39
|
+
* elapsed, does exactly ONE registry lookup. A successful lookup (non-null)
|
|
40
|
+
* advances the cache (ts + latest); a failed/empty lookup (null or throw) keeps
|
|
41
|
+
* the cache untouched so a previously-known update survives (AC9). When not due,
|
|
42
|
+
* reports off the last-known cache with no network call (AC5).
|
|
43
|
+
*/
|
|
44
|
+
export declare function runCheck(deps: CheckDeps, installed: string, opts?: {
|
|
45
|
+
force?: boolean;
|
|
46
|
+
}): Promise<CheckResult>;
|