@skein-code/cli 0.2.2 → 0.2.3
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/dist/cli.js +196 -18
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/cli.tsx
|
|
4
4
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
5
5
|
import { stdin as input, stdout as output } from "node:process";
|
|
6
|
-
import { writeFile as
|
|
6
|
+
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
7
7
|
import { basename as basename12, resolve as resolve22 } from "node:path";
|
|
8
8
|
import { Command, Option } from "commander";
|
|
9
9
|
import chalk3 from "chalk";
|
|
@@ -220,7 +220,7 @@ function isSqliteBusy(error) {
|
|
|
220
220
|
// package.json
|
|
221
221
|
var package_default = {
|
|
222
222
|
name: "@skein-code/cli",
|
|
223
|
-
version: "0.2.
|
|
223
|
+
version: "0.2.3",
|
|
224
224
|
description: "A context-first, model-agnostic coding agent with an auditable terminal workspace.",
|
|
225
225
|
type: "module",
|
|
226
226
|
license: "MIT",
|
|
@@ -11135,6 +11135,9 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
|
|
|
11135
11135
|
if (item.kind === "banner") {
|
|
11136
11136
|
return /* @__PURE__ */ jsx(Banner, { model: item.model, engine: item.engine, workspace: item.workspace, version: item.version, width, glyphs }, item.id);
|
|
11137
11137
|
}
|
|
11138
|
+
if (item.kind === "update") {
|
|
11139
|
+
return /* @__PURE__ */ jsx(UpdateNotice, { current: item.current, latest: item.latest, command: item.command, width, glyphs }, item.id);
|
|
11140
|
+
}
|
|
11138
11141
|
const color = item.tone === "error" ? theme.error : item.tone === "success" ? theme.success : theme.muted;
|
|
11139
11142
|
const noticeGlyph = item.tone === "error" ? glyphs.error : item.tone === "success" ? glyphs.success : glyphs.info;
|
|
11140
11143
|
return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color, wrap: "wrap", children: `${noticeGlyph} ${sanitizeTerminalText(item.text)}` }) }, item.id);
|
|
@@ -11708,6 +11711,20 @@ function Banner({ model, engine, workspace, version, width, glyphs }) {
|
|
|
11708
11711
|
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncateDisplay(`type a request ${glyphs.separator} /help for commands ${glyphs.separator} @ to attach files`, innerWidth) })
|
|
11709
11712
|
] });
|
|
11710
11713
|
}
|
|
11714
|
+
function UpdateNotice({ current, latest, command: command2, width, glyphs }) {
|
|
11715
|
+
const theme = useTheme();
|
|
11716
|
+
const parts = [
|
|
11717
|
+
{ text: glyphs.up, color: theme.accent, bold: true },
|
|
11718
|
+
{ text: " a new version is available ", color: theme.text, bold: false },
|
|
11719
|
+
{ text: `v${current}`, color: theme.dim, bold: false },
|
|
11720
|
+
{ text: ` ${glyphs.arrow} `, color: theme.muted, bold: false },
|
|
11721
|
+
{ text: `v${latest}`, color: theme.success, bold: true },
|
|
11722
|
+
{ text: ` ${command2}`, color: theme.dim, bold: false }
|
|
11723
|
+
];
|
|
11724
|
+
const rendered = truncateDisplay(parts.map((part) => part.text).join(""), safeWidth(width));
|
|
11725
|
+
const truncated = rendered.length < parts.map((part) => part.text).join("").length;
|
|
11726
|
+
return /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: truncated ? /* @__PURE__ */ jsx(Text, { color: theme.muted, children: rendered }) : parts.map((part, index) => /* @__PURE__ */ jsx(Text, { color: part.color, bold: part.bold, children: part.text }, index)) });
|
|
11727
|
+
}
|
|
11711
11728
|
function RichText({ value, glyphs }) {
|
|
11712
11729
|
const theme = useTheme();
|
|
11713
11730
|
let inCode = false;
|
|
@@ -11800,6 +11817,141 @@ function safeWidth(width) {
|
|
|
11800
11817
|
return Math.max(1, Math.floor(Number.isFinite(width) ? width : 80));
|
|
11801
11818
|
}
|
|
11802
11819
|
|
|
11820
|
+
// src/utils/update-check.ts
|
|
11821
|
+
import { mkdir as mkdir9, readFile as readFile15, writeFile as writeFile2 } from "node:fs/promises";
|
|
11822
|
+
import { join as join16 } from "node:path";
|
|
11823
|
+
var PACKAGE_NAME = "@skein-code/cli";
|
|
11824
|
+
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
11825
|
+
var CACHE_FILE = "update-check.json";
|
|
11826
|
+
var CHECK_INTERVAL_MS = 1e3 * 60 * 60 * 24;
|
|
11827
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
11828
|
+
var MAX_BODY_BYTES = 1e6;
|
|
11829
|
+
function truthyEnv(value) {
|
|
11830
|
+
return value !== void 0 && value !== "" && value !== "false" && value !== "0";
|
|
11831
|
+
}
|
|
11832
|
+
function isCi(env) {
|
|
11833
|
+
return truthyEnv(env.CI) || truthyEnv(env.CONTINUOUS_INTEGRATION) || truthyEnv(env.GITHUB_ACTIONS) || truthyEnv(env.GITLAB_CI) || truthyEnv(env.BUILD_NUMBER);
|
|
11834
|
+
}
|
|
11835
|
+
function isUpdateCheckDisabled(env = process.env) {
|
|
11836
|
+
if (truthyEnv(env.SKEIN_NO_UPDATE_CHECK) || truthyEnv(env.MOSAIC_NO_UPDATE_CHECK) || truthyEnv(env.NO_UPDATE_NOTIFIER)) {
|
|
11837
|
+
return true;
|
|
11838
|
+
}
|
|
11839
|
+
if (env.NODE_ENV === "test") return true;
|
|
11840
|
+
return isCi(env);
|
|
11841
|
+
}
|
|
11842
|
+
function parseVersion(value) {
|
|
11843
|
+
const cleaned = value.trim().replace(/^v/iu, "");
|
|
11844
|
+
const buildAt = cleaned.indexOf("+");
|
|
11845
|
+
const noBuild = buildAt === -1 ? cleaned : cleaned.slice(0, buildAt);
|
|
11846
|
+
const dashAt = noBuild.indexOf("-");
|
|
11847
|
+
const mainStr = dashAt === -1 ? noBuild : noBuild.slice(0, dashAt);
|
|
11848
|
+
const preStr = dashAt === -1 ? "" : noBuild.slice(dashAt + 1);
|
|
11849
|
+
const parts = mainStr.split(".");
|
|
11850
|
+
if (parts.length !== 3) return null;
|
|
11851
|
+
const [major, minor, patch] = parts.map((part) => Number(part));
|
|
11852
|
+
if ([major, minor, patch].some((n) => n === void 0 || !Number.isInteger(n) || n < 0)) return null;
|
|
11853
|
+
return { main: [major, minor, patch], pre: preStr ? preStr.split(".") : [] };
|
|
11854
|
+
}
|
|
11855
|
+
function compareVersions(a, b) {
|
|
11856
|
+
const pa = parseVersion(a);
|
|
11857
|
+
const pb = parseVersion(b);
|
|
11858
|
+
if (!pa || !pb) return a === b ? 0 : a < b ? -1 : 1;
|
|
11859
|
+
for (let i = 0; i < 3; i++) {
|
|
11860
|
+
const x = pa.main[i];
|
|
11861
|
+
const y = pb.main[i];
|
|
11862
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
11863
|
+
}
|
|
11864
|
+
if (pa.pre.length === 0 && pb.pre.length === 0) return 0;
|
|
11865
|
+
if (pa.pre.length === 0) return 1;
|
|
11866
|
+
if (pb.pre.length === 0) return -1;
|
|
11867
|
+
const len = Math.max(pa.pre.length, pb.pre.length);
|
|
11868
|
+
for (let i = 0; i < len; i++) {
|
|
11869
|
+
const x = pa.pre[i];
|
|
11870
|
+
const y = pb.pre[i];
|
|
11871
|
+
if (x === void 0) return -1;
|
|
11872
|
+
if (y === void 0) return 1;
|
|
11873
|
+
const xNum = /^\d+$/u.test(x);
|
|
11874
|
+
const yNum = /^\d+$/u.test(y);
|
|
11875
|
+
if (xNum && yNum) {
|
|
11876
|
+
const nx = Number(x);
|
|
11877
|
+
const ny = Number(y);
|
|
11878
|
+
if (nx !== ny) return nx < ny ? -1 : 1;
|
|
11879
|
+
} else if (xNum !== yNum) {
|
|
11880
|
+
return xNum ? -1 : 1;
|
|
11881
|
+
} else if (x !== y) {
|
|
11882
|
+
return x < y ? -1 : 1;
|
|
11883
|
+
}
|
|
11884
|
+
}
|
|
11885
|
+
return 0;
|
|
11886
|
+
}
|
|
11887
|
+
function updateCachePath(env = process.env) {
|
|
11888
|
+
return join16(resolveHomeNamespace(env), CACHE_FILE);
|
|
11889
|
+
}
|
|
11890
|
+
function upgradeCommand() {
|
|
11891
|
+
return `npm i -g ${PACKAGE_NAME}`;
|
|
11892
|
+
}
|
|
11893
|
+
function updateNoticeText(notice) {
|
|
11894
|
+
return `Update available ${notice.current} \u2192 ${notice.latest} \xB7 run ${notice.command}`;
|
|
11895
|
+
}
|
|
11896
|
+
function noticeIfNewer(latest, current) {
|
|
11897
|
+
if (latest && compareVersions(latest, current) > 0) {
|
|
11898
|
+
return { current, latest, command: upgradeCommand() };
|
|
11899
|
+
}
|
|
11900
|
+
return void 0;
|
|
11901
|
+
}
|
|
11902
|
+
async function readUpdateCache(env = process.env) {
|
|
11903
|
+
try {
|
|
11904
|
+
const raw = await readFile15(updateCachePath(env), "utf8");
|
|
11905
|
+
const parsed = JSON.parse(raw);
|
|
11906
|
+
if (typeof parsed?.checkedAt === "number" && (typeof parsed.latest === "string" || parsed.latest === null)) {
|
|
11907
|
+
return { checkedAt: parsed.checkedAt, latest: parsed.latest };
|
|
11908
|
+
}
|
|
11909
|
+
} catch {
|
|
11910
|
+
}
|
|
11911
|
+
return null;
|
|
11912
|
+
}
|
|
11913
|
+
async function writeUpdateCache(cache, env) {
|
|
11914
|
+
try {
|
|
11915
|
+
const dir = resolveHomeNamespace(env);
|
|
11916
|
+
await mkdir9(dir, { recursive: true, mode: 448 });
|
|
11917
|
+
await writeFile2(updateCachePath(env), JSON.stringify(cache), "utf8");
|
|
11918
|
+
} catch {
|
|
11919
|
+
}
|
|
11920
|
+
}
|
|
11921
|
+
async function fetchLatestVersion(fetchImpl, url = REGISTRY_URL) {
|
|
11922
|
+
try {
|
|
11923
|
+
const response = await fetchImpl(url, {
|
|
11924
|
+
headers: { accept: "application/json" },
|
|
11925
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
11926
|
+
});
|
|
11927
|
+
if (!response.ok) return null;
|
|
11928
|
+
const body = await response.text();
|
|
11929
|
+
if (body.length > MAX_BODY_BYTES) return null;
|
|
11930
|
+
const parsed = JSON.parse(body);
|
|
11931
|
+
return typeof parsed.version === "string" ? parsed.version : null;
|
|
11932
|
+
} catch {
|
|
11933
|
+
return null;
|
|
11934
|
+
}
|
|
11935
|
+
}
|
|
11936
|
+
async function resolveCachedUpdateNotice(currentVersion, env = process.env) {
|
|
11937
|
+
if (isUpdateCheckDisabled(env)) return void 0;
|
|
11938
|
+
const cache = await readUpdateCache(env);
|
|
11939
|
+
return noticeIfNewer(cache?.latest ?? null, currentVersion);
|
|
11940
|
+
}
|
|
11941
|
+
async function refreshUpdateCache(currentVersion, options = {}) {
|
|
11942
|
+
const env = options.env ?? process.env;
|
|
11943
|
+
if (isUpdateCheckDisabled(env)) return void 0;
|
|
11944
|
+
const cache = await readUpdateCache(env);
|
|
11945
|
+
const now = Date.now();
|
|
11946
|
+
if (!options.force && cache && now - cache.checkedAt < CHECK_INTERVAL_MS) {
|
|
11947
|
+
return noticeIfNewer(cache.latest, currentVersion);
|
|
11948
|
+
}
|
|
11949
|
+
const latest = await fetchLatestVersion(options.fetchImpl ?? fetch);
|
|
11950
|
+
const effectiveLatest = latest ?? cache?.latest ?? null;
|
|
11951
|
+
await writeUpdateCache({ checkedAt: now, latest: effectiveLatest }, env);
|
|
11952
|
+
return noticeIfNewer(effectiveLatest, currentVersion);
|
|
11953
|
+
}
|
|
11954
|
+
|
|
11803
11955
|
// src/ui/composer.tsx
|
|
11804
11956
|
import { useEffect, useRef, useState } from "react";
|
|
11805
11957
|
import { Text as Text2, useInput, usePaste } from "ink";
|
|
@@ -12722,6 +12874,29 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
12722
12874
|
useEffect2(() => {
|
|
12723
12875
|
setSuggestionIndex(0);
|
|
12724
12876
|
}, [input2]);
|
|
12877
|
+
useEffect2(() => {
|
|
12878
|
+
let cancelled = false;
|
|
12879
|
+
const showNotice = (notice) => {
|
|
12880
|
+
if (cancelled || !notice) return;
|
|
12881
|
+
setTimeline((items) => {
|
|
12882
|
+
const bannerIndex = items.findIndex((item) => item.kind === "banner");
|
|
12883
|
+
if (bannerIndex === -1) return items;
|
|
12884
|
+
const existing = items.find((item) => item.kind === "update");
|
|
12885
|
+
if (existing) {
|
|
12886
|
+
if (existing.kind === "update" && existing.latest === notice.latest) return items;
|
|
12887
|
+
return items.map((item) => item === existing ? { id: item.id, kind: "update", current: notice.current, latest: notice.latest, command: notice.command } : item);
|
|
12888
|
+
}
|
|
12889
|
+
const next = items.slice();
|
|
12890
|
+
next.splice(bannerIndex + 1, 0, { id: nextId(), kind: "update", current: notice.current, latest: notice.latest, command: notice.command });
|
|
12891
|
+
return next;
|
|
12892
|
+
});
|
|
12893
|
+
};
|
|
12894
|
+
void resolveCachedUpdateNotice(package_default.version).then(showNotice);
|
|
12895
|
+
void refreshUpdateCache(package_default.version).then(showNotice);
|
|
12896
|
+
return () => {
|
|
12897
|
+
cancelled = true;
|
|
12898
|
+
};
|
|
12899
|
+
}, []);
|
|
12725
12900
|
useEffect2(() => {
|
|
12726
12901
|
if (suggestionMode !== "mention" || !mentionToken) {
|
|
12727
12902
|
mentionRequest.current += 1;
|
|
@@ -14913,9 +15088,9 @@ function scopeKey(scope, context) {
|
|
|
14913
15088
|
}
|
|
14914
15089
|
|
|
14915
15090
|
// src/skills/catalog.ts
|
|
14916
|
-
import { lstat as lstat19, readFile as
|
|
15091
|
+
import { lstat as lstat19, readFile as readFile16, readdir as readdir7, realpath as realpath8 } from "node:fs/promises";
|
|
14917
15092
|
import { homedir as homedir4 } from "node:os";
|
|
14918
|
-
import { basename as basename11, join as
|
|
15093
|
+
import { basename as basename11, join as join17, resolve as resolve20 } from "node:path";
|
|
14919
15094
|
import { parse as parseYaml3 } from "yaml";
|
|
14920
15095
|
var SkillCatalog = class {
|
|
14921
15096
|
constructor(workspace, config) {
|
|
@@ -14935,7 +15110,7 @@ var SkillCatalog = class {
|
|
|
14935
15110
|
for (const location of locations) {
|
|
14936
15111
|
const entries = await safeDirectories(location.path);
|
|
14937
15112
|
for (const entry of entries) {
|
|
14938
|
-
const skillPath =
|
|
15113
|
+
const skillPath = join17(location.path, entry, "SKILL.md");
|
|
14939
15114
|
const metadata = await readMetadata(skillPath);
|
|
14940
15115
|
if (!metadata) continue;
|
|
14941
15116
|
const descriptor = {
|
|
@@ -14986,10 +15161,10 @@ function discoveryLocations(workspace, configured) {
|
|
|
14986
15161
|
const home = homedir4();
|
|
14987
15162
|
const workspaceRoot = resolve20(workspace);
|
|
14988
15163
|
return [
|
|
14989
|
-
{ path:
|
|
14990
|
-
{ path:
|
|
14991
|
-
{ path:
|
|
14992
|
-
{ path:
|
|
15164
|
+
{ path: join17(home, ".agents", "skills"), scope: "user", trusted: true },
|
|
15165
|
+
{ path: join17(home, ".claude", "skills"), scope: "user", trusted: true },
|
|
15166
|
+
{ path: join17(home, ".augment", "skills"), scope: "user", trusted: true },
|
|
15167
|
+
{ path: join17(resolveHomeNamespace(), "skills"), scope: "user", trusted: true },
|
|
14993
15168
|
...configured.map((path) => {
|
|
14994
15169
|
const resolved = resolve20(workspaceRoot, path);
|
|
14995
15170
|
return {
|
|
@@ -14998,10 +15173,10 @@ function discoveryLocations(workspace, configured) {
|
|
|
14998
15173
|
trusted: !isInside(workspaceRoot, resolved)
|
|
14999
15174
|
};
|
|
15000
15175
|
}),
|
|
15001
|
-
{ path:
|
|
15002
|
-
{ path:
|
|
15003
|
-
{ path:
|
|
15004
|
-
{ path:
|
|
15176
|
+
{ path: join17(workspace, ".agents", "skills"), scope: "workspace", trusted: false },
|
|
15177
|
+
{ path: join17(workspace, ".claude", "skills"), scope: "workspace", trusted: false },
|
|
15178
|
+
{ path: join17(workspace, ".augment", "skills"), scope: "workspace", trusted: false },
|
|
15179
|
+
{ path: join17(resolveProjectNamespaceSync(workspaceRoot).active, "skills"), scope: "workspace", trusted: false }
|
|
15005
15180
|
];
|
|
15006
15181
|
}
|
|
15007
15182
|
async function safeDirectories(path) {
|
|
@@ -15045,7 +15220,7 @@ async function safeRead(path, maxBytes) {
|
|
|
15045
15220
|
const resolvedParent = await realpath8(parent);
|
|
15046
15221
|
const resolvedPath = await realpath8(path);
|
|
15047
15222
|
if (!resolvedPath.startsWith(`${resolvedParent}/`)) return void 0;
|
|
15048
|
-
return await
|
|
15223
|
+
return await readFile16(path, "utf8");
|
|
15049
15224
|
} catch {
|
|
15050
15225
|
return void 0;
|
|
15051
15226
|
}
|
|
@@ -15475,10 +15650,12 @@ program.command("status").description("Show model, context, workspace, and index
|
|
|
15475
15650
|
const engine = new ContextEngine(config);
|
|
15476
15651
|
const status = await engine.status();
|
|
15477
15652
|
const namespace = resolveProjectNamespaceSync(config.workspaceRoots[0] ?? process.cwd());
|
|
15653
|
+
const update = await refreshUpdateCache(package_default.version).catch(() => void 0);
|
|
15478
15654
|
if (options.json === true) {
|
|
15479
|
-
|
|
15655
|
+
const updateJson = update ? { current: update.current, latest: update.latest, command: update.command } : { current: package_default.version, latest: null, command: upgradeCommand() };
|
|
15656
|
+
printObject({ config: configSummary(config), context: status, namespace, update: updateJson }, true);
|
|
15480
15657
|
} else {
|
|
15481
|
-
printStatusSummary(config, status, namespace);
|
|
15658
|
+
printStatusSummary(config, status, namespace, update);
|
|
15482
15659
|
}
|
|
15483
15660
|
});
|
|
15484
15661
|
program.command("doctor").description("Diagnose prerequisites and safe fallbacks").option("-w, --workspace <path>", "workspace root").option("--config <path>", "explicit config file").option("--json", "print JSON").option("--visual", "inspect terminal rendering, glyphs, and keyboard support").action(async (options) => {
|
|
@@ -15581,7 +15758,7 @@ sessionCommand.command("export <id>").description("Export a session as Markdown"
|
|
|
15581
15758
|
const store = new SessionStore(workspaceOption(options.workspace));
|
|
15582
15759
|
const session = await requireSessionSelector(store, id);
|
|
15583
15760
|
const markdown = sessionMarkdown(session);
|
|
15584
|
-
if (options.output) await
|
|
15761
|
+
if (options.output) await writeFile3(resolve22(options.output), markdown, "utf8");
|
|
15585
15762
|
else process.stdout.write(markdown);
|
|
15586
15763
|
});
|
|
15587
15764
|
var checkpointCommand = program.command("checkpoint").description("Inspect and restore pre-mutation snapshots");
|
|
@@ -16219,7 +16396,7 @@ function printObject(value, json) {
|
|
|
16219
16396
|
else process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
16220
16397
|
`);
|
|
16221
16398
|
}
|
|
16222
|
-
function printStatusSummary(config, context, namespace) {
|
|
16399
|
+
function printStatusSummary(config, context, namespace, update) {
|
|
16223
16400
|
const glyphs = cliGlyphs;
|
|
16224
16401
|
const dim = (text) => chalk3.dim(text);
|
|
16225
16402
|
const line = (level, name, detail) => {
|
|
@@ -16248,6 +16425,7 @@ function printStatusSummary(config, context, namespace) {
|
|
|
16248
16425
|
const storageDetail = namespace.activeKind === "canonical" ? `${namespaceName} (canonical)` : namespace.phase === "active" ? `${namespaceName} (legacy; new projects switch to .skein from 0.3.0)` : `${namespaceName} (legacy; run ${PRODUCT_COMMAND} migrate --yes before removal)`;
|
|
16249
16426
|
const storageReady = namespace.activeKind === "canonical" || namespace.phase === "active";
|
|
16250
16427
|
line(storageReady ? "ok" : "warn", "Storage", storageDetail);
|
|
16428
|
+
line(update ? "warn" : "ok", "Version", update ? updateNoticeText(update) : `v${package_default.version} (up to date)`);
|
|
16251
16429
|
process.stdout.write(`
|
|
16252
16430
|
${dim(`Run ${PRODUCT_COMMAND} status --json for the full machine-readable record.`)}
|
|
16253
16431
|
`);
|