kushi-agents 5.8.0 → 5.8.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/bin/cli.mjs +4 -0
- package/package.json +1 -1
- package/src/main.mjs +10 -0
- package/src/multi-host.mjs +1 -0
- package/src/quickstart-m365.mjs +129 -0
package/bin/cli.mjs
CHANGED
|
@@ -206,6 +206,8 @@ if (args.includes('--help') || args.includes('-h')) {
|
|
|
206
206
|
--yes, -y Skip the project-root check
|
|
207
207
|
--no-settings Skip .vscode/settings.json update (vscode workspace target only)
|
|
208
208
|
--no-instructions Skip .github/copilot-instructions.md merge (vscode workspace target only)
|
|
209
|
+
--no-prompt Skip the post-install m365-auth quickstart (3 fields that
|
|
210
|
+
make 'kushi discover' fast). Also disabled by KUSHI_NO_PROMPT=1.
|
|
209
211
|
|
|
210
212
|
WorkIQ (REQUIRED — Kushi cannot pull evidence without it):
|
|
211
213
|
--with-workiq Auto-install WorkIQ via winget (Windows) / brew (macOS)
|
|
@@ -281,6 +283,7 @@ if (wantsVscode || wantsAllHosts || wantsUninstall) {
|
|
|
281
283
|
uninstall: wantsUninstall,
|
|
282
284
|
profile: getFlag('--profile'),
|
|
283
285
|
includeWorkspace: !args.includes('--no-workspace'),
|
|
286
|
+
noPrompt: args.includes('--no-prompt'),
|
|
284
287
|
}).catch((err) => {
|
|
285
288
|
console.error(`\n ${err.message}\n`);
|
|
286
289
|
process.exit(1);
|
|
@@ -300,6 +303,7 @@ if (wantsVscode || wantsAllHosts || wantsUninstall) {
|
|
|
300
303
|
yes: args.includes('--yes') || args.includes('-y'),
|
|
301
304
|
noSettings: args.includes('--no-settings'),
|
|
302
305
|
noInstructions: args.includes('--no-instructions'),
|
|
306
|
+
noPrompt: args.includes('--no-prompt'),
|
|
303
307
|
target,
|
|
304
308
|
profile: getFlag('--profile'),
|
|
305
309
|
withWorkiq: args.includes('--with-workiq'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kushi-agents",
|
|
3
|
-
"version": "5.8.
|
|
3
|
+
"version": "5.8.1",
|
|
4
4
|
"description": "Install Kushi — multi-source project evidence agent with Comprehensive Structured Capture (CSC) into weekly-only files across Email, Teams, OneNote, Loop, SharePoint, Meetings, CRM, ADO. Meetings retain a sibling verbatim/ audit folder. WorkIQ-only for M365 sources (Graph / m365_* FORBIDDEN as fallbacks; user-paste is first-class). Host-agnostic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/main.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { installRunnerDeps } from './install-runner-deps.mjs';
|
|
|
17
17
|
import { mergeSettings } from './settings.mjs';
|
|
18
18
|
import { mergeCopilotInstructions } from './copilot-instructions.mjs';
|
|
19
19
|
import { seedConfig } from './seed-config.mjs';
|
|
20
|
+
import { runM365Quickstart } from './quickstart-m365.mjs';
|
|
20
21
|
import { checkWorkIQ, pingWorkIQ, tryInstallWorkIQ } from './check-workiq.mjs';
|
|
21
22
|
import {
|
|
22
23
|
resolveProfile,
|
|
@@ -115,6 +116,15 @@ async function installVscode(options, resolved, version) {
|
|
|
115
116
|
const seedRes = seedConfig(PKG_ROOT, fullDest);
|
|
116
117
|
printSeedReport(seedRes, `${dest}/`);
|
|
117
118
|
|
|
119
|
+
// v5.8.1: prompt for the 3 fields that most affect discover speed.
|
|
120
|
+
// Skips silently when --no-prompt is set, when not running in a TTY, or
|
|
121
|
+
// when all 3 fields are already populated (re-install case).
|
|
122
|
+
const noPrompt = options.noPrompt || process.env.KUSHI_NO_PROMPT === '1';
|
|
123
|
+
const qs = await runM365Quickstart({ destRoot: fullDest, noPrompt });
|
|
124
|
+
if (qs.ran === false && qs.reason && !['already-populated', 'no-prompt-flag'].includes(qs.reason)) {
|
|
125
|
+
console.log(` Quickstart skipped: ${qs.reason}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
118
128
|
if (!options.noSettings) {
|
|
119
129
|
const { created, keysAdded, keysUnchanged } = mergeSettings(
|
|
120
130
|
projectRoot,
|
package/src/multi-host.mjs
CHANGED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// src/quickstart-m365.mjs
|
|
2
|
+
// Post-install interactive prompt for the 3 fields that most affect kushi
|
|
3
|
+
// `discover` wall time (folders, dateFloor via lookback-days, OneNote notebook).
|
|
4
|
+
// Runs at the end of `installWorkspace()` when the destination just got a fresh
|
|
5
|
+
// m365-auth.json seed. Skips silently when not a TTY or when --no-prompt is set.
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
const SENTINELS = [/__FILL_ME_IN__/, /^<[A-Za-z][^>]*>$/, /^<auto>$/];
|
|
11
|
+
function looksLikeSentinel(s) {
|
|
12
|
+
if (!s) return true;
|
|
13
|
+
return SENTINELS.some((re) => re.test(s));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isoDateNDaysAgo(n) {
|
|
17
|
+
const d = new Date();
|
|
18
|
+
d.setDate(d.getDate() - n);
|
|
19
|
+
return d.toISOString().slice(0, 10);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Run the 3-question quickstart. Returns a small report object:
|
|
24
|
+
* { ran: bool, reason?: string, file?: string, fields?: string[] }
|
|
25
|
+
*
|
|
26
|
+
* @param {object} opts
|
|
27
|
+
* @param {string} opts.destRoot - absolute path to .kushi/ (the workspace install root)
|
|
28
|
+
* @param {boolean} [opts.force] - overwrite existing non-empty values
|
|
29
|
+
* @param {boolean} [opts.noPrompt]- skip prompting (CI / scripted installs)
|
|
30
|
+
*/
|
|
31
|
+
export async function runM365Quickstart({ destRoot, force = false, noPrompt = false } = {}) {
|
|
32
|
+
if (noPrompt) return { ran: false, reason: 'no-prompt-flag' };
|
|
33
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY) return { ran: false, reason: 'not-a-tty' };
|
|
34
|
+
|
|
35
|
+
const target = path.join(destRoot, 'config', 'user', 'm365-auth.json');
|
|
36
|
+
if (!fs.existsSync(target)) return { ran: false, reason: 'm365-auth-not-found' };
|
|
37
|
+
|
|
38
|
+
let parsed;
|
|
39
|
+
try { parsed = JSON.parse(fs.readFileSync(target, 'utf8')); }
|
|
40
|
+
catch (e) { return { ran: false, reason: `parse-error: ${e.message}` }; }
|
|
41
|
+
parsed.m365Auth ??= {};
|
|
42
|
+
const m = parsed.m365Auth;
|
|
43
|
+
m.emailContext ??= {};
|
|
44
|
+
m.teamsChatContext ??= {};
|
|
45
|
+
m.calendarContext ??= {};
|
|
46
|
+
m.oneNote ??= {};
|
|
47
|
+
|
|
48
|
+
// Skip silently if all 3 fields are already populated and --force not passed.
|
|
49
|
+
const foldersSet = Array.isArray(m.emailContext.folders) && m.emailContext.folders.length > 0;
|
|
50
|
+
const floorSet = !looksLikeSentinel(m.emailContext.dateFloor);
|
|
51
|
+
const notebookSet = !looksLikeSentinel(m.oneNote.defaultNotebookName);
|
|
52
|
+
if (foldersSet && floorSet && notebookSet && !force) {
|
|
53
|
+
return { ran: false, reason: 'already-populated' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const readline = await import('node:readline/promises');
|
|
57
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr, terminal: true });
|
|
58
|
+
const ask = async (q, def) => {
|
|
59
|
+
const ans = (await rl.question(`${q} ${def != null && def !== '' ? `[${def}]` : ''} > `)).trim();
|
|
60
|
+
return ans === '' ? def : ans;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
process.stderr.write('\n ┌─ Quickstart: 3 questions that make `kushi discover` fast ──\n');
|
|
64
|
+
process.stderr.write(` │ Editing: ${target}\n`);
|
|
65
|
+
process.stderr.write(' │ Press Enter at any prompt to skip (you can always edit the file later).\n');
|
|
66
|
+
process.stderr.write(' └────────────────────────────────────────────────────────────\n\n');
|
|
67
|
+
|
|
68
|
+
const fields = [];
|
|
69
|
+
|
|
70
|
+
// 1. Email folders
|
|
71
|
+
process.stderr.write(' [1/3] Which Outlook mail folders contain your project mail?\n');
|
|
72
|
+
process.stderr.write(' Examples:\n');
|
|
73
|
+
process.stderr.write(' • "1. FDE, 99. FDE Not Active" (Microsoft FDE consultant)\n');
|
|
74
|
+
process.stderr.write(' • "Inbox, Archive, Projects" (generic)\n');
|
|
75
|
+
process.stderr.write(' • "Inbox" (everything in inbox)\n');
|
|
76
|
+
const curFolders = Array.isArray(m.emailContext.folders) ? m.emailContext.folders.join(', ') : '';
|
|
77
|
+
const foldersAns = await ask(' Comma-separated folder names', curFolders);
|
|
78
|
+
if (foldersAns && foldersAns !== curFolders) {
|
|
79
|
+
const arr = foldersAns.split(',').map(s => s.trim()).filter(Boolean);
|
|
80
|
+
m.emailContext.folders = arr;
|
|
81
|
+
if (m.emailContext.includeSubfolders == null) m.emailContext.includeSubfolders = true;
|
|
82
|
+
if (m.emailContext.enabled == null) m.emailContext.enabled = true;
|
|
83
|
+
fields.push(`emailContext.folders=[${arr.join(', ')}]`);
|
|
84
|
+
} else if (!foldersAns) {
|
|
85
|
+
process.stderr.write(' (skipped — discover will scan the whole mailbox, which can take minutes per source)\n');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 2. Look-back days
|
|
89
|
+
process.stderr.write('\n [2/3] How many days back should discover scan? (smaller = faster)\n');
|
|
90
|
+
process.stderr.write(' Recommended: 60 for active engagements, 90 for setup.\n');
|
|
91
|
+
const curFloor = m.emailContext.dateFloor;
|
|
92
|
+
const defaultDays = curFloor && /^\d{4}-\d{2}-\d{2}$/.test(curFloor)
|
|
93
|
+
? Math.max(1, Math.round((Date.now() - Date.parse(curFloor)) / 86400000))
|
|
94
|
+
: 60;
|
|
95
|
+
const daysAns = await ask(' Look-back days', defaultDays);
|
|
96
|
+
const days = Number(daysAns);
|
|
97
|
+
if (Number.isFinite(days) && days > 0 && days <= 3650) {
|
|
98
|
+
const floor = isoDateNDaysAgo(days);
|
|
99
|
+
m.emailContext.dateFloor = floor;
|
|
100
|
+
m.teamsChatContext.dateFloor = floor;
|
|
101
|
+
m.calendarContext.dateFloor = floor;
|
|
102
|
+
fields.push(`dateFloor=${floor} (${days} days)`);
|
|
103
|
+
} else {
|
|
104
|
+
process.stderr.write(` ⚠ "${daysAns}" is not a valid number 1–3650 — skipping dateFloor.\n`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 3. OneNote notebook
|
|
108
|
+
process.stderr.write('\n [3/3] Which OneNote notebook holds your project notes?\n');
|
|
109
|
+
process.stderr.write(' Default for Microsoft consultants: "ISE Work"\n');
|
|
110
|
+
const curNb = m.oneNote.defaultNotebookName || '';
|
|
111
|
+
const nbAns = await ask(' Notebook name', curNb || 'ISE Work');
|
|
112
|
+
if (nbAns && nbAns !== curNb) {
|
|
113
|
+
m.oneNote.defaultNotebookName = nbAns;
|
|
114
|
+
if (m.oneNote.enabled == null) m.oneNote.enabled = true;
|
|
115
|
+
fields.push(`oneNote.defaultNotebookName="${nbAns}"`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
rl.close();
|
|
119
|
+
|
|
120
|
+
if (fields.length === 0) {
|
|
121
|
+
process.stderr.write('\n ⓘ No changes — m365-auth.json left as seeded. Edit it later to enable bounded discover.\n\n');
|
|
122
|
+
return { ran: true, reason: 'no-changes', file: target, fields: [] };
|
|
123
|
+
}
|
|
124
|
+
fs.writeFileSync(target, JSON.stringify(parsed, null, 2) + '\n');
|
|
125
|
+
process.stderr.write(`\n ✓ Wrote ${fields.length} field(s) to ${target}:\n`);
|
|
126
|
+
for (const f of fields) process.stderr.write(` • ${f}\n`);
|
|
127
|
+
process.stderr.write('\n');
|
|
128
|
+
return { ran: true, file: target, fields };
|
|
129
|
+
}
|