ineedcodes 1.7.0 → 1.7.2
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/package.json +1 -1
- package/src/cli.js +32 -0
- package/src/session.js +6 -5
- package/src/skills.js +29 -8
- package/src/ui.js +1 -1
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
// First open asks for provider setup. After that, just say what you want.
|
|
4
4
|
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as os from 'node:os';
|
|
8
|
+
import * as path from 'node:path';
|
|
6
9
|
|
|
7
10
|
const [MAJOR] = process.versions.node.split('.').map(Number);
|
|
8
11
|
if (!(MAJOR >= 20)) {
|
|
@@ -27,6 +30,7 @@ ${bold('ineed')} ${dim(`v${VERSION}`)} - your terminal, now autonomous
|
|
|
27
30
|
|
|
28
31
|
${green('ineed')} interactive session (first open: setup)
|
|
29
32
|
${green('ineed "fix the build errors"')} one-shot task
|
|
33
|
+
${green('ineed unlock')} enable gated skills on this machine
|
|
30
34
|
${green('ineed --reset')} redo provider setup
|
|
31
35
|
${green('ineed --version')} show version
|
|
32
36
|
|
|
@@ -35,6 +39,34 @@ Inside a session, ${dim('/help')} lists the shortcuts. Or just talk to it.
|
|
|
35
39
|
process.exit(0);
|
|
36
40
|
}
|
|
37
41
|
|
|
42
|
+
// unlock: store the developer keyword locally so gated security skills activate here
|
|
43
|
+
if (args[0] === 'unlock') {
|
|
44
|
+
const readline = await import('node:readline');
|
|
45
|
+
const { makeInput, dim, gray } = await import('./ui.js');
|
|
46
|
+
const keywordDir = process.env.INEED_CONFIG_DIR || path.join(os.homedir(), '.ineedcodes');
|
|
47
|
+
const keywordFile = path.join(keywordDir, 'keyword');
|
|
48
|
+
const given = args[1];
|
|
49
|
+
let keyword = '';
|
|
50
|
+
if (given) {
|
|
51
|
+
keyword = given.trim();
|
|
52
|
+
} else {
|
|
53
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
54
|
+
const ask = makeInput(rl);
|
|
55
|
+
keyword = (await ask('Developer keyword: ', { secret: true })).trim();
|
|
56
|
+
rl.close();
|
|
57
|
+
}
|
|
58
|
+
if (!keyword) {
|
|
59
|
+
console.error(red('No keyword given. Nothing saved.'));
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
fs.mkdirSync(keywordDir, { recursive: true });
|
|
63
|
+
fs.writeFileSync(keywordFile, keyword, { mode: 0o600 });
|
|
64
|
+
try { fs.chmodSync(keywordFile, 0o600); } catch {}
|
|
65
|
+
console.log(green('Gated skills unlocked on this machine.'));
|
|
66
|
+
console.log(dim('Saved to ' + keywordFile + ' (0600). Use them by mentioning the keyword in a task, e.g. /skills or "use the sqli skill".'));
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
38
70
|
// internal: one-shot worker. Spawned by the branch below, never run by users.
|
|
39
71
|
if (args[0] === '--child') {
|
|
40
72
|
const task = args.slice(1).join(' ');
|
package/src/session.js
CHANGED
|
@@ -12,7 +12,7 @@ import { saveSession, listSessions, loadSession } from './sessions.js';
|
|
|
12
12
|
import * as boost from './boost.js';
|
|
13
13
|
import { spawnSync } from 'node:child_process';
|
|
14
14
|
import { mcpConfigured } from './mcp.js';
|
|
15
|
-
import { listSkills, findSkill } from './skills.js';
|
|
15
|
+
import { listSkills, findSkill, devKeyword } from './skills.js';
|
|
16
16
|
|
|
17
17
|
function currentBranch(cwd) {
|
|
18
18
|
const r = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, encoding: 'utf8' });
|
|
@@ -477,17 +477,18 @@ export async function startSession(cfg, { fresh = false, resume = null } = {}) {
|
|
|
477
477
|
}
|
|
478
478
|
if (input === '/skills' || input.startsWith('/skills ')) {
|
|
479
479
|
const arg = input.slice(7).trim();
|
|
480
|
-
const unlocked = input.includes('0xyas051191') || (history.some(m => String(m.content).includes('0xyas051191')) && input.includes('0xyas051191'));
|
|
481
480
|
if (arg) {
|
|
482
481
|
const s = findSkill(arg, process.cwd(), input);
|
|
483
482
|
if (s) { say(box([bold(`Skill ${s.name}`) + dim(` (${s.scope}${s.gated ? ', gated' : ''})`), s.description, '', dim('Instructions:'), s.instructions.slice(0, 1_500)])); say(dim('Say "use ' + s.name + ' to ..." and the agent follows them.')); }
|
|
484
483
|
else say(red(`No skill named ${arg}.`));
|
|
485
484
|
return;
|
|
486
485
|
}
|
|
487
|
-
const
|
|
488
|
-
const
|
|
486
|
+
const machineUnlocked = devKeyword() !== '';
|
|
487
|
+
const all = listSkills(process.cwd(), machineUnlocked ? devKeyword() : input);
|
|
488
|
+
const gated = machineUnlocked ? all.filter(x => x.gated).length : -1;
|
|
489
489
|
say(all.length ? all.map(s => ` ${cyan(s.name)} ${dim('(' + s.scope + ')')} ${s.description}`).join('\n') : yellow('No skills installed.'));
|
|
490
|
-
if (gated) say(dim(` (+${gated} gated security skills
|
|
490
|
+
if (gated > 0) say(dim(` (+${gated} gated security skills unlocked on this machine)`));
|
|
491
|
+
else if (!machineUnlocked) say(dim(` gated security skills are locked on this machine - run ${bold('ineed unlock')} to enable`));
|
|
491
492
|
return;
|
|
492
493
|
}
|
|
493
494
|
if (input === '/new') {
|
package/src/skills.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
// Frontmatter: name, description, tools (optional restriction), instructions body below.
|
|
4
4
|
//
|
|
5
5
|
// GATED SKILLS: SKILL.md files whose frontmatter carries "gated: true" stay invisible
|
|
6
|
-
// unless the
|
|
7
|
-
//
|
|
6
|
+
// unless the objective text contains the developer keyword. The keyword itself lives
|
|
7
|
+
// OUTSIDE this repository: in the INEED_DEV_KEYWORD environment variable or in
|
|
8
|
+
// ~/.ineedcodes/keyword (chmod 600). A fresh install cannot unlock gated skills.
|
|
8
9
|
|
|
9
10
|
import * as fs from 'node:fs';
|
|
10
11
|
import * as path from 'node:path';
|
|
@@ -13,7 +14,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
14
|
import * as url from 'node:url';
|
|
14
15
|
const PKG_SKILLS = path.join(path.dirname(url.fileURLToPath(import.meta.url)), '..', 'skills');
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
const KEYWORD_FILE = path.join(CONFIG_DIR, 'keyword');
|
|
18
|
+
|
|
19
|
+
export function devKeyword() {
|
|
20
|
+
if (process.env.INEED_DEV_KEYWORD) return String(process.env.INEED_DEV_KEYWORD).trim();
|
|
21
|
+
try { return fs.readFileSync(KEYWORD_FILE, 'utf8').trim(); } catch { return ''; }
|
|
22
|
+
}
|
|
17
23
|
|
|
18
24
|
const BUILTIN = [
|
|
19
25
|
{
|
|
@@ -28,9 +34,23 @@ function parseFrontmatter(raw) {
|
|
|
28
34
|
const m = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
29
35
|
if (!m) return null;
|
|
30
36
|
const meta = {};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
const lines = m[1].split('\n');
|
|
38
|
+
for (let i = 0; i < lines.length; i++) {
|
|
39
|
+
const kv = lines[i].match(/^(\w+):\s*(.*)$/);
|
|
40
|
+
if (!kv) continue;
|
|
41
|
+
// folded scalar (>-, >): join the indented continuation lines into one paragraph
|
|
42
|
+
if (/^(description|summary)$/.test(kv[1]) && ['', '>-', '>', '|', '|-'].includes(kv[2])) {
|
|
43
|
+
const parts = [];
|
|
44
|
+
let j = i + 1;
|
|
45
|
+
for (; j < lines.length; j++) {
|
|
46
|
+
if (/^\S/.test(lines[j])) break;
|
|
47
|
+
parts.push(lines[j].trim());
|
|
48
|
+
}
|
|
49
|
+
meta[kv[1]] = parts.join(' ').trim();
|
|
50
|
+
i = j - 1;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
meta[kv[1]] = kv[2].trim();
|
|
34
54
|
}
|
|
35
55
|
if (!meta.name) return null;
|
|
36
56
|
return {
|
|
@@ -56,8 +76,9 @@ function loadDir(dir, scope, out, forceGated = false) {
|
|
|
56
76
|
}
|
|
57
77
|
|
|
58
78
|
function isUnlocked(objective) {
|
|
59
|
-
|
|
60
|
-
|
|
79
|
+
const kw = devKeyword();
|
|
80
|
+
if (!kw || !objective) return false;
|
|
81
|
+
return String(objective).includes(kw);
|
|
61
82
|
}
|
|
62
83
|
|
|
63
84
|
export function listSkills(cwd, objective = '') {
|
package/src/ui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ui.js: terminal helpers. No dependencies, respects NO_COLOR and non-TTY.
|
|
2
2
|
|
|
3
|
-
export const VERSION = '1.7.
|
|
3
|
+
export const VERSION = '1.7.2';
|
|
4
4
|
|
|
5
5
|
const USE_COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
6
6
|
const wrap = (code, t) => USE_COLOR ? `\x1b[${code}m${t}\x1b[0m` : String(t);
|