@devkitvault/recall 1.2.0 → 1.2.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.
@@ -4,15 +4,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.askCommand = void 0;
7
+ exports.printSuggestion = printSuggestion;
8
+ exports.printAskError = printAskError;
9
+ exports.saveSuggestion = saveSuggestion;
10
+ exports.resolveAsk = resolveAsk;
7
11
  exports.runAsk = runAsk;
8
12
  const chalk_1 = __importDefault(require("chalk"));
9
13
  const commander_1 = require("commander");
10
14
  const inquirer_1 = __importDefault(require("inquirer"));
11
- const ora_1 = __importDefault(require("ora"));
12
15
  const api_1 = require("../lib/api");
13
16
  const auth_1 = require("../lib/auth");
14
17
  const events_1 = require("../lib/events");
18
+ const project_1 = require("../lib/project");
15
19
  const runtime_1 = require("../lib/runtime");
20
+ const spinner_1 = require("../lib/spinner");
16
21
  function riskColor(risk) {
17
22
  if (risk === 'DANGEROUS')
18
23
  return chalk_1.default.red(risk);
@@ -20,76 +25,107 @@ function riskColor(risk) {
20
25
  return chalk_1.default.yellow(risk);
21
26
  return chalk_1.default.green(risk);
22
27
  }
23
- async function runAsk(query) {
24
- const token = await (0, auth_1.requireAuth)();
28
+ function printSuggestion(result) {
29
+ console.log();
30
+ for (const line of result.command.split('\n')) {
31
+ console.log(` ${chalk_1.default.white(line)}`);
32
+ }
33
+ if (result.explanation) {
34
+ console.log(` ${chalk_1.default.dim(result.explanation)}`);
35
+ }
36
+ console.log();
37
+ console.log(` ${chalk_1.default.dim('shell:')} ${result.shell} ${chalk_1.default.dim('risk:')} ${riskColor(result.risk)} ${chalk_1.default.dim('source:')} ${result.source}`);
38
+ if (result.source === 'vault') {
39
+ const playbook = result.explanation?.includes('playbooks');
40
+ console.log(chalk_1.default.dim(playbook ? ' from your playbooks' : ' from your vault'));
41
+ }
42
+ if (result.source === 'project') {
43
+ console.log(chalk_1.default.dim(' from this repo'));
44
+ }
45
+ if (result.remaining !== null) {
46
+ console.log(` ${chalk_1.default.dim(`ai left this month: ${result.remaining}`)}`);
47
+ }
48
+ if (result.alternatives?.length) {
49
+ console.log();
50
+ for (const alt of result.alternatives) {
51
+ console.log(` ${chalk_1.default.dim('alt')} ${alt}`);
52
+ }
53
+ }
54
+ console.log();
55
+ console.log(chalk_1.default.dim(' Ask mode does not run commands. Copy or save this yourself.'));
56
+ console.log();
57
+ }
58
+ function printAskError(err) {
59
+ if (err instanceof api_1.ApiError && err.status === 403 && err.body?.upgrade) {
60
+ console.log(chalk_1.default.yellow(`\n ${err.message}`));
61
+ console.log(chalk_1.default.dim(` Upgrade at ${chalk_1.default.white(err.body.upgrade)}\n`));
62
+ return;
63
+ }
64
+ if (err instanceof api_1.ApiError) {
65
+ console.error(chalk_1.default.red(`\n ${err.message}\n`));
66
+ return;
67
+ }
68
+ console.error(chalk_1.default.red('\n Failed to resolve that request.\n'));
69
+ }
70
+ async function saveSuggestion(token, command, name, source, risk) {
71
+ await api_1.ApiClient.post('/commands', {
72
+ command,
73
+ name: name?.trim() || undefined,
74
+ }, token);
75
+ await (0, events_1.track)('ai_accepted', { source, risk, saved: true });
76
+ console.log(chalk_1.default.green('\n Saved.\n'));
77
+ }
78
+ async function resolveAsk(opts) {
25
79
  const runtime = (0, runtime_1.detectRuntime)();
26
- const spinner = (0, ora_1.default)('Looking up...').start();
80
+ const spinner = (0, spinner_1.delayedSpinner)('Looking up...');
27
81
  try {
28
- const result = await api_1.ApiClient.post('/ai/resolve', {
29
- query,
82
+ const body = {
83
+ query: opts.query,
30
84
  os: runtime.os,
31
85
  shell: runtime.shell,
86
+ projectMeta: (0, project_1.detectProject)(),
32
87
  mode: 'ask',
33
- }, token);
88
+ };
89
+ if (opts.turns?.length)
90
+ body.turns = opts.turns;
91
+ const result = await api_1.ApiClient.post('/ai/resolve', body, opts.token);
34
92
  spinner.stop();
35
- console.log();
36
- console.log(` ${chalk_1.default.white(result.command)}`);
37
- if (result.explanation) {
38
- console.log(` ${chalk_1.default.dim(result.explanation)}`);
39
- }
40
- console.log();
41
- console.log(` ${chalk_1.default.dim('shell:')} ${result.shell} ${chalk_1.default.dim('risk:')} ${riskColor(result.risk)} ${chalk_1.default.dim('source:')} ${result.source}`);
42
- if (result.source === 'vault') {
43
- console.log(chalk_1.default.dim(' from your vault'));
44
- }
45
- if (result.remaining !== null) {
46
- console.log(` ${chalk_1.default.dim(`ai left this month: ${result.remaining}`)}`);
47
- }
48
- if (result.alternatives?.length) {
49
- console.log();
50
- for (const alt of result.alternatives) {
51
- console.log(` ${chalk_1.default.dim('alt')} ${alt}`);
52
- }
53
- }
54
- console.log();
55
- console.log(chalk_1.default.dim(' Ask mode does not run commands. Copy or save this yourself.'));
56
- console.log();
57
- if (result.source === 'vault') {
58
- await (0, events_1.track)('ai_accepted', { source: result.source, risk: result.risk, saved: false });
59
- return;
60
- }
61
- const { save } = await inquirer_1.default.prompt([{
62
- type: 'confirm',
63
- name: 'save',
64
- message: 'Save this command?',
65
- default: false,
66
- }]);
67
- if (!save)
68
- return;
69
- const { name } = await inquirer_1.default.prompt([{
70
- type: 'input',
71
- name: 'name',
72
- message: 'Name (optional):',
73
- }]);
74
- await api_1.ApiClient.post('/commands', {
75
- command: result.command,
76
- name: name?.trim() || undefined,
77
- }, token);
78
- await (0, events_1.track)('ai_accepted', { source: result.source, risk: result.risk, saved: true });
79
- console.log(chalk_1.default.green('\n Saved.\n'));
93
+ printSuggestion(result);
94
+ return result;
80
95
  }
81
96
  catch (err) {
82
97
  spinner.stop();
83
- if (err instanceof api_1.ApiError && err.status === 403 && err.body?.upgrade) {
84
- console.log(chalk_1.default.yellow(`\n ${err.message}`));
85
- console.log(chalk_1.default.dim(` Upgrade at ${chalk_1.default.white(err.body.upgrade)}\n`));
86
- return;
87
- }
88
- if (err instanceof api_1.ApiError) {
89
- console.error(chalk_1.default.red(`\n ${err.message}\n`));
90
- return;
91
- }
92
- console.error(chalk_1.default.red('\n Failed to resolve that request.\n'));
98
+ printAskError(err);
99
+ return null;
100
+ }
101
+ }
102
+ async function runAsk(query) {
103
+ const token = await (0, auth_1.requireAuth)();
104
+ const result = await resolveAsk({ query, token });
105
+ if (!result)
106
+ return;
107
+ if (result.source === 'vault') {
108
+ await (0, events_1.track)('ai_accepted', { source: result.source, risk: result.risk, saved: false });
109
+ return;
110
+ }
111
+ const { save } = await inquirer_1.default.prompt([{
112
+ type: 'confirm',
113
+ name: 'save',
114
+ message: 'Save this command?',
115
+ default: false,
116
+ }]);
117
+ if (!save)
118
+ return;
119
+ const { name } = await inquirer_1.default.prompt([{
120
+ type: 'input',
121
+ name: 'name',
122
+ message: 'Name (optional):',
123
+ }]);
124
+ try {
125
+ await saveSuggestion(token, result.command, name, result.source, result.risk);
126
+ }
127
+ catch (err) {
128
+ printAskError(err);
93
129
  }
94
130
  }
95
131
  exports.askCommand = new commander_1.Command('ask')
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.playbookCommand = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const child_process_1 = require("child_process");
9
+ const cli_table3_1 = __importDefault(require("cli-table3"));
10
+ const commander_1 = require("commander");
11
+ const ora_1 = __importDefault(require("ora"));
12
+ const api_1 = require("../lib/api");
13
+ const auth_1 = require("../lib/auth");
14
+ const playbookSave = new commander_1.Command('save')
15
+ .argument('<name>', 'Playbook name')
16
+ .argument('<steps...>', 'Ordered commands to run')
17
+ .description('Save an ordered workflow')
18
+ .action(async (name, steps) => {
19
+ const token = await (0, auth_1.requireAuth)();
20
+ const cleaned = steps.map((s) => s.trim()).filter(Boolean);
21
+ if (!cleaned.length) {
22
+ console.log(chalk_1.default.yellow('\n At least one step is required.\n'));
23
+ return;
24
+ }
25
+ const spinner = (0, ora_1.default)('Saving playbook...').start();
26
+ try {
27
+ await api_1.ApiClient.post('/playbooks', { name, steps: cleaned }, token);
28
+ spinner.succeed(chalk_1.default.green(`Playbook "${name}" saved`));
29
+ console.log(chalk_1.default.dim(`\n ${cleaned.length} step${cleaned.length === 1 ? '' : 's'}. Ask never runs them.\n`));
30
+ }
31
+ catch (err) {
32
+ const message = err instanceof Error ? err.message : 'Failed to save playbook';
33
+ spinner.fail(chalk_1.default.red(message));
34
+ }
35
+ });
36
+ const playbookList = new commander_1.Command('list')
37
+ .description('List your playbooks')
38
+ .action(async () => {
39
+ const token = await (0, auth_1.requireAuth)();
40
+ const spinner = (0, ora_1.default)('Fetching playbooks...').start();
41
+ try {
42
+ const { playbooks } = await api_1.ApiClient.get('/playbooks', token);
43
+ spinner.stop();
44
+ if (!playbooks.length) {
45
+ console.log(chalk_1.default.dim('\n No playbooks yet.'));
46
+ console.log(chalk_1.default.dim(' Run: recall playbook save deploy "docker compose up -d" "pnpm migrate"\n'));
47
+ return;
48
+ }
49
+ const table = new cli_table3_1.default({
50
+ head: [chalk_1.default.cyan('Name'), chalk_1.default.cyan('Steps')],
51
+ style: { head: [], border: ['grey'] },
52
+ colWidths: [24, 48],
53
+ wordWrap: true,
54
+ });
55
+ for (const book of playbooks) {
56
+ table.push([
57
+ chalk_1.default.white(book.name),
58
+ book.steps.join(' → '),
59
+ ]);
60
+ }
61
+ console.log(table.toString());
62
+ }
63
+ catch {
64
+ spinner.fail(chalk_1.default.red('Failed to fetch playbooks'));
65
+ }
66
+ });
67
+ const playbookShow = new commander_1.Command('show')
68
+ .argument('<name>', 'Playbook name')
69
+ .description('Show playbook steps')
70
+ .action(async (name) => {
71
+ const token = await (0, auth_1.requireAuth)();
72
+ const spinner = (0, ora_1.default)('Fetching...').start();
73
+ try {
74
+ const book = await api_1.ApiClient.get(`/playbooks/by-name/${encodeURIComponent(name)}`, token);
75
+ spinner.stop();
76
+ console.log(chalk_1.default.bold(`\n ${book.name}\n`));
77
+ book.steps.forEach((step, i) => {
78
+ console.log(` ${chalk_1.default.dim(String(i + 1).padStart(2, '0'))} ${step}`);
79
+ });
80
+ console.log();
81
+ }
82
+ catch {
83
+ spinner.fail(chalk_1.default.red(`Playbook "${name}" not found`));
84
+ }
85
+ });
86
+ const playbookRun = new commander_1.Command('run')
87
+ .argument('<name>', 'Playbook name')
88
+ .option('-d, --dry-run', 'Print steps without running')
89
+ .description('Run playbook steps in order (this executes)')
90
+ .action(async (name, opts) => {
91
+ const token = await (0, auth_1.requireAuth)();
92
+ const spinner = (0, ora_1.default)('Fetching playbook...').start();
93
+ try {
94
+ const book = await api_1.ApiClient.get(`/playbooks/by-name/${encodeURIComponent(name)}`, token);
95
+ spinner.stop();
96
+ console.log(chalk_1.default.dim(`\n Playbook: ${chalk_1.default.white(book.name)}\n`));
97
+ if (opts.dryRun) {
98
+ book.steps.forEach((step, i) => {
99
+ console.log(` ${i + 1}. ${step}`);
100
+ });
101
+ console.log();
102
+ return;
103
+ }
104
+ await api_1.ApiClient.post(`/playbooks/${book.id}/run`, {}, token);
105
+ for (const [i, step] of book.steps.entries()) {
106
+ console.log(chalk_1.default.dim(` step ${i + 1}/${book.steps.length}`));
107
+ (0, child_process_1.execSync)(step, { stdio: 'inherit' });
108
+ }
109
+ console.log();
110
+ }
111
+ catch (err) {
112
+ spinner.stop();
113
+ const message = err instanceof Error ? err.message : `Playbook "${name}" failed`;
114
+ console.error(chalk_1.default.red(`\n ${message}\n`));
115
+ }
116
+ });
117
+ const playbookDelete = new commander_1.Command('delete')
118
+ .argument('<name>', 'Playbook name')
119
+ .description('Delete a playbook')
120
+ .action(async (name) => {
121
+ const token = await (0, auth_1.requireAuth)();
122
+ const spinner = (0, ora_1.default)('Looking up...').start();
123
+ try {
124
+ const book = await api_1.ApiClient.get(`/playbooks/by-name/${encodeURIComponent(name)}`, token);
125
+ spinner.text = 'Deleting...';
126
+ await api_1.ApiClient.delete(`/playbooks/${book.id}`, token);
127
+ spinner.succeed(chalk_1.default.green(`Deleted playbook "${name}"`));
128
+ }
129
+ catch {
130
+ spinner.fail(chalk_1.default.red(`Playbook "${name}" not found`));
131
+ }
132
+ });
133
+ exports.playbookCommand = new commander_1.Command('playbook')
134
+ .description('Ordered multi-step workflows')
135
+ .addCommand(playbookSave)
136
+ .addCommand(playbookList)
137
+ .addCommand(playbookShow)
138
+ .addCommand(playbookRun)
139
+ .addCommand(playbookDelete);
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runRepl = runRepl;
7
+ const node_readline_1 = __importDefault(require("node:readline"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const auth_1 = require("../lib/auth");
10
+ const events_1 = require("../lib/events");
11
+ const turns_1 = require("../lib/turns");
12
+ const ask_1 = require("./ask");
13
+ function printReplHelp() {
14
+ console.log(chalk_1.default.dim(' Ask prints a command. It does not run it.'));
15
+ console.log(chalk_1.default.dim(' /save [name] save the last suggestion'));
16
+ console.log(chalk_1.default.dim(' /clear forget this session'));
17
+ console.log(chalk_1.default.dim(' /exit leave'));
18
+ }
19
+ async function runRepl() {
20
+ const token = await (0, auth_1.requireAuth)();
21
+ await (0, events_1.track)('repl_started');
22
+ const rl = node_readline_1.default.createInterface({
23
+ input: process.stdin,
24
+ output: process.stdout,
25
+ prompt: 'Recall > ',
26
+ });
27
+ let turns = [];
28
+ let last = null;
29
+ let turnIndex = 0;
30
+ let busy = false;
31
+ let open = true;
32
+ console.log(chalk_1.default.dim(' Ask session. Prints only. /help for commands.'));
33
+ rl.prompt();
34
+ async function handleLine(raw) {
35
+ const line = raw.trim();
36
+ if (!line)
37
+ return;
38
+ if ((0, turns_1.isExitCommand)(line)) {
39
+ rl.close();
40
+ return;
41
+ }
42
+ if (line === '/help') {
43
+ printReplHelp();
44
+ return;
45
+ }
46
+ if (line === '/clear') {
47
+ turns = [];
48
+ last = null;
49
+ console.log(chalk_1.default.dim(' session cleared'));
50
+ return;
51
+ }
52
+ if (line === '/save' || line.startsWith('/save ')) {
53
+ if (!last) {
54
+ console.log(chalk_1.default.dim(' nothing to save'));
55
+ return;
56
+ }
57
+ const name = line === '/save' ? undefined : line.slice(6).trim();
58
+ try {
59
+ await (0, ask_1.saveSuggestion)(token, last.command, name, last.source, last.risk);
60
+ }
61
+ catch {
62
+ console.error(chalk_1.default.red('\n Failed to save.\n'));
63
+ }
64
+ return;
65
+ }
66
+ if (line.startsWith('/')) {
67
+ console.log(chalk_1.default.dim(' unknown command. /help'));
68
+ return;
69
+ }
70
+ const result = await (0, ask_1.resolveAsk)({
71
+ query: line,
72
+ token,
73
+ turns: (0, turns_1.trimTurns)(turns),
74
+ });
75
+ if (!result)
76
+ return;
77
+ last = result;
78
+ turns = (0, turns_1.trimTurns)([...turns, { query: line, command: result.command }]);
79
+ turnIndex += 1;
80
+ await (0, events_1.track)('repl_turns', { source: result.source, n: turnIndex });
81
+ if (result.source === 'vault') {
82
+ await (0, events_1.track)('ai_accepted', { source: result.source, risk: result.risk, saved: false });
83
+ }
84
+ }
85
+ rl.on('line', (raw) => {
86
+ if (busy)
87
+ return;
88
+ busy = true;
89
+ rl.pause();
90
+ void handleLine(raw)
91
+ .catch(() => {
92
+ console.error(chalk_1.default.red('\n Failed to resolve that request.\n'));
93
+ })
94
+ .finally(() => {
95
+ busy = false;
96
+ if (open) {
97
+ rl.resume();
98
+ rl.prompt();
99
+ }
100
+ });
101
+ });
102
+ rl.on('SIGINT', () => {
103
+ rl.close();
104
+ });
105
+ await new Promise((resolve) => {
106
+ rl.on('close', () => {
107
+ open = false;
108
+ console.log();
109
+ resolve();
110
+ });
111
+ });
112
+ }
@@ -17,6 +17,7 @@ const PLANS = {
17
17
  price: '$0/month',
18
18
  features: [
19
19
  '20 AI asks / month',
20
+ '3 playbooks',
20
21
  '100 command saves',
21
22
  'Search and filter',
22
23
  'Export as JSON or shell script',
@@ -28,6 +29,7 @@ const PLANS = {
28
29
  price: '$6/month',
29
30
  features: [
30
31
  '500 AI asks / month',
32
+ 'Unlimited playbooks',
31
33
  'Unlimited commands',
32
34
  'Command groups',
33
35
  'Share commands via link',
package/dist/index.js CHANGED
@@ -24,6 +24,8 @@ const import_1 = require("./commands/import");
24
24
  const list_1 = require("./commands/list");
25
25
  const org_1 = require("./commands/org");
26
26
  const pin_1 = require("./commands/pin");
27
+ const playbook_1 = require("./commands/playbook");
28
+ const repl_1 = require("./commands/repl");
27
29
  const run_1 = require("./commands/run");
28
30
  const save_1 = require("./commands/save");
29
31
  const search_1 = require("./commands/search");
@@ -42,7 +44,7 @@ const KNOWN = new Set([
42
44
  'export', 'import', 'whoami', 'upgrade', 'sync', 'group', 'org',
43
45
  'config', 'pin', 'alias', 'history', 'share', 'snippet', 'env',
44
46
  'audit', 'approve', 'template', 'doctor', 'completion', 'feedback',
45
- 'support', 'ask', 'help',
47
+ 'support', 'ask', 'playbook', 'help',
46
48
  ]);
47
49
  const rawArgs = process.argv.slice(2);
48
50
  if (rawArgs.length > 0
@@ -80,12 +82,18 @@ program.addCommand(env_1.envCommand);
80
82
  program.addCommand(audit_1.auditCommand);
81
83
  program.addCommand(approve_1.approveCommand);
82
84
  program.addCommand(template_1.templateCommand);
85
+ program.addCommand(playbook_1.playbookCommand);
83
86
  program.addCommand(doctor_1.doctorCommand);
84
87
  program.addCommand(completion_1.completionCommand);
85
88
  program.addCommand(feedback_1.feedbackCommand);
86
89
  program.addCommand(support_1.supportCommand);
87
90
  async function main() {
88
91
  void (0, events_1.trackInstalledOnce)();
92
+ const raw = process.argv.slice(2);
93
+ if (raw.length === 0) {
94
+ await (0, repl_1.runRepl)();
95
+ return;
96
+ }
89
97
  await program.parseAsync();
90
98
  }
91
99
  main().catch((err) => {
@@ -11,7 +11,7 @@ const os_1 = __importDefault(require("os"));
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.recall');
13
13
  const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
14
- exports.APP_VERSION = "2.5.0";
14
+ exports.APP_VERSION = "2.6.0";
15
15
  exports.ENVIRONMENTS = {
16
16
  production: 'https://api.devkitvault.com',
17
17
  local: 'http://127.0.0.1:3001',
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectProject = detectProject;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const MAX_WALK = 16;
10
+ const MAX_PACKAGE_JSON_BYTES = 200_000;
11
+ const MAX_SCRIPTS = 40;
12
+ const SECRETISH = /key|secret|token|password|credential/i;
13
+ const LOCKFILES = [
14
+ { file: 'pnpm-lock.yaml', manager: 'pnpm' },
15
+ { file: 'yarn.lock', manager: 'yarn' },
16
+ { file: 'bun.lock', manager: 'bun' },
17
+ { file: 'bun.lockb', manager: 'bun' },
18
+ { file: 'package-lock.json', manager: 'npm' },
19
+ ];
20
+ function exists(dir, name) {
21
+ try {
22
+ return fs_1.default.existsSync(path_1.default.join(dir, name));
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ function isBlockedDir(dir) {
29
+ const parts = dir.split(path_1.default.sep);
30
+ return parts.includes('node_modules') || parts.includes('.git');
31
+ }
32
+ function walkUp(start) {
33
+ const dirs = [];
34
+ let dir = path_1.default.resolve(start);
35
+ const root = path_1.default.parse(dir).root;
36
+ for (let i = 0; i < MAX_WALK; i++) {
37
+ dirs.push(dir);
38
+ if (exists(dir, '.git'))
39
+ break;
40
+ if (dir === root)
41
+ break;
42
+ const parent = path_1.default.dirname(dir);
43
+ if (parent === dir)
44
+ break;
45
+ dir = parent;
46
+ }
47
+ return dirs;
48
+ }
49
+ function readScriptNames(packageJsonPath) {
50
+ try {
51
+ const stat = fs_1.default.statSync(packageJsonPath);
52
+ if (!stat.isFile() || stat.size > MAX_PACKAGE_JSON_BYTES)
53
+ return [];
54
+ const raw = fs_1.default.readFileSync(packageJsonPath, 'utf8');
55
+ const json = JSON.parse(raw);
56
+ if (!json || typeof json !== 'object' || Array.isArray(json))
57
+ return [];
58
+ const scripts = json.scripts;
59
+ if (!scripts || typeof scripts !== 'object' || Array.isArray(scripts))
60
+ return [];
61
+ const names = [];
62
+ for (const key of Object.keys(scripts)) {
63
+ if (!key || key.length > 64)
64
+ continue;
65
+ names.push(SECRETISH.test(key) ? '[redacted]' : key);
66
+ if (names.length >= MAX_SCRIPTS)
67
+ break;
68
+ }
69
+ return [...new Set(names)];
70
+ }
71
+ catch {
72
+ return [];
73
+ }
74
+ }
75
+ function markersIn(dir) {
76
+ const found = [];
77
+ if (exists(dir, '.git'))
78
+ found.push('git');
79
+ if (exists(dir, 'package.json'))
80
+ found.push('node');
81
+ if (exists(dir, 'requirements.txt') || exists(dir, 'pyproject.toml'))
82
+ found.push('python');
83
+ if (exists(dir, 'manage.py'))
84
+ found.push('django');
85
+ if (exists(dir, 'go.mod'))
86
+ found.push('go');
87
+ if (exists(dir, 'Cargo.toml'))
88
+ found.push('rust');
89
+ if (exists(dir, 'Dockerfile'))
90
+ found.push('docker');
91
+ if (exists(dir, 'compose.yml')
92
+ || exists(dir, 'compose.yaml')
93
+ || exists(dir, 'docker-compose.yml')
94
+ || exists(dir, 'docker-compose.yaml')) {
95
+ found.push('compose');
96
+ }
97
+ if (exists(dir, 'Makefile') || exists(dir, 'makefile'))
98
+ found.push('makefile');
99
+ return found;
100
+ }
101
+ function detectProject(cwd = process.cwd()) {
102
+ let start = cwd;
103
+ try {
104
+ start = fs_1.default.realpathSync(cwd);
105
+ }
106
+ catch {
107
+ start = path_1.default.resolve(cwd);
108
+ }
109
+ if (isBlockedDir(start))
110
+ return undefined;
111
+ const dirs = walkUp(start).filter((dir) => !isBlockedDir(dir));
112
+ if (!dirs.length)
113
+ return undefined;
114
+ const has = new Set();
115
+ for (const dir of dirs) {
116
+ for (const marker of markersIn(dir))
117
+ has.add(marker);
118
+ }
119
+ const pkgDir = dirs.find((dir) => exists(dir, 'package.json'));
120
+ const scripts = pkgDir ? readScriptNames(path_1.default.join(pkgDir, 'package.json')) : [];
121
+ let manager;
122
+ for (const dir of dirs) {
123
+ const hit = LOCKFILES.find((lock) => exists(dir, lock.file));
124
+ if (hit) {
125
+ manager = hit.manager;
126
+ break;
127
+ }
128
+ }
129
+ if (!manager && scripts.length)
130
+ manager = 'npm';
131
+ if (!has.size && !scripts.length && !manager)
132
+ return undefined;
133
+ const meta = {};
134
+ if (manager)
135
+ meta.manager = manager;
136
+ if (scripts.length)
137
+ meta.scripts = scripts;
138
+ if (has.size)
139
+ meta.has = [...has];
140
+ return meta;
141
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.delayedSpinner = delayedSpinner;
7
+ const ora_1 = __importDefault(require("ora"));
8
+ const DELAY_MS = 120;
9
+ function delayedSpinner(label) {
10
+ let spinner = null;
11
+ const timer = setTimeout(() => {
12
+ spinner = (0, ora_1.default)(label).start();
13
+ }, DELAY_MS);
14
+ return {
15
+ stop() {
16
+ clearTimeout(timer);
17
+ spinner?.stop();
18
+ },
19
+ };
20
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.trimTurns = trimTurns;
4
+ exports.isExitCommand = isExitCommand;
5
+ const MAX_TURNS = 4;
6
+ const MAX_FIELD = 200;
7
+ function trimTurns(turns) {
8
+ const out = [];
9
+ for (const turn of turns) {
10
+ const query = turn.query.trim().slice(0, MAX_FIELD);
11
+ const command = turn.command.trim().slice(0, MAX_FIELD);
12
+ if (!query || !command)
13
+ continue;
14
+ out.push({ query, command });
15
+ }
16
+ return out.slice(-MAX_TURNS);
17
+ }
18
+ function isExitCommand(line) {
19
+ const t = line.trim().toLowerCase();
20
+ return t === 'exit' || t === 'quit' || t === '/exit' || t === '/quit';
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devkitvault/recall",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "recall CLI — AI terminal copilot with a personal command vault",
5
5
  "keywords": [
6
6
  "cli",