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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ineedcodes",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "description": "Your terminal, now autonomous. Just say what you want, ineed does the rest.",
5
5
  "license": "MIT",
6
6
  "type": "module",
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 all = listSkills(process.cwd(), input);
488
- const gated = all.filter(s => s.gated).length;
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: mention 0xyas051191 in a task to unlock)`));
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 user includes the developer keyword 0xyas051191 in their message. The keyword
7
- // is checked against the raw objective text, so it never ships enabled by accident.
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
- export const DEV_KEYWORD = '0xyas051191';
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
- for (const line of m[1].split('\n')) {
32
- const kv = line.match(/^(\w+):\s*(.+)$/);
33
- if (kv) meta[kv[1]] = kv[2].trim();
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
- if (!objective) return false;
60
- return String(objective).includes(DEV_KEYWORD);
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.0';
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);