kushi-agents 5.4.0 → 5.4.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/bin/cli.mjs +17 -2
- package/package.json +2 -2
- package/src/cli-no-args-tty.test.mjs +59 -0
- package/src/setup-wizard.mjs +39 -8
- package/src/setup-wizard.test.mjs +19 -1
package/bin/cli.mjs
CHANGED
|
@@ -5,8 +5,20 @@ import { runMultiHost } from '../src/multi-host.mjs';
|
|
|
5
5
|
|
|
6
6
|
const args = process.argv.slice(2);
|
|
7
7
|
|
|
8
|
-
// ── bare invocation (v5.4.0+)
|
|
9
|
-
|
|
8
|
+
// ── bare invocation (v5.4.0+) ────────────────────────────────────────────────
|
|
9
|
+
// v5.4.1: on an interactive TTY, auto-launch the setup wizard (matches the
|
|
10
|
+
// ergonomics of `npx create-*`). Non-TTY (CI, scripts, piped stdin) and the
|
|
11
|
+
// explicit KUSHI_SKIP_WELCOME=1 still print the welcome card and exit 0, so
|
|
12
|
+
// nothing is installed by side-effect.
|
|
13
|
+
if (args.length === 0) {
|
|
14
|
+
const forceWelcome = process.env.KUSHI_SKIP_WELCOME === '1';
|
|
15
|
+
const forceWizard = process.env.KUSHI_FORCE_WIZARD === '1';
|
|
16
|
+
const interactive = forceWizard || (process.stdin.isTTY && !forceWelcome);
|
|
17
|
+
if (interactive) {
|
|
18
|
+
const { runSetupWizard } = await import('../src/setup-wizard.mjs');
|
|
19
|
+
await runSetupWizard({ args: [] });
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
10
22
|
await printWelcome();
|
|
11
23
|
process.exit(0);
|
|
12
24
|
}
|
|
@@ -50,10 +62,13 @@ async function printWelcome() {
|
|
|
50
62
|
console.log(`
|
|
51
63
|
kushi v${version} — multi-source M365 project evidence agent
|
|
52
64
|
|
|
65
|
+
(non-interactive shell — nothing was installed)
|
|
66
|
+
|
|
53
67
|
First time? kushi doctor
|
|
54
68
|
Bootstrap a project: kushi setup <project>
|
|
55
69
|
Ask a question: kushi ask <project> "..."
|
|
56
70
|
Wizard install: npx kushi-agents --setup-wizard
|
|
71
|
+
Host install: npx kushi-agents --clawpilot | --vscode | --all-hosts
|
|
57
72
|
|
|
58
73
|
Docs: https://gim-home.github.io/kushi/
|
|
59
74
|
Skills: ${skillCount} installed in plugin/skills/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kushi-agents",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.2",
|
|
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": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"license": "MIT",
|
|
43
43
|
"scripts": {
|
|
44
|
-
"test": "node --test src/check-workiq.test.mjs src/seed-config.test.mjs src/sanitize-workiq-input.test.mjs src/detect-vertex-repo.test.mjs src/vertex-validate.test.mjs src/emit-vertex.e2e.test.mjs src/config-root-resolve.test.mjs src/forbidden-workiq-phrasings.test.mjs src/multi-host-install.test.mjs src/eval-aggregator.test.mjs src/eval-runner.test.mjs src/skill-creator.test.mjs src/skill-checker.test.mjs src/hooks-dispatcher.test.mjs src/parallel-refresh.test.mjs src/otel-emit.test.mjs src/teach.test.mjs src/schema-evolve.test.mjs src/global-wiki.test.mjs src/promote.test.mjs src/doctor.test.mjs src/setup-wizard.test.mjs src/cli-no-args.test.mjs",
|
|
44
|
+
"test": "node --test src/check-workiq.test.mjs src/seed-config.test.mjs src/sanitize-workiq-input.test.mjs src/detect-vertex-repo.test.mjs src/vertex-validate.test.mjs src/emit-vertex.e2e.test.mjs src/config-root-resolve.test.mjs src/forbidden-workiq-phrasings.test.mjs src/multi-host-install.test.mjs src/eval-aggregator.test.mjs src/eval-runner.test.mjs src/skill-creator.test.mjs src/skill-checker.test.mjs src/hooks-dispatcher.test.mjs src/parallel-refresh.test.mjs src/otel-emit.test.mjs src/teach.test.mjs src/schema-evolve.test.mjs src/global-wiki.test.mjs src/promote.test.mjs src/doctor.test.mjs src/setup-wizard.test.mjs src/cli-no-args.test.mjs src/cli-no-args-tty.test.mjs",
|
|
45
45
|
"test:integration:bootstrap": "node src/bootstrap-dryrun.integration.test.mjs",
|
|
46
46
|
"smoke": "node scripts/smoke.mjs",
|
|
47
47
|
"eval": "pwsh plugin/skills/eval/run-evals.ps1 -Skill",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// kushi v5.4.1 — bare `kushi` on an interactive TTY auto-runs the setup
|
|
2
|
+
// wizard. Non-TTY (covered by cli-no-args.test.mjs) still prints the welcome
|
|
3
|
+
// card and never installs anything. We can't easily fake a real PTY inside
|
|
4
|
+
// node:test, so this test uses KUSHI_FORCE_WIZARD=1 as the documented escape
|
|
5
|
+
// hatch that mirrors the TTY branch.
|
|
6
|
+
|
|
7
|
+
import test from 'node:test';
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import { spawnSync } from 'node:child_process';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
+
const cli = path.join(repoRoot, 'bin', 'cli.mjs');
|
|
17
|
+
|
|
18
|
+
function mkTmp() {
|
|
19
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kushi-cli-tty-'));
|
|
20
|
+
return dir;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test('cli no-args + KUSHI_FORCE_WIZARD=1: routes through setup-wizard, not welcome card', () => {
|
|
24
|
+
const installRoot = mkTmp();
|
|
25
|
+
const wizardRoot = mkTmp();
|
|
26
|
+
try {
|
|
27
|
+
const r = spawnSync(process.execPath, [cli], {
|
|
28
|
+
encoding: 'utf-8',
|
|
29
|
+
timeout: 30_000,
|
|
30
|
+
env: {
|
|
31
|
+
...process.env,
|
|
32
|
+
KUSHI_FORCE_WIZARD: '1',
|
|
33
|
+
KUSHI_SKIP_INSTALL: '1',
|
|
34
|
+
KUSHI_INSTALL_ROOT: installRoot,
|
|
35
|
+
KUSHI_WIZARD_ROOT: wizardRoot,
|
|
36
|
+
KUSHI_WIZARD_HOSTS: 'clawpilot',
|
|
37
|
+
KUSHI_WIZARD_GLOBAL: 'n',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const out = (r.stdout || '') + (r.stderr || '');
|
|
41
|
+
assert.equal(r.status, 0, `wizard exits 0\nout=${out}`);
|
|
42
|
+
assert.ok(!/non-interactive shell/.test(out), 'should not print welcome banner when wizard branch is taken');
|
|
43
|
+
assert.ok(/KUSHI_SKIP_INSTALL=1/.test(out) || /wizard complete/i.test(out) || /engagement root/i.test(out),
|
|
44
|
+
`expected wizard output, got:\n${out}`);
|
|
45
|
+
} finally {
|
|
46
|
+
fs.rmSync(installRoot, { recursive: true, force: true });
|
|
47
|
+
fs.rmSync(wizardRoot, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('cli no-args + KUSHI_SKIP_WELCOME=1: still prints welcome even on TTY override', () => {
|
|
52
|
+
const r = spawnSync(process.execPath, [cli], {
|
|
53
|
+
encoding: 'utf-8',
|
|
54
|
+
timeout: 30_000,
|
|
55
|
+
env: { ...process.env, KUSHI_SKIP_WELCOME: '1', KUSHI_FORCE_WIZARD: '' },
|
|
56
|
+
});
|
|
57
|
+
assert.equal(r.status, 0);
|
|
58
|
+
assert.match(r.stdout, /kushi v\d+\.\d+\.\d+/);
|
|
59
|
+
});
|
package/src/setup-wizard.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
// kushi v5.4.
|
|
1
|
+
// kushi v5.4.2 — interactive setup wizard.
|
|
2
2
|
// Non-interactive overrides (used by tests + CI):
|
|
3
|
-
// KUSHI_WIZARD_ROOT
|
|
4
|
-
// KUSHI_WIZARD_HOSTS
|
|
5
|
-
// KUSHI_WIZARD_GLOBAL
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// KUSHI_WIZARD_ROOT — engagement root path
|
|
4
|
+
// KUSHI_WIZARD_HOSTS — comma list: clawpilot,vscode (or "both")
|
|
5
|
+
// KUSHI_WIZARD_GLOBAL — "y" or "n"
|
|
6
|
+
// KUSHI_WIZARD_WORKSPACE — "y" or "n" — scaffold cwd as a kushi workspace
|
|
7
|
+
// KUSHI_INSTALL_ROOT — override ~/.copilot/ for install target (test isolation)
|
|
8
|
+
// KUSHI_SKIP_INSTALL — "1" to skip the actual install step (tests)
|
|
8
9
|
|
|
9
10
|
import fs from 'node:fs';
|
|
10
11
|
import path from 'node:path';
|
|
@@ -41,7 +42,7 @@ function makePrompter() {
|
|
|
41
42
|
const v = process.env[key];
|
|
42
43
|
return (typeof v === 'string' && v.length > 0) ? v : null;
|
|
43
44
|
};
|
|
44
|
-
if (fromEnv('KUSHI_WIZARD_ROOT') || fromEnv('KUSHI_WIZARD_HOSTS') || fromEnv('KUSHI_WIZARD_GLOBAL')) {
|
|
45
|
+
if (fromEnv('KUSHI_WIZARD_ROOT') || fromEnv('KUSHI_WIZARD_HOSTS') || fromEnv('KUSHI_WIZARD_GLOBAL') || fromEnv('KUSHI_WIZARD_WORKSPACE')) {
|
|
45
46
|
return { interactive: false, ask: async (_q, _def, envKey) => fromEnv(envKey) };
|
|
46
47
|
}
|
|
47
48
|
if (!process.stdin.isTTY) {
|
|
@@ -60,15 +61,23 @@ function makePrompter() {
|
|
|
60
61
|
export async function runSetupWizard({ args = [] } = {}) {
|
|
61
62
|
const detectedRoot = detectEngagementRoot();
|
|
62
63
|
const detectedHosts = detectHosts();
|
|
64
|
+
const cwd = process.cwd();
|
|
65
|
+
const cwdHasKushi = fs.existsSync(path.join(cwd, '.kushi'));
|
|
66
|
+
const defaultWorkspace = cwdHasKushi ? 'n' : 'y';
|
|
63
67
|
const p = makePrompter();
|
|
64
68
|
|
|
65
69
|
console.log('');
|
|
66
|
-
console.log(' kushi setup wizard —
|
|
70
|
+
console.log(' kushi setup wizard — 4 questions, then I install.');
|
|
67
71
|
console.log('');
|
|
68
72
|
|
|
69
73
|
const root = (await p.ask(' Where is your engagement root?', detectedRoot, 'KUSHI_WIZARD_ROOT')) || detectedRoot;
|
|
70
74
|
const hosts = (await p.ask(' Install for clawpilot / vscode / both?', detectedHosts, 'KUSHI_WIZARD_HOSTS')) || detectedHosts;
|
|
71
75
|
const wantGlobal = (await p.ask(' Enable global wiki at ~/.kushi-global/?', 'y', 'KUSHI_WIZARD_GLOBAL')) || 'y';
|
|
76
|
+
const wantWorkspace = (await p.ask(
|
|
77
|
+
` Scaffold current directory (${cwd}) as a kushi workspace?${cwdHasKushi ? ' (already present)' : ''}`,
|
|
78
|
+
defaultWorkspace,
|
|
79
|
+
'KUSHI_WIZARD_WORKSPACE',
|
|
80
|
+
)) || defaultWorkspace;
|
|
72
81
|
|
|
73
82
|
if (p.close) p.close();
|
|
74
83
|
|
|
@@ -76,6 +85,8 @@ export async function runSetupWizard({ args = [] } = {}) {
|
|
|
76
85
|
engagementRoot: root,
|
|
77
86
|
hosts: hosts.toLowerCase(),
|
|
78
87
|
globalWiki: /^y/i.test(wantGlobal),
|
|
88
|
+
workspaceInstall: /^y/i.test(wantWorkspace),
|
|
89
|
+
workspacePath: cwd,
|
|
79
90
|
};
|
|
80
91
|
|
|
81
92
|
console.log('');
|
|
@@ -83,6 +94,7 @@ export async function runSetupWizard({ args = [] } = {}) {
|
|
|
83
94
|
console.log(` engagement root: ${answers.engagementRoot}`);
|
|
84
95
|
console.log(` hosts: ${answers.hosts}`);
|
|
85
96
|
console.log(` global wiki: ${answers.globalWiki ? 'yes' : 'no'}`);
|
|
97
|
+
console.log(` workspace: ${answers.workspaceInstall ? `yes (${cwd})` : 'no'}`);
|
|
86
98
|
console.log('');
|
|
87
99
|
|
|
88
100
|
if (process.env.KUSHI_SKIP_INSTALL === '1') {
|
|
@@ -122,6 +134,25 @@ export async function runSetupWizard({ args = [] } = {}) {
|
|
|
122
134
|
}
|
|
123
135
|
}
|
|
124
136
|
|
|
137
|
+
if (answers.workspaceInstall) {
|
|
138
|
+
try {
|
|
139
|
+
const { main } = await import('./main.mjs');
|
|
140
|
+
await main({
|
|
141
|
+
force: false,
|
|
142
|
+
yes: true,
|
|
143
|
+
noSettings: false,
|
|
144
|
+
noInstructions: false,
|
|
145
|
+
target: 'vscode',
|
|
146
|
+
profile: undefined,
|
|
147
|
+
withWorkiq: false,
|
|
148
|
+
workiqPath: undefined,
|
|
149
|
+
skipWorkiqCheck: true,
|
|
150
|
+
});
|
|
151
|
+
} catch (err) {
|
|
152
|
+
console.error(` workspace install skipped: ${err.message}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
125
156
|
console.log('');
|
|
126
157
|
console.log(' ✅ wizard complete — next steps:');
|
|
127
158
|
console.log(' kushi doctor');
|
|
@@ -31,18 +31,20 @@ async function withEnv(envOverrides, fn) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
test('setup-wizard: env-driven cases (sequential to avoid env race)', async (t) => {
|
|
34
|
-
await t.test('case 1: env overrides resolve all
|
|
34
|
+
await t.test('case 1: env overrides resolve all four prompts', async () => {
|
|
35
35
|
const root = path.join(TESTTMP, `wizard-root-${Date.now()}-1`);
|
|
36
36
|
fs.mkdirSync(root, { recursive: true });
|
|
37
37
|
const result = await withEnv({
|
|
38
38
|
KUSHI_WIZARD_ROOT: root,
|
|
39
39
|
KUSHI_WIZARD_HOSTS: 'clawpilot',
|
|
40
40
|
KUSHI_WIZARD_GLOBAL: 'n',
|
|
41
|
+
KUSHI_WIZARD_WORKSPACE: 'n',
|
|
41
42
|
KUSHI_SKIP_INSTALL: '1',
|
|
42
43
|
}, () => runSetupWizard());
|
|
43
44
|
assert.equal(result.engagementRoot, root, 'root from env');
|
|
44
45
|
assert.equal(result.hosts, 'clawpilot', 'host from env');
|
|
45
46
|
assert.equal(result.globalWiki, false, 'global wiki off');
|
|
47
|
+
assert.equal(result.workspaceInstall, false, 'workspace off');
|
|
46
48
|
});
|
|
47
49
|
|
|
48
50
|
await t.test('case 2: "both" hosts retained in answers', async () => {
|
|
@@ -52,6 +54,7 @@ test('setup-wizard: env-driven cases (sequential to avoid env race)', async (t)
|
|
|
52
54
|
KUSHI_WIZARD_ROOT: root,
|
|
53
55
|
KUSHI_WIZARD_HOSTS: 'both',
|
|
54
56
|
KUSHI_WIZARD_GLOBAL: 'n',
|
|
57
|
+
KUSHI_WIZARD_WORKSPACE: 'n',
|
|
55
58
|
KUSHI_SKIP_INSTALL: '1',
|
|
56
59
|
}, () => runSetupWizard());
|
|
57
60
|
assert.equal(result.hosts, 'both');
|
|
@@ -64,6 +67,7 @@ test('setup-wizard: env-driven cases (sequential to avoid env race)', async (t)
|
|
|
64
67
|
KUSHI_WIZARD_ROOT: installRoot,
|
|
65
68
|
KUSHI_WIZARD_HOSTS: 'clawpilot',
|
|
66
69
|
KUSHI_WIZARD_GLOBAL: 'y',
|
|
70
|
+
KUSHI_WIZARD_WORKSPACE: 'n',
|
|
67
71
|
KUSHI_SKIP_INSTALL: '1',
|
|
68
72
|
KUSHI_INSTALL_ROOT: installRoot,
|
|
69
73
|
}, () => runSetupWizard());
|
|
@@ -71,4 +75,18 @@ test('setup-wizard: env-driven cases (sequential to avoid env race)', async (t)
|
|
|
71
75
|
assert.equal(result.globalWiki, true);
|
|
72
76
|
assert.equal(result.engagementRoot, installRoot);
|
|
73
77
|
});
|
|
78
|
+
|
|
79
|
+
await t.test('case 4: workspace scaffold opt-in records cwd', async () => {
|
|
80
|
+
const root = path.join(TESTTMP, `wizard-root-${Date.now()}-4`);
|
|
81
|
+
fs.mkdirSync(root, { recursive: true });
|
|
82
|
+
const result = await withEnv({
|
|
83
|
+
KUSHI_WIZARD_ROOT: root,
|
|
84
|
+
KUSHI_WIZARD_HOSTS: 'clawpilot',
|
|
85
|
+
KUSHI_WIZARD_GLOBAL: 'n',
|
|
86
|
+
KUSHI_WIZARD_WORKSPACE: 'y',
|
|
87
|
+
KUSHI_SKIP_INSTALL: '1',
|
|
88
|
+
}, () => runSetupWizard());
|
|
89
|
+
assert.equal(result.workspaceInstall, true, 'workspace install requested');
|
|
90
|
+
assert.equal(result.workspacePath, process.cwd(), 'workspace path = cwd');
|
|
91
|
+
});
|
|
74
92
|
});
|