sekura 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/catalogue.js +42 -7
- package/dist/commands/init.js +23 -2
- package/dist/writers.js +133 -6
- package/package.json +30 -7
package/README.md
CHANGED
|
@@ -36,13 +36,13 @@ npx sekura@latest uninstall # remove the entries, the skill and the
|
|
|
36
36
|
| GitHub Copilot (VS Code) | the VS Code user `mcp.json` → `servers.sekura` | `.mcp.json` |
|
|
37
37
|
| OpenAI Codex | `codex mcp add sekura --url …`, or `~/.codex/config.toml` → `[mcp_servers.sekura]` | — |
|
|
38
38
|
| Kiro | `~/.kiro/settings/mcp.json` | `.kiro/settings/mcp.json` |
|
|
39
|
-
| Cline |
|
|
39
|
+
| Cline | `~/.cline/data/settings/cline_mcp_settings.json` (shared by the extension, v4 and later, and the Cline CLI) | — |
|
|
40
40
|
| Zed | `~/.config/zed/settings.json` → `context_servers.sekura` (shown by hand when the file has comments) | — |
|
|
41
41
|
| Antigravity | `~/.gemini/config/mcp_config.json` → `serverUrl` | `.agents/mcp_config.json` |
|
|
42
42
|
| OpenCode | `~/.config/opencode/opencode.json` → `mcp.sekura` | `opencode.json` |
|
|
43
43
|
| JetBrains Junie | — | `.junie/mcp/mcp.json` |
|
|
44
44
|
| Factory Droid, Tabnine, Devin | the tool's own `mcp add` command | — |
|
|
45
|
-
| Goose |
|
|
45
|
+
| Goose | `~/.config/goose/config.yaml` → `extensions.sekura` (`streamable_http`) | — |
|
|
46
46
|
|
|
47
47
|
The skill goes to `~/.agents/skills/sekura/SKILL.md` and, when Claude Code is present, `~/.claude/skills/sekura/SKILL.md`; in project scope to the same paths under the project, plus a section between `<!-- sekura:start -->` and `<!-- sekura:end -->` in `AGENTS.md` (and `CLAUDE.md` if one exists). Reruns replace only what sits between the markers.
|
|
48
48
|
|
package/dist/catalogue.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// A tool is "present" when its config directory exists or its binary is on
|
|
10
10
|
// PATH. Presence decides what the wizard offers, never what it writes: the
|
|
11
11
|
// person chooses, and a tool they did not choose is not touched.
|
|
12
|
-
import { existsSync } from 'node:fs';
|
|
12
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
13
13
|
import { homedir, platform } from 'node:os';
|
|
14
14
|
import { join, delimiter } from 'node:path';
|
|
15
15
|
export const ENDPOINT = 'https://sekura.ai/mcp';
|
|
@@ -17,7 +17,7 @@ export const SERVER_NAME = 'sekura';
|
|
|
17
17
|
export const DOCS = 'https://sekura.ai/connect/';
|
|
18
18
|
/** The home directory, overridable for tests. */
|
|
19
19
|
export const home = () => process.env.SEKURA_HOME || homedir();
|
|
20
|
-
/** VS Code's user directory, where Copilot's `mcp.json`
|
|
20
|
+
/** VS Code's user directory, where Copilot's `mcp.json` lives. */
|
|
21
21
|
export function codeUserDir(h = home()) {
|
|
22
22
|
switch (platform()) {
|
|
23
23
|
case 'darwin': return join(h, 'Library', 'Application Support', 'Code', 'User');
|
|
@@ -26,6 +26,34 @@ export function codeUserDir(h = home()) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
const xdg = (h) => process.env.XDG_CONFIG_HOME || join(h, '.config');
|
|
29
|
+
/** Cline's MCP settings. Since v4 the extension and the Cline CLI share one
|
|
30
|
+
* file under ~/.cline/data (cline/cline, sdk/packages/shared/src/storage/
|
|
31
|
+
* paths.ts, resolveMcpSettingsPath), with the same three overrides. The
|
|
32
|
+
* file in VS Code's global storage is only migrated from, once per source,
|
|
33
|
+
* so an entry written there after that is never read. */
|
|
34
|
+
export function clineSettingsPath(h = home()) {
|
|
35
|
+
const explicit = process.env.CLINE_MCP_SETTINGS_PATH?.trim();
|
|
36
|
+
if (explicit)
|
|
37
|
+
return explicit;
|
|
38
|
+
const data = process.env.CLINE_DATA_DIR?.trim() || join(process.env.CLINE_DIR?.trim() || join(h, '.cline'), 'data');
|
|
39
|
+
return join(data, 'settings', 'cline_mcp_settings.json');
|
|
40
|
+
}
|
|
41
|
+
/** True when a VS Code extension is installed: its folder is `<publisher.name>-<version>`. */
|
|
42
|
+
function hasExtension(h, id) {
|
|
43
|
+
try {
|
|
44
|
+
return readdirSync(join(h, '.vscode', 'extensions')).some((d) => d.toLowerCase().startsWith(`${id}-`));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Goose's config directory: ~/.config/goose on macOS and Linux (Goose uses
|
|
51
|
+
* the XDG layout on both), %APPDATA%\Block\goose\config on Windows. */
|
|
52
|
+
export function gooseDir(h = home()) {
|
|
53
|
+
if (platform() === 'win32')
|
|
54
|
+
return join(process.env.APPDATA || join(h, 'AppData', 'Roaming'), 'Block', 'goose', 'config');
|
|
55
|
+
return join(xdg(h), 'goose');
|
|
56
|
+
}
|
|
29
57
|
/** True when `bin` is an executable on PATH. */
|
|
30
58
|
export function onPath(bin) {
|
|
31
59
|
const exts = platform() === 'win32' ? ['.exe', '.cmd', '.bat', ''] : [''];
|
|
@@ -46,7 +74,7 @@ export const TOOLS = [
|
|
|
46
74
|
id: 'tools-claude', name: 'Claude Code',
|
|
47
75
|
detect: (h) => existsSync(join(h, '.claude')) || existsSync(join(h, '.claude.json')) || onPath('claude'),
|
|
48
76
|
user: (h) => [
|
|
49
|
-
{ kind: 'command', bin: 'claude', add: ['mcp', 'add', '--transport', 'http', '-s', 'user', SERVER_NAME, ENDPOINT], remove: ['mcp', 'remove', '-s', 'user', SERVER_NAME] },
|
|
77
|
+
{ kind: 'command', bin: 'claude', add: ['mcp', 'add', '--transport', 'http', '-s', 'user', SERVER_NAME, ENDPOINT], remove: ['mcp', 'remove', '-s', 'user', SERVER_NAME], get: ['mcp', 'get', SERVER_NAME] },
|
|
50
78
|
{ kind: 'json', path: join(h, '.claude.json'), key: 'mcpServers', entry: http },
|
|
51
79
|
],
|
|
52
80
|
project: (cwd) => [{ kind: 'json', path: join(cwd, '.mcp.json'), key: 'mcpServers', entry: http }],
|
|
@@ -84,8 +112,10 @@ export const TOOLS = [
|
|
|
84
112
|
},
|
|
85
113
|
{
|
|
86
114
|
id: 'tools-cline', name: 'Cline',
|
|
87
|
-
|
|
88
|
-
|
|
115
|
+
// A fresh v4 install has no global-storage folder until the extension
|
|
116
|
+
// writes one, so the installed extension and ~/.cline count too.
|
|
117
|
+
detect: (h) => existsSync(join(h, '.cline')) || hasExtension(h, 'saoudrizwan.claude-dev') || existsSync(join(codeUserDir(h), 'globalStorage', 'saoudrizwan.claude-dev')),
|
|
118
|
+
user: (h) => [{ kind: 'json', path: clineSettingsPath(h), key: 'mcpServers', entry: { type: 'streamableHttp', url: ENDPOINT } }],
|
|
89
119
|
signIn: SIGN_IN,
|
|
90
120
|
},
|
|
91
121
|
{
|
|
@@ -138,8 +168,13 @@ export const TOOLS = [
|
|
|
138
168
|
},
|
|
139
169
|
{
|
|
140
170
|
id: 'tools-goose', name: 'Goose',
|
|
141
|
-
detect: (h) => existsSync(
|
|
142
|
-
|
|
171
|
+
detect: (h) => existsSync(gooseDir(h)) || onPath('goose'),
|
|
172
|
+
// The streamable_http shape of Goose's ExtensionConfig (block/goose,
|
|
173
|
+
// crates/goose/src/agents/extension.rs; documentation/docs/guides/config-files.md).
|
|
174
|
+
user: (h) => [{ kind: 'yaml', path: join(gooseDir(h), 'config.yaml'), key: 'extensions', lines: [
|
|
175
|
+
'type: streamable_http', `name: ${SERVER_NAME}`, 'enabled: true', `uri: ${ENDPOINT}`,
|
|
176
|
+
'envs: {}', 'env_keys: []', 'headers: {}', 'timeout: 300', 'bundled: null', 'available_tools: []',
|
|
177
|
+
] }],
|
|
143
178
|
signIn: SIGN_IN,
|
|
144
179
|
},
|
|
145
180
|
];
|
package/dist/commands/init.js
CHANGED
|
@@ -2,10 +2,25 @@
|
|
|
2
2
|
// nothing is written before the review is shown and (unless --yes) agreed.
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { checkbox, confirm, select } from '@inquirer/prompts';
|
|
5
|
-
import {
|
|
5
|
+
import { realpathSync } from 'node:fs';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { dirname, resolve } from 'node:path';
|
|
8
|
+
import { DOCS, ENDPOINT, TOOLS, home } from '../catalogue.js';
|
|
6
9
|
import { applyStep, buildPlan, describeStep, detectTools } from '../plan.js';
|
|
7
10
|
import { verifyEndpoint } from '../verify.js';
|
|
8
11
|
import { banner, cross, dot, say, tick } from '../ui.js';
|
|
12
|
+
/** The home folder (or the top of the disk) is no project: project scope
|
|
13
|
+
* there would put AGENTS.md and .mcp.json in it for every tool to find. */
|
|
14
|
+
export function isProjectDir(cwd) {
|
|
15
|
+
const real = (p) => { try {
|
|
16
|
+
return realpathSync(p);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return resolve(p);
|
|
20
|
+
} };
|
|
21
|
+
const dir = real(cwd);
|
|
22
|
+
return dir !== real(home()) && dir !== real(homedir()) && dirname(dir) !== dir;
|
|
23
|
+
}
|
|
9
24
|
export async function runInit(opts) {
|
|
10
25
|
const interactive = !opts.yes && process.stdin.isTTY && process.stderr.isTTY;
|
|
11
26
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -35,7 +50,13 @@ export async function runInit(opts) {
|
|
|
35
50
|
}
|
|
36
51
|
// 2. Scope and skill.
|
|
37
52
|
let scope = opts.scope ?? 'user';
|
|
38
|
-
|
|
53
|
+
const inProject = isProjectDir(cwd);
|
|
54
|
+
if (scope === 'project' && !inProject) {
|
|
55
|
+
say(` ${cwd} is your home folder or the top of the disk, not a project: run sekura init --scope project inside the project, or use --scope user.`);
|
|
56
|
+
say(' Nothing was changed.');
|
|
57
|
+
return { written: 0, byHand: 0, failed: 1 };
|
|
58
|
+
}
|
|
59
|
+
if (!opts.scope && interactive && inProject) {
|
|
39
60
|
scope = await select({
|
|
40
61
|
message: 'Where',
|
|
41
62
|
choices: [
|
package/dist/writers.js
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
// save them a copy-paste. Such a file is reported with the lines to add by
|
|
9
9
|
// hand. A TOML file (Codex) is edited as text: the table is appended when
|
|
10
10
|
// absent, replaced when present, and the rest of the file is left byte for
|
|
11
|
-
// byte.
|
|
11
|
+
// byte. So is a YAML file (Goose): the one entry under its top-level key,
|
|
12
|
+
// at the indent the file already uses; Goose rewrites the file itself, so
|
|
13
|
+
// whether the entry is there is read from its fields, not its text.
|
|
12
14
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
13
15
|
import { dirname } from 'node:path';
|
|
14
16
|
import { spawnSync } from 'node:child_process';
|
|
15
|
-
import { SERVER_NAME } from './catalogue.js';
|
|
17
|
+
import { ENDPOINT, SERVER_NAME } from './catalogue.js';
|
|
16
18
|
const describeJson = (t) => JSON.stringify({ [t.key]: { [SERVER_NAME]: t.entry } }, null, 2);
|
|
17
19
|
/** What applying the target would do, for the review before any write. */
|
|
18
20
|
export function describe(t, dryRun = false) {
|
|
@@ -20,6 +22,7 @@ export function describe(t, dryRun = false) {
|
|
|
20
22
|
switch (t.kind) {
|
|
21
23
|
case 'json': return `${verb}write ${t.path}: ${t.key}.${SERVER_NAME} = ${JSON.stringify(t.entry)}`;
|
|
22
24
|
case 'toml': return `${verb}write ${t.path}: [${t.table}] ${t.lines.join('; ')}`;
|
|
25
|
+
case 'yaml': return `${verb}write ${t.path}: ${t.key}.${SERVER_NAME} (${t.lines.slice(0, 4).join(', ')})`;
|
|
23
26
|
case 'command': return `${verb}run ${t.bin} ${t.add.join(' ')}`;
|
|
24
27
|
case 'link': return `by hand: ${t.note} ${t.href}`;
|
|
25
28
|
}
|
|
@@ -123,9 +126,109 @@ export function hasToml(t) {
|
|
|
123
126
|
const span = tomlSpan(text, t.table);
|
|
124
127
|
return !!span && text.slice(span[0], span[1]).trim() === tomlBlock(t).trim();
|
|
125
128
|
}
|
|
129
|
+
const yamlBlock = (t, indent) => `${indent}${SERVER_NAME}:\n` + t.lines.map((l) => `${indent}${indent}${l}\n`).join('');
|
|
130
|
+
const leading = (line) => /^[ ]*/.exec(line)[0];
|
|
131
|
+
const blank = (line) => !line.trim() || /^\s*#/.test(line);
|
|
132
|
+
/** Where `key:` and our entry under it sit, line by line. `inline` is a key
|
|
133
|
+
* whose value is on its own line (`extensions: {}`), which is not edited. */
|
|
134
|
+
function yamlLayout(lines, key) {
|
|
135
|
+
const head = lines.findIndex((l) => l.startsWith(`${key}:`));
|
|
136
|
+
if (head < 0)
|
|
137
|
+
return { missing: true };
|
|
138
|
+
if (!/^[^:]+:[ \t]*(#.*)?$/.test(lines[head]))
|
|
139
|
+
return { head, inline: true };
|
|
140
|
+
let end = head + 1;
|
|
141
|
+
while (end < lines.length && (blank(lines[end]) ? !/^#/.test(lines[end]) : /^\s/.test(lines[end])))
|
|
142
|
+
end++;
|
|
143
|
+
while (end > head + 1 && !lines[end - 1].trim())
|
|
144
|
+
end--;
|
|
145
|
+
const first = lines.slice(head + 1, end).find((l) => !blank(l));
|
|
146
|
+
const indent = first ? leading(first) : ' ';
|
|
147
|
+
const start = lines.findIndex((l, i) => i > head && i < end && l.replace(/\s+$/, '') === `${indent}${SERVER_NAME}:`);
|
|
148
|
+
let stop = start + 1;
|
|
149
|
+
if (start >= 0)
|
|
150
|
+
while (stop < end && (blank(lines[stop]) || leading(lines[stop]).length > indent.length))
|
|
151
|
+
stop++;
|
|
152
|
+
while (start >= 0 && stop > start + 1 && !lines[stop - 1].trim())
|
|
153
|
+
stop--;
|
|
154
|
+
return { head, end, indent, entry: start >= 0 ? [start, stop] : null };
|
|
155
|
+
}
|
|
156
|
+
const unquote = (v) => v.replace(/\s+#.*$/, '').trim().replace(/^(['"])(.*)\1$/, '$2');
|
|
157
|
+
/** Our entry's own fields as they stand, whatever order or quoting Goose gave them. */
|
|
158
|
+
function yamlFields(lines) {
|
|
159
|
+
const own = lines.find((l) => !blank(l));
|
|
160
|
+
const depth = own ? leading(own).length : 0;
|
|
161
|
+
const out = {};
|
|
162
|
+
for (const l of lines) {
|
|
163
|
+
const m = /^( *)([A-Za-z_]+):(.*)$/.exec(l);
|
|
164
|
+
if (m && m[1].length === depth)
|
|
165
|
+
out[m[2]] = unquote(m[3]);
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
function yamlOk(t, entry) {
|
|
170
|
+
const f = yamlFields(entry);
|
|
171
|
+
const want = yamlFields(t.lines);
|
|
172
|
+
return f.type === want.type && f.uri === ENDPOINT && f.enabled === 'true';
|
|
173
|
+
}
|
|
174
|
+
const splitLines = (text) => text.replace(/\n$/, '').split('\n');
|
|
175
|
+
export function applyYaml(t) {
|
|
176
|
+
const text = existsSync(t.path) ? readFileSync(t.path, 'utf8') : '';
|
|
177
|
+
if (/\t/.test(text.split('\n').filter((l) => /^\s/.test(l)).join(''))) {
|
|
178
|
+
return { status: 'by-hand', where: t.path, why: 'it is indented with tabs, which this installer will not rewrite', snippet: `${t.key}:\n${yamlBlock(t, ' ')}` };
|
|
179
|
+
}
|
|
180
|
+
const lines = text ? splitLines(text) : [];
|
|
181
|
+
const at = yamlLayout(lines, t.key);
|
|
182
|
+
if ('inline' in at) {
|
|
183
|
+
return { status: 'by-hand', where: t.path, why: `its ${t.key} is written on one line, which this installer will not rewrite`, snippet: `${t.key}:\n${yamlBlock(t, ' ')}` };
|
|
184
|
+
}
|
|
185
|
+
let out;
|
|
186
|
+
if ('missing' in at) {
|
|
187
|
+
const sep = !text ? [] : lines[lines.length - 1].trim() ? [''] : [];
|
|
188
|
+
out = [...lines, ...sep, `${t.key}:`, ...splitLines(yamlBlock(t, ' '))];
|
|
189
|
+
}
|
|
190
|
+
else if (at.entry) {
|
|
191
|
+
const [a, b] = at.entry;
|
|
192
|
+
if (yamlOk(t, lines.slice(a + 1, b)))
|
|
193
|
+
return { status: 'unchanged', where: t.path };
|
|
194
|
+
out = [...lines.slice(0, a), ...splitLines(yamlBlock(t, at.indent)), ...lines.slice(b)];
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
out = [...lines.slice(0, at.end), ...splitLines(yamlBlock(t, at.indent)), ...lines.slice(at.end)];
|
|
198
|
+
}
|
|
199
|
+
mkdirSync(dirname(t.path), { recursive: true });
|
|
200
|
+
writeFileSync(t.path, out.join('\n') + '\n');
|
|
201
|
+
return { status: 'written', where: t.path };
|
|
202
|
+
}
|
|
203
|
+
export function removeYaml(t) {
|
|
204
|
+
if (!existsSync(t.path))
|
|
205
|
+
return { status: 'absent', where: t.path };
|
|
206
|
+
const lines = splitLines(readFileSync(t.path, 'utf8'));
|
|
207
|
+
const at = yamlLayout(lines, t.key);
|
|
208
|
+
if (!('entry' in at) || !at.entry)
|
|
209
|
+
return { status: 'absent', where: t.path };
|
|
210
|
+
const [a, b] = at.entry;
|
|
211
|
+
const out = [...lines.slice(0, a), ...lines.slice(b)];
|
|
212
|
+
// A mapping left with nothing under it reads as null, which Goose refuses.
|
|
213
|
+
const rest = [...lines.slice(at.head + 1, a), ...lines.slice(b, at.end)];
|
|
214
|
+
if (!rest.some((l) => !blank(l)))
|
|
215
|
+
out[at.head] = `${t.key}: {}`;
|
|
216
|
+
writeFileSync(t.path, out.join('\n') + '\n');
|
|
217
|
+
return { status: 'removed', where: t.path };
|
|
218
|
+
}
|
|
219
|
+
export function hasYaml(t) {
|
|
220
|
+
if (!existsSync(t.path))
|
|
221
|
+
return false;
|
|
222
|
+
const lines = splitLines(readFileSync(t.path, 'utf8'));
|
|
223
|
+
const at = yamlLayout(lines, t.key);
|
|
224
|
+
return 'entry' in at && !!at.entry && yamlOk(t, lines.slice(at.entry[0] + 1, at.entry[1]));
|
|
225
|
+
}
|
|
126
226
|
// ── commands ────────────────────────────────────────────────────────────────
|
|
227
|
+
function spawn(bin, args) {
|
|
228
|
+
return spawnSync(bin, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
229
|
+
}
|
|
127
230
|
export function runCommand(bin, args) {
|
|
128
|
-
const r =
|
|
231
|
+
const r = spawn(bin, args);
|
|
129
232
|
const where = `${bin} ${args.join(' ')}`;
|
|
130
233
|
if (r.error)
|
|
131
234
|
return { status: 'failed', where, why: r.error.message };
|
|
@@ -133,12 +236,34 @@ export function runCommand(bin, args) {
|
|
|
133
236
|
return { status: 'failed', where, why: (r.stderr || r.stdout || `exit ${r.status}`).trim().split('\n').slice(-3).join(' ') };
|
|
134
237
|
return { status: 'ran', where };
|
|
135
238
|
}
|
|
136
|
-
|
|
239
|
+
/** Whether the tool's own command lists our entry at our address. */
|
|
240
|
+
function commandHas(t) {
|
|
241
|
+
if (!t.get)
|
|
242
|
+
return 'unknown';
|
|
243
|
+
const r = spawn(t.bin, t.get);
|
|
244
|
+
if (r.error)
|
|
245
|
+
return 'unknown';
|
|
246
|
+
return r.status === 0 && `${r.stdout}`.includes(ENDPOINT);
|
|
247
|
+
}
|
|
248
|
+
/** Adds the entry. A tool that refuses because the name is taken is asked
|
|
249
|
+
* what it holds: our address is "already there"; another one is replaced,
|
|
250
|
+
* the way a JSON entry pointing elsewhere is corrected on rerun. */
|
|
251
|
+
export function applyCommand(t) {
|
|
252
|
+
const r = runCommand(t.bin, t.add);
|
|
253
|
+
if (r.status !== 'failed' || !t.get || !/already exists/i.test(r.why))
|
|
254
|
+
return r;
|
|
255
|
+
if (commandHas(t) === true)
|
|
256
|
+
return { status: 'unchanged', where: `${t.bin} ${t.get.join(' ')}` };
|
|
257
|
+
const out = runCommand(t.bin, t.remove);
|
|
258
|
+
return out.status === 'failed' ? out : runCommand(t.bin, t.add);
|
|
259
|
+
}
|
|
260
|
+
// ── one door for every kind ────────────────────────────────────────────
|
|
137
261
|
export function apply(t) {
|
|
138
262
|
switch (t.kind) {
|
|
139
263
|
case 'json': return applyJson(t);
|
|
140
264
|
case 'toml': return applyToml(t);
|
|
141
|
-
case '
|
|
265
|
+
case 'yaml': return applyYaml(t);
|
|
266
|
+
case 'command': return applyCommand(t);
|
|
142
267
|
case 'link': return { status: 'by-hand', where: t.href, why: t.note, snippet: '' };
|
|
143
268
|
}
|
|
144
269
|
}
|
|
@@ -146,6 +271,7 @@ export function remove(t) {
|
|
|
146
271
|
switch (t.kind) {
|
|
147
272
|
case 'json': return removeJson(t);
|
|
148
273
|
case 'toml': return removeToml(t);
|
|
274
|
+
case 'yaml': return removeYaml(t);
|
|
149
275
|
case 'command': return runCommand(t.bin, t.remove);
|
|
150
276
|
case 'link': return { status: 'absent', where: t.href };
|
|
151
277
|
}
|
|
@@ -158,7 +284,8 @@ export function has(t) {
|
|
|
158
284
|
return r === 'unreadable' ? 'unknown' : r;
|
|
159
285
|
}
|
|
160
286
|
case 'toml': return hasToml(t);
|
|
161
|
-
case '
|
|
287
|
+
case 'yaml': return hasYaml(t);
|
|
288
|
+
case 'command': return commandHas(t);
|
|
162
289
|
case 'link': return 'unknown';
|
|
163
290
|
}
|
|
164
291
|
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sekura",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Connect your coding tool to Sekura in one command: npx sekura@latest init",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://sekura.ai/connect/",
|
|
8
|
-
"repository": {
|
|
9
|
-
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/sekuraai/sekura-cli.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/sekuraai/sekura-cli/issues"
|
|
14
|
+
},
|
|
10
15
|
"author": "Sekura Inc. <hello@sekura.ai>",
|
|
11
|
-
"keywords": [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
"keywords": [
|
|
17
|
+
"sekura",
|
|
18
|
+
"mcp",
|
|
19
|
+
"security",
|
|
20
|
+
"vulnerability-scanning",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"cursor",
|
|
23
|
+
"copilot",
|
|
24
|
+
"codex"
|
|
25
|
+
],
|
|
26
|
+
"bin": {
|
|
27
|
+
"sekura": "dist/cli.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"skills",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
15
38
|
"scripts": {
|
|
16
39
|
"build": "tsc -p tsconfig.json && node -e \"require('fs').chmodSync('dist/cli.js', 0o755)\"",
|
|
17
40
|
"test": "npm run build && node --test test/init.test.mjs",
|