instar 0.2.3 → 0.3.0
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/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +178 -18
- package/dist/core/UpdateChecker.js +10 -1
- package/package.json +1 -1
package/dist/commands/init.d.ts
CHANGED
|
@@ -38,5 +38,11 @@ interface InitOptions {
|
|
|
38
38
|
* Main init entry point. Handles both fresh and existing project modes.
|
|
39
39
|
*/
|
|
40
40
|
export declare function initProject(options: InitOptions): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Refresh hooks and Claude settings for an existing installation.
|
|
43
|
+
* Called after updates to ensure new hooks are installed.
|
|
44
|
+
* Re-writes all hook files (idempotent) and merges new hooks into settings.
|
|
45
|
+
*/
|
|
46
|
+
export declare function refreshHooksAndSettings(projectDir: string, stateDir: string): void;
|
|
41
47
|
export {};
|
|
42
48
|
//# sourceMappingURL=init.d.ts.map
|
package/dist/commands/init.js
CHANGED
|
@@ -569,6 +569,15 @@ function getDefaultJobs(port) {
|
|
|
569
569
|
},
|
|
570
570
|
];
|
|
571
571
|
}
|
|
572
|
+
/**
|
|
573
|
+
* Refresh hooks and Claude settings for an existing installation.
|
|
574
|
+
* Called after updates to ensure new hooks are installed.
|
|
575
|
+
* Re-writes all hook files (idempotent) and merges new hooks into settings.
|
|
576
|
+
*/
|
|
577
|
+
export function refreshHooksAndSettings(projectDir, stateDir) {
|
|
578
|
+
installHooks(stateDir);
|
|
579
|
+
installClaudeSettings(projectDir);
|
|
580
|
+
}
|
|
572
581
|
function installHooks(stateDir) {
|
|
573
582
|
const hooksDir = path.join(stateDir, 'hooks');
|
|
574
583
|
fs.mkdirSync(hooksDir, { recursive: true });
|
|
@@ -628,6 +637,132 @@ if [ -f "$INSTAR_DIR/AGENT.md" ]; then
|
|
|
628
637
|
[ -n "$AGENT_NAME" ] && echo "Identity reminder: $AGENT_NAME"
|
|
629
638
|
echo "Read .instar/AGENT.md and .instar/MEMORY.md to restore full context."
|
|
630
639
|
fi
|
|
640
|
+
`, { mode: 0o755 });
|
|
641
|
+
// Deferral detector — catches agents deferring work they could do themselves.
|
|
642
|
+
// PreToolUse hook for Bash. Scans outgoing communication for deferral patterns.
|
|
643
|
+
// When detected, injects a due diligence checklist as additionalContext.
|
|
644
|
+
// Does NOT block — just adds awareness so the agent can reconsider.
|
|
645
|
+
fs.writeFileSync(path.join(hooksDir, 'deferral-detector.js'), `#!/usr/bin/env node
|
|
646
|
+
// Deferral detector — catches agents deferring work they could do themselves.
|
|
647
|
+
// PreToolUse hook for Bash commands. Scans outgoing messages for deferral patterns.
|
|
648
|
+
// When detected, injects a due diligence checklist (does NOT block).
|
|
649
|
+
//
|
|
650
|
+
// Born from an agent saying "This is credential input I cannot do myself"
|
|
651
|
+
// when it already had the token available via CLI tools.
|
|
652
|
+
|
|
653
|
+
let data = '';
|
|
654
|
+
process.stdin.on('data', chunk => data += chunk);
|
|
655
|
+
process.stdin.on('end', () => {
|
|
656
|
+
try {
|
|
657
|
+
const input = JSON.parse(data);
|
|
658
|
+
if (input.tool_name !== 'Bash') process.exit(0);
|
|
659
|
+
|
|
660
|
+
const command = (input.tool_input || {}).command || '';
|
|
661
|
+
if (!command) process.exit(0);
|
|
662
|
+
|
|
663
|
+
// Only check communication commands (messages to humans)
|
|
664
|
+
const commPatterns = [
|
|
665
|
+
/telegram-reply/i, /send-email/i, /send-message/i,
|
|
666
|
+
/POST.*\\/telegram\\/reply/i, /slack.*send/i
|
|
667
|
+
];
|
|
668
|
+
if (!commPatterns.some(p => p.test(command))) process.exit(0);
|
|
669
|
+
|
|
670
|
+
// Exempt: genuinely human-only actions
|
|
671
|
+
if (/password|captcha|legal|billing|payment credential/i.test(command)) process.exit(0);
|
|
672
|
+
|
|
673
|
+
// Deferral patterns
|
|
674
|
+
const patterns = [
|
|
675
|
+
{ re: /(?:I |i )(?:can'?t|cannot|am (?:not |un)able to)/i, type: 'inability_claim' },
|
|
676
|
+
{ re: /(?:this |it )(?:requires|needs) (?:your|human|manual) (?:input|intervention|action)/i, type: 'human_required' },
|
|
677
|
+
{ re: /you(?:'ll| will)? need to (?:do|handle|complete|input|enter|run|execute|click)/i, type: 'directing_human' },
|
|
678
|
+
{ re: /(?:you (?:can|could|should|might want to) )(?:run|execute|navigate|open|click)/i, type: 'suggesting_human_action' },
|
|
679
|
+
{ re: /(?:want me to|should I|shall I|would you like me to) (?:proceed|continue|go ahead)/i, type: 'permission_seeking' },
|
|
680
|
+
{ re: /(?:blocker|blocking issue|can'?t proceed (?:without|until))/i, type: 'claimed_blocker' },
|
|
681
|
+
];
|
|
682
|
+
|
|
683
|
+
const matches = patterns.filter(p => p.re.test(command));
|
|
684
|
+
if (matches.length === 0) process.exit(0);
|
|
685
|
+
|
|
686
|
+
const checklist = [
|
|
687
|
+
'DEFERRAL DETECTED — Before claiming you cannot do something, verify:',
|
|
688
|
+
'',
|
|
689
|
+
'1. Did you check --help or docs for the tool you are using?',
|
|
690
|
+
'2. Did you search for a token/API-based alternative to interactive auth?',
|
|
691
|
+
'3. Do you already have credentials/tokens that might work? (env vars, CLI auth, saved configs)',
|
|
692
|
+
'4. Can you use browser automation to complete interactive flows?',
|
|
693
|
+
'5. Is this GENUINELY beyond your access? (e.g., typing a password, solving a CAPTCHA)',
|
|
694
|
+
'',
|
|
695
|
+
'If ANY check might work — try it first.',
|
|
696
|
+
'The pattern: You are DESCRIBING work instead of DOING work.',
|
|
697
|
+
'',
|
|
698
|
+
'Detected: ' + matches.map(m => m.type).join(', '),
|
|
699
|
+
].join('\\n');
|
|
700
|
+
|
|
701
|
+
process.stdout.write(JSON.stringify({ decision: 'approve', additionalContext: checklist }));
|
|
702
|
+
} catch { /* don't break on errors */ }
|
|
703
|
+
process.exit(0);
|
|
704
|
+
});
|
|
705
|
+
`, { mode: 0o755 });
|
|
706
|
+
// External communication guard — ensures identity grounding before external posting.
|
|
707
|
+
// PreToolUse hook for Bash. Detects commands that post to external platforms.
|
|
708
|
+
// Injects a reminder to re-read identity before sending. Advisory, not blocking.
|
|
709
|
+
fs.writeFileSync(path.join(hooksDir, 'external-communication-guard.js'), `#!/usr/bin/env node
|
|
710
|
+
// External communication guard — identity grounding before external posting.
|
|
711
|
+
// PreToolUse hook for Bash. Detects external posting commands (curl POST, API calls,
|
|
712
|
+
// CLI tools that post to external services). Injects identity re-read reminder.
|
|
713
|
+
//
|
|
714
|
+
// "An agent that knows itself is harder to compromise."
|
|
715
|
+
// "An agent that forgets itself posts things it shouldn't."
|
|
716
|
+
|
|
717
|
+
let data = '';
|
|
718
|
+
process.stdin.on('data', chunk => data += chunk);
|
|
719
|
+
process.stdin.on('end', () => {
|
|
720
|
+
try {
|
|
721
|
+
const input = JSON.parse(data);
|
|
722
|
+
if (input.tool_name !== 'Bash') process.exit(0);
|
|
723
|
+
|
|
724
|
+
const command = (input.tool_input || {}).command || '';
|
|
725
|
+
if (!command) process.exit(0);
|
|
726
|
+
|
|
727
|
+
// Patterns that indicate external posting
|
|
728
|
+
const postingPatterns = [
|
|
729
|
+
/curl\\s.*-X\\s+POST/i,
|
|
730
|
+
/curl\\s.*-X\\s+PUT/i,
|
|
731
|
+
/curl\\s.*-X\\s+PATCH/i,
|
|
732
|
+
/curl\\s.*-d\\s+['"]/i,
|
|
733
|
+
/curl\\s.*--data/i,
|
|
734
|
+
/gh\\s+issue\\s+(?:comment|create)/i,
|
|
735
|
+
/gh\\s+pr\\s+(?:comment|create|review)/i,
|
|
736
|
+
/gh\\s+api\\s+graphql.*mutation/i,
|
|
737
|
+
/sendgrid|mailgun|ses\\.amazonaws.*send/i,
|
|
738
|
+
/telegram-reply/i,
|
|
739
|
+
/send-email/i,
|
|
740
|
+
/slack.*(?:chat\\.postMessage|send)/i,
|
|
741
|
+
];
|
|
742
|
+
|
|
743
|
+
if (!postingPatterns.some(p => p.test(command))) process.exit(0);
|
|
744
|
+
|
|
745
|
+
// Exempt: localhost, internal APIs, health checks
|
|
746
|
+
if (/localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0/i.test(command)) process.exit(0);
|
|
747
|
+
if (/curl\\s+-s\\s+https?:\\/\\/[^\\s]+\\s*$/i.test(command)) process.exit(0); // Simple GET
|
|
748
|
+
if (/heartbeat|keepalive|health/i.test(command)) process.exit(0);
|
|
749
|
+
|
|
750
|
+
const reminder = [
|
|
751
|
+
'EXTERNAL COMMUNICATION DETECTED — Identity grounding check:',
|
|
752
|
+
'',
|
|
753
|
+
'Before posting externally, verify:',
|
|
754
|
+
'1. Have you read .instar/AGENT.md recently in this session?',
|
|
755
|
+
'2. Does this message represent who you are and your principles?',
|
|
756
|
+
'3. Are you posting something you would stand behind across sessions?',
|
|
757
|
+
'4. Is the tone and content consistent with your identity?',
|
|
758
|
+
'',
|
|
759
|
+
'Security Through Identity: An agent that knows itself is harder to compromise.',
|
|
760
|
+
].join('\\n');
|
|
761
|
+
|
|
762
|
+
process.stdout.write(JSON.stringify({ decision: 'approve', additionalContext: reminder }));
|
|
763
|
+
} catch { /* don't break on errors */ }
|
|
764
|
+
process.exit(0);
|
|
765
|
+
});
|
|
631
766
|
`, { mode: 0o755 });
|
|
632
767
|
}
|
|
633
768
|
function installHealthWatchdog(projectDir, port, projectName) {
|
|
@@ -675,25 +810,50 @@ function installClaudeSettings(projectDir) {
|
|
|
675
810
|
settings.hooks = {};
|
|
676
811
|
}
|
|
677
812
|
const hooks = settings.hooks;
|
|
678
|
-
//
|
|
813
|
+
// All instar-managed hooks for PreToolUse/Bash
|
|
814
|
+
const instarBashHooks = [
|
|
815
|
+
{
|
|
816
|
+
type: 'command',
|
|
817
|
+
command: 'bash .instar/hooks/dangerous-command-guard.sh "$TOOL_INPUT"',
|
|
818
|
+
blocking: true,
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
type: 'command',
|
|
822
|
+
command: 'bash .instar/hooks/grounding-before-messaging.sh "$TOOL_INPUT"',
|
|
823
|
+
blocking: false,
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
type: 'command',
|
|
827
|
+
command: 'node .instar/hooks/deferral-detector.js',
|
|
828
|
+
timeout: 5000,
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
type: 'command',
|
|
832
|
+
command: 'node .instar/hooks/external-communication-guard.js',
|
|
833
|
+
timeout: 5000,
|
|
834
|
+
},
|
|
835
|
+
];
|
|
836
|
+
// PreToolUse: merge instar hooks into existing or create fresh
|
|
679
837
|
if (!hooks.PreToolUse) {
|
|
680
|
-
hooks.PreToolUse = [
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
838
|
+
hooks.PreToolUse = [{ matcher: 'Bash', hooks: instarBashHooks }];
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
// Find existing Bash matcher or create one
|
|
842
|
+
const preToolUse = hooks.PreToolUse;
|
|
843
|
+
let bashEntry = preToolUse.find(e => e.matcher === 'Bash');
|
|
844
|
+
if (!bashEntry) {
|
|
845
|
+
bashEntry = { matcher: 'Bash', hooks: [] };
|
|
846
|
+
preToolUse.push(bashEntry);
|
|
847
|
+
}
|
|
848
|
+
if (!bashEntry.hooks)
|
|
849
|
+
bashEntry.hooks = [];
|
|
850
|
+
// Add any instar hooks not already present (by command string)
|
|
851
|
+
const existingCommands = new Set(bashEntry.hooks.map(h => h.command));
|
|
852
|
+
for (const hook of instarBashHooks) {
|
|
853
|
+
if (!existingCommands.has(hook.command)) {
|
|
854
|
+
bashEntry.hooks.push(hook);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
697
857
|
}
|
|
698
858
|
// PostToolUse: session start identity injection
|
|
699
859
|
if (!hooks.PostToolUse) {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { execFile } from 'node:child_process';
|
|
14
14
|
import fs from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
|
+
import { refreshHooksAndSettings } from '../commands/init.js';
|
|
16
17
|
const GITHUB_RELEASES_URL = 'https://api.github.com/repos/SageMindAI/instar/releases';
|
|
17
18
|
export class UpdateChecker {
|
|
18
19
|
stateDir;
|
|
@@ -110,13 +111,21 @@ export class UpdateChecker {
|
|
|
110
111
|
// Save rollback info on successful update
|
|
111
112
|
if (success) {
|
|
112
113
|
this.saveRollbackInfo(previousVersion, newVersion);
|
|
114
|
+
// Refresh hooks and settings — new versions may include new hooks
|
|
115
|
+
try {
|
|
116
|
+
const projectDir = path.resolve(this.stateDir, '..');
|
|
117
|
+
refreshHooksAndSettings(projectDir, this.stateDir);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Non-critical — hooks can be refreshed manually via `instar init`
|
|
121
|
+
}
|
|
113
122
|
}
|
|
114
123
|
return {
|
|
115
124
|
success,
|
|
116
125
|
previousVersion,
|
|
117
126
|
newVersion,
|
|
118
127
|
message: success
|
|
119
|
-
? `Updated from v${previousVersion} to v${newVersion}. ${info.changeSummary || 'Restart to use the new version.'}`
|
|
128
|
+
? `Updated from v${previousVersion} to v${newVersion}. Hooks refreshed. ${info.changeSummary || 'Restart to use the new version.'}`
|
|
120
129
|
: `Update command ran but version didn't change (still v${previousVersion}). May need manual intervention.`,
|
|
121
130
|
restartNeeded: success,
|
|
122
131
|
healthCheck: 'skipped', // Can't check health until after restart
|