@remits/remits-cli 0.1.113 → 0.1.114
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/index.js +241 -24
- package/package.json +3 -2
- package/skills/remits-cli/SKILL.md +139 -3548
- package/skills/remits-cli/references/account-targeting.md +165 -0
- package/skills/remits-cli/references/agent-sessions.md +218 -0
- package/skills/remits-cli/references/branch-variants.md +372 -0
- package/skills/remits-cli/references/cli-state.md +158 -0
- package/skills/remits-cli/references/command-reference.md +268 -0
- package/skills/remits-cli/references/component-integrity.md +175 -0
- package/skills/remits-cli/references/component-resolution.md +209 -0
- package/skills/remits-cli/references/development-loop.md +366 -0
- package/skills/remits-cli/references/investigation.md +251 -0
- package/skills/remits-cli/references/support-tickets.md +389 -0
- package/skills/remits-cli/references/tool-reference.md +962 -0
- package/skills/remits-cli/references/troubleshooting.md +135 -0
package/index.js
CHANGED
|
@@ -24,7 +24,10 @@ const SESSIONS_FILE = path.join(SESSION_DIR, 'sessions.json');
|
|
|
24
24
|
const CONFIG_FILE = path.join(SESSION_DIR, 'config.json');
|
|
25
25
|
const LISTENER_PID_FILE = path.join(SESSION_DIR, 'listener.pid');
|
|
26
26
|
const ACTIVITY_LOG_FILE = path.join(SESSION_DIR, 'activity.log');
|
|
27
|
-
const
|
|
27
|
+
const SKILL_SOURCE_DIR = path.join(__dirname, 'skills', 'remits-cli');
|
|
28
|
+
const SKILL_SOURCE_FILE = path.join(SKILL_SOURCE_DIR, 'SKILL.md');
|
|
29
|
+
const SKILL_REFERENCES_DIRNAME = 'references';
|
|
30
|
+
const SKILL_REFERENCES_SOURCE_DIR = path.join(SKILL_SOURCE_DIR, SKILL_REFERENCES_DIRNAME);
|
|
28
31
|
const ACCOUNT_REPO_INDEX_FILE = path.join(SESSION_DIR, 'account-repos.json');
|
|
29
32
|
const SERVICE_STATE_FILE = path.join(SESSION_DIR, 'service-state.json');
|
|
30
33
|
const ISSUES_DIR = path.join(SESSION_DIR, 'issues');
|
|
@@ -1297,7 +1300,6 @@ function updateAccountRepoIndex(cwd) {
|
|
|
1297
1300
|
if (!info) {
|
|
1298
1301
|
return;
|
|
1299
1302
|
}
|
|
1300
|
-
try { syncInstalledSkills(); } catch (_) { /* best-effort */ }
|
|
1301
1303
|
const entry = buildAccountRepoEntryFromInfo(info, cwd, 'cwd');
|
|
1302
1304
|
if (!entry) {
|
|
1303
1305
|
return;
|
|
@@ -1371,7 +1373,6 @@ function discoverAccountRepos() {
|
|
|
1371
1373
|
for (const root of roots) {
|
|
1372
1374
|
walkForAccountInfo(root, 0, matches, visited, platformMatches);
|
|
1373
1375
|
}
|
|
1374
|
-
try { syncInstalledSkills(); } catch (_) { /* best-effort */ }
|
|
1375
1376
|
|
|
1376
1377
|
const entries = [];
|
|
1377
1378
|
for (const file of matches) {
|
|
@@ -1924,13 +1925,166 @@ async function loggedGet(api, cwd, endpoint, params = {}) {
|
|
|
1924
1925
|
}
|
|
1925
1926
|
}
|
|
1926
1927
|
|
|
1928
|
+
// ---------------------------------------------------------------------------
|
|
1929
|
+
// Skill delivery
|
|
1930
|
+
//
|
|
1931
|
+
// The skill is SKILL.md (the index and the rules) plus the reference files in
|
|
1932
|
+
// skills/remits-cli/references/. Both ship inside the npm package, so the whole
|
|
1933
|
+
// skill is available before the CLI has ever authenticated.
|
|
1934
|
+
//
|
|
1935
|
+
// Every delivered markdown file goes through deliverSkillMarkdown(), which is
|
|
1936
|
+
// the ONE delivery path: it substitutes the machine-specific placeholders and
|
|
1937
|
+
// resolves each file's table of contents to real line numbers. Writing a file
|
|
1938
|
+
// out with fs.writeFileSync + readFileSync instead would ship the same skill
|
|
1939
|
+
// with a different table of contents depending on which door it came out of.
|
|
1940
|
+
// ---------------------------------------------------------------------------
|
|
1941
|
+
|
|
1942
|
+
// GitHub-style heading anchor: lowercase, punctuation dropped, spaces to hyphens.
|
|
1943
|
+
function headingSlug(headingText) {
|
|
1944
|
+
return String(headingText || '')
|
|
1945
|
+
.toLowerCase()
|
|
1946
|
+
.replace(/[^a-z0-9 _-]/g, '')
|
|
1947
|
+
.trim()
|
|
1948
|
+
.replace(/\s/g, '-');
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
function formatTocEntry(indent, target) {
|
|
1952
|
+
if (!target) return null;
|
|
1953
|
+
return indent + '- L' + target.line + ' ' + target.title;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
// Rewrites a document's "## Table of Contents" entries to carry the line number of
|
|
1957
|
+
// the heading they point at, turning the TOC into a jump table:
|
|
1958
|
+
//
|
|
1959
|
+
// - [Component Structure](#component-structure) -> - L59 Component Structure
|
|
1960
|
+
// - Line 31: Component Structure -> - L59 Component Structure
|
|
1961
|
+
//
|
|
1962
|
+
// Numbers are computed HERE, at delivery, never written into the repo file.
|
|
1963
|
+
// Hand-maintained line numbers rot on the first edit above them, and a WRONG
|
|
1964
|
+
// number is worse than none: it sends the reader to the wrong section, where it
|
|
1965
|
+
// may conclude the content is missing. Resolving at delivery means the numbers
|
|
1966
|
+
// cannot be stale, while the repo file keeps anchor links that survive edits and
|
|
1967
|
+
// produce clean diffs.
|
|
1968
|
+
//
|
|
1969
|
+
// INVARIANT: every rewrite is one line in, one line out. The line count must not
|
|
1970
|
+
// change, or the numbers computed against the input would no longer describe the
|
|
1971
|
+
// output. Locked down by test/skill-references.test.js.
|
|
1972
|
+
//
|
|
1973
|
+
// An entry whose target heading cannot be resolved is left exactly as written —
|
|
1974
|
+
// it degrades to the original text, never to a guessed number.
|
|
1975
|
+
function resolveTableOfContentsLineNumbers(markdown) {
|
|
1976
|
+
if (!markdown || !markdown.trim()) {
|
|
1977
|
+
return markdown;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
const lines = markdown.split('\n');
|
|
1981
|
+
const tocStart = lines.findIndex((line) => /^##\s+table of contents\s*$/i.test(line));
|
|
1982
|
+
if (tocStart < 0) {
|
|
1983
|
+
return markdown;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
let tocEnd = lines.length;
|
|
1987
|
+
for (let i = tocStart + 1; i < lines.length; i += 1) {
|
|
1988
|
+
if (/^##(?!#)\s+.*$/.test(lines[i])) {
|
|
1989
|
+
tocEnd = i;
|
|
1990
|
+
break;
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// A `#`-prefixed line inside a fenced code block is CONTENT, not a heading. The
|
|
1995
|
+
// references routinely show markdown and shell samples that contain one. Indexing
|
|
1996
|
+
// those shifts GitHub's duplicate-slug disambiguation (-1, -2 ...) out of step with
|
|
1997
|
+
// the real headings, so a legitimate repeated heading resolves to the wrong line.
|
|
1998
|
+
const byAnchor = new Map();
|
|
1999
|
+
const byTitle = new Map();
|
|
2000
|
+
const slugCounts = new Map();
|
|
2001
|
+
let inFence = false;
|
|
2002
|
+
|
|
2003
|
+
lines.forEach((line, index) => {
|
|
2004
|
+
if (/^\s{0,3}(```|~~~)/.test(line)) {
|
|
2005
|
+
inFence = !inFence;
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
if (inFence) return;
|
|
2009
|
+
|
|
2010
|
+
const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
2011
|
+
if (!heading) return;
|
|
2012
|
+
|
|
2013
|
+
const title = heading[2].trim();
|
|
2014
|
+
const target = { line: index + 1, title };
|
|
2015
|
+
|
|
2016
|
+
const baseSlug = headingSlug(title);
|
|
2017
|
+
const seen = slugCounts.get(baseSlug) || 0;
|
|
2018
|
+
slugCounts.set(baseSlug, seen + 1);
|
|
2019
|
+
const slug = seen === 0 ? baseSlug : baseSlug + '-' + seen;
|
|
2020
|
+
|
|
2021
|
+
if (!byAnchor.has(slug)) byAnchor.set(slug, target);
|
|
2022
|
+
if (!byTitle.has(title)) byTitle.set(title, target);
|
|
2023
|
+
});
|
|
2024
|
+
|
|
2025
|
+
let changed = false;
|
|
2026
|
+
for (let i = tocStart + 1; i < tocEnd; i += 1) {
|
|
2027
|
+
const rewritten = resolveTocEntry(lines[i], byAnchor, byTitle);
|
|
2028
|
+
if (rewritten !== null) {
|
|
2029
|
+
lines[i] = rewritten;
|
|
2030
|
+
changed = true;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
return changed ? lines.join('\n') : markdown;
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// The rewritten entry carries the ACTUAL heading text, not the TOC's own link text.
|
|
2038
|
+
// The two drift (a heading gets reworded and the TOC entry does not), and emitting
|
|
2039
|
+
// the heading text both self-heals that drift and guarantees the entry is a literal
|
|
2040
|
+
// match for the heading — so an agent can grep it as-is rather than trusting the number.
|
|
2041
|
+
//
|
|
2042
|
+
// Returns null when the line is not a resolvable TOC entry and must be left untouched.
|
|
2043
|
+
function resolveTocEntry(line, byAnchor, byTitle) {
|
|
2044
|
+
const anchorEntry = /^(\s*)[-*]\s+\[(.+)\]\(#([^)\s]+)\)\s*$/.exec(line);
|
|
2045
|
+
if (anchorEntry) {
|
|
2046
|
+
return formatTocEntry(anchorEntry[1], byAnchor.get(anchorEntry[3]));
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// Legacy hand-written form. Re-resolving it corrects files that have not been
|
|
2050
|
+
// audited yet, instead of shipping their stale numbers.
|
|
2051
|
+
const legacyEntry = /^(\s*)[-*]\s+Line\s+\d+:\s*(.+?)\s*$/.exec(line);
|
|
2052
|
+
if (legacyEntry) {
|
|
2053
|
+
const title = legacyEntry[2];
|
|
2054
|
+
return formatTocEntry(legacyEntry[1], byTitle.get(title) || byAnchor.get(headingSlug(title)));
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
return null;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
// THE single delivery path for a skill markdown file. Substitutes the placeholders
|
|
2061
|
+
// that only the installing machine can fill in, then resolves the table of contents.
|
|
2062
|
+
function deliverSkillMarkdown(sourceText, referencesDir) {
|
|
2063
|
+
let body = String(sourceText);
|
|
2064
|
+
body = body.split('{{ACCOUNT_REPO_INDEX_PATH}}').join(ACCOUNT_REPO_INDEX_FILE);
|
|
2065
|
+
body = body.split('{{SKILL_REFERENCES_DIR}}').join(referencesDir);
|
|
2066
|
+
return resolveTableOfContentsLineNumbers(body);
|
|
2067
|
+
}
|
|
2068
|
+
|
|
1927
2069
|
function resolveSkillTargets(target) {
|
|
1928
2070
|
const value = String(target || 'all').toLowerCase();
|
|
1929
2071
|
const home = os.homedir();
|
|
2072
|
+
// Gemini reads a FLAT skill file, so its references cannot sit beside it as
|
|
2073
|
+
// `references/`. That is why SKILL.md states the reference directory as an
|
|
2074
|
+
// absolute path substituted per target rather than a relative one.
|
|
1930
2075
|
const targets = {
|
|
1931
|
-
codex:
|
|
1932
|
-
|
|
1933
|
-
|
|
2076
|
+
codex: {
|
|
2077
|
+
skillPath: path.join(home, '.codex', 'skills', 'remits-cli', 'SKILL.md'),
|
|
2078
|
+
referencesDir: path.join(home, '.codex', 'skills', 'remits-cli', SKILL_REFERENCES_DIRNAME)
|
|
2079
|
+
},
|
|
2080
|
+
claude: {
|
|
2081
|
+
skillPath: path.join(home, '.claude', 'skills', 'remits-cli', 'SKILL.md'),
|
|
2082
|
+
referencesDir: path.join(home, '.claude', 'skills', 'remits-cli', SKILL_REFERENCES_DIRNAME)
|
|
2083
|
+
},
|
|
2084
|
+
gemini: {
|
|
2085
|
+
skillPath: path.join(home, '.gemini', 'skills', 'remits-cli.md'),
|
|
2086
|
+
referencesDir: path.join(home, '.gemini', 'skills', 'remits-cli', SKILL_REFERENCES_DIRNAME)
|
|
2087
|
+
}
|
|
1934
2088
|
};
|
|
1935
2089
|
|
|
1936
2090
|
if (value === 'all') {
|
|
@@ -1942,34 +2096,83 @@ function resolveSkillTargets(target) {
|
|
|
1942
2096
|
return [[value, targets[value]]];
|
|
1943
2097
|
}
|
|
1944
2098
|
|
|
1945
|
-
function
|
|
2099
|
+
function listSkillReferenceSources() {
|
|
2100
|
+
if (!fs.existsSync(SKILL_REFERENCES_SOURCE_DIR)) {
|
|
2101
|
+
return [];
|
|
2102
|
+
}
|
|
2103
|
+
return fs.readdirSync(SKILL_REFERENCES_SOURCE_DIR)
|
|
2104
|
+
.filter((name) => name.endsWith('.md'))
|
|
2105
|
+
.sort();
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
function buildSkillSourceBody(referencesDir) {
|
|
1946
2109
|
if (!fs.existsSync(SKILL_SOURCE_FILE)) {
|
|
1947
2110
|
throw new Error('Skill source file not found: ' + SKILL_SOURCE_FILE);
|
|
1948
2111
|
}
|
|
2112
|
+
return deliverSkillMarkdown(fs.readFileSync(SKILL_SOURCE_FILE, 'utf8'), referencesDir);
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// Install the reference set beside (or, for gemini, alongside) the skill file, and
|
|
2116
|
+
// prune any reference this CLI version no longer ships. A stale reference is worse
|
|
2117
|
+
// than a missing one: SKILL.md would not name it, but an agent listing the directory
|
|
2118
|
+
// would still find and trust it.
|
|
2119
|
+
function writeSkillReferences(referencesDir, options = {}) {
|
|
2120
|
+
const names = listSkillReferenceSources();
|
|
2121
|
+
const written = [];
|
|
2122
|
+
if (!names.length) {
|
|
2123
|
+
return written;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
ensureDir(referencesDir);
|
|
2127
|
+
const expected = new Set(names);
|
|
2128
|
+
|
|
2129
|
+
for (const name of names) {
|
|
2130
|
+
const body = deliverSkillMarkdown(
|
|
2131
|
+
fs.readFileSync(path.join(SKILL_REFERENCES_SOURCE_DIR, name), 'utf8'),
|
|
2132
|
+
referencesDir
|
|
2133
|
+
);
|
|
2134
|
+
const targetPath = path.join(referencesDir, name);
|
|
2135
|
+
if (options.overwrite === false && fs.existsSync(targetPath)) {
|
|
2136
|
+
continue;
|
|
2137
|
+
}
|
|
2138
|
+
if (writeFileIfChanged(targetPath, body)) {
|
|
2139
|
+
written.push(targetPath);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
try {
|
|
2144
|
+
for (const existing of fs.readdirSync(referencesDir)) {
|
|
2145
|
+
if (existing.endsWith('.md') && !expected.has(existing)) {
|
|
2146
|
+
fs.unlinkSync(path.join(referencesDir, existing));
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
} catch (_) {
|
|
2150
|
+
// Best-effort pruning; a stale file never blocks delivery of the current set.
|
|
2151
|
+
}
|
|
1949
2152
|
|
|
1950
|
-
|
|
1951
|
-
sourceBody = sourceBody.replace('{{ACCOUNT_REPO_INDEX_PATH}}', ACCOUNT_REPO_INDEX_FILE);
|
|
1952
|
-
return sourceBody;
|
|
2153
|
+
return written;
|
|
1953
2154
|
}
|
|
1954
2155
|
|
|
1955
2156
|
function syncInstalledSkills(target = 'all') {
|
|
1956
2157
|
const targets = resolveSkillTargets(target);
|
|
1957
|
-
const sourceBody = buildSkillSourceBody();
|
|
1958
2158
|
const summary = {
|
|
1959
2159
|
updated: [],
|
|
1960
2160
|
failed: []
|
|
1961
2161
|
};
|
|
1962
2162
|
|
|
1963
|
-
for (const [agent,
|
|
2163
|
+
for (const [agent, spec] of targets) {
|
|
1964
2164
|
try {
|
|
1965
|
-
ensureDir(path.dirname(
|
|
1966
|
-
if (writeFileIfChanged(
|
|
1967
|
-
summary.updated.push({ agent, path:
|
|
2165
|
+
ensureDir(path.dirname(spec.skillPath));
|
|
2166
|
+
if (writeFileIfChanged(spec.skillPath, buildSkillSourceBody(spec.referencesDir))) {
|
|
2167
|
+
summary.updated.push({ agent, path: spec.skillPath });
|
|
2168
|
+
}
|
|
2169
|
+
for (const referencePath of writeSkillReferences(spec.referencesDir)) {
|
|
2170
|
+
summary.updated.push({ agent, path: referencePath });
|
|
1968
2171
|
}
|
|
1969
2172
|
} catch (error) {
|
|
1970
2173
|
summary.failed.push({
|
|
1971
2174
|
agent,
|
|
1972
|
-
path:
|
|
2175
|
+
path: spec.skillPath,
|
|
1973
2176
|
reason: error && error.message ? error.message : String(error)
|
|
1974
2177
|
});
|
|
1975
2178
|
}
|
|
@@ -1985,19 +2188,24 @@ async function installSkillsCommand(flags) {
|
|
|
1985
2188
|
|
|
1986
2189
|
const overwrite = flags.overwrite === true || flags.overwrite === 'true';
|
|
1987
2190
|
const targets = resolveSkillTargets(flags.target || 'all');
|
|
1988
|
-
const sourceBody = buildSkillSourceBody();
|
|
1989
2191
|
|
|
1990
2192
|
const installed = [];
|
|
1991
2193
|
const skipped = [];
|
|
1992
2194
|
|
|
1993
|
-
for (const [agent,
|
|
1994
|
-
ensureDir(path.dirname(
|
|
1995
|
-
if (fs.existsSync(
|
|
1996
|
-
skipped.push({ agent, path:
|
|
1997
|
-
|
|
2195
|
+
for (const [agent, spec] of targets) {
|
|
2196
|
+
ensureDir(path.dirname(spec.skillPath));
|
|
2197
|
+
if (fs.existsSync(spec.skillPath) && !overwrite) {
|
|
2198
|
+
skipped.push({ agent, path: spec.skillPath, reason: 'already exists (use --overwrite true)' });
|
|
2199
|
+
} else {
|
|
2200
|
+
atomicWriteFile(spec.skillPath, buildSkillSourceBody(spec.referencesDir));
|
|
2201
|
+
installed.push({ agent, path: spec.skillPath });
|
|
2202
|
+
}
|
|
2203
|
+
// References are refreshed even when the skill file itself was left alone: an
|
|
2204
|
+
// agent reading a customized SKILL.md still needs the reference set it points at.
|
|
2205
|
+
const references = writeSkillReferences(spec.referencesDir);
|
|
2206
|
+
if (references.length) {
|
|
2207
|
+
installed.push({ agent, path: spec.referencesDir + ' (' + references.length + ' reference file(s))' });
|
|
1998
2208
|
}
|
|
1999
|
-
atomicWriteFile(targetPath, sourceBody);
|
|
2000
|
-
installed.push({ agent, path: targetPath });
|
|
2001
2209
|
}
|
|
2002
2210
|
|
|
2003
2211
|
console.log('Skill installation complete.');
|
|
@@ -9140,6 +9348,15 @@ async function main() {
|
|
|
9140
9348
|
if (autoUpdateIfNeeded(originalArgv, { requireSuccess: requireSuccessfulAutoUpdate })) return;
|
|
9141
9349
|
}
|
|
9142
9350
|
|
|
9351
|
+
// Refresh the installed skill on EVERY command, not just from inside an account repo. This runs
|
|
9352
|
+
// after the auto-update re-exec, so it is the new version writing the new skill. It was previously
|
|
9353
|
+
// hung off updateAccountRepoIndex(), which returns early outside a repo — so a user who upgraded and
|
|
9354
|
+
// then ran `remits-cli agent serve` from a plain tab (the documented way to work tickets, explicitly
|
|
9355
|
+
// "no need to be in an account repo") kept the PREVIOUS skill on disk indefinitely: a stale index
|
|
9356
|
+
// pointing at reference files that the upgrade had just renamed or removed. Reading and comparing the
|
|
9357
|
+
// whole corpus across all three agent targets measures ~3ms, so there is nothing to save by guarding it.
|
|
9358
|
+
try { syncInstalledSkills(); } catch (_) { /* best-effort */ }
|
|
9359
|
+
|
|
9143
9360
|
// Best-effort: update account-repo index whenever running from inside a remits repo
|
|
9144
9361
|
try { updateAccountRepoIndex(process.cwd()); } catch (_) { /* not in account repo */ }
|
|
9145
9362
|
// Auto-start the background service if not already running.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remits/remits-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.114",
|
|
4
4
|
"description": "Local CLI for auth, component sync, and live test execution against Remits",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"index.js",
|
|
14
14
|
"README.md",
|
|
15
|
-
"skills/remits-cli/SKILL.md"
|
|
15
|
+
"skills/remits-cli/SKILL.md",
|
|
16
|
+
"skills/remits-cli/references"
|
|
16
17
|
],
|
|
17
18
|
"publishConfig": {
|
|
18
19
|
"access": "public"
|