instar 0.3.3 → 0.3.5
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.js +44 -0
- package/dist/commands/server.js +7 -1
- package/dist/commands/setup.js +32 -0
- package/dist/core/PostUpdateMigrator.d.ts +65 -0
- package/dist/core/PostUpdateMigrator.js +347 -0
- package/dist/core/UpdateChecker.d.ts +13 -1
- package/dist/core/UpdateChecker.js +35 -11
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/scaffold/templates.js +33 -0
- package/dist/server/routes.js +98 -0
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -399,8 +399,28 @@ Your identity is not fixed at creation. It grows through experience. As you work
|
|
|
399
399
|
- Server: \`instar server start\` (port ${port})
|
|
400
400
|
- Status: \`curl http://localhost:${port}/health\`
|
|
401
401
|
|
|
402
|
+
### Reporting Issues & Feedback (IMPORTANT — Read This First)
|
|
403
|
+
|
|
404
|
+
**You have a built-in feedback system. NEVER use GitHub, \`gh\`, or any external issue tracker.**
|
|
405
|
+
|
|
406
|
+
When something isn't working with instar, or the user wants a feature, use your feedback API:
|
|
407
|
+
|
|
408
|
+
\`\`\`bash
|
|
409
|
+
curl -X POST http://localhost:${port}/feedback \\
|
|
410
|
+
-H 'Content-Type: application/json' \\
|
|
411
|
+
-d '{"type":"bug","title":"Short description","description":"Full details with context"}'
|
|
412
|
+
\`\`\`
|
|
413
|
+
|
|
414
|
+
This routes feedback to the Instar maintainers automatically. Valid types: \`bug\`, \`feature\`, \`improvement\`, \`question\`.
|
|
415
|
+
|
|
416
|
+
**NEVER use \`gh issue\`, \`gh api\`, or GitHub CLI to file issues.** The feedback API is your channel.
|
|
417
|
+
- View submitted feedback: \`curl http://localhost:${port}/feedback\`
|
|
418
|
+
- Retry failed forwards: \`curl -X POST http://localhost:${port}/feedback/retry\`
|
|
419
|
+
|
|
402
420
|
### What You Have (Capabilities)
|
|
403
421
|
|
|
422
|
+
**Feedback System** — Report bugs, request features, suggest improvements. All via \`POST /feedback\`. NOT GitHub.
|
|
423
|
+
|
|
404
424
|
**Job Scheduler** — You can run tasks on a schedule. Jobs are defined in \`.instar/jobs.json\`.
|
|
405
425
|
- View jobs: \`curl http://localhost:${port}/jobs\`
|
|
406
426
|
- Trigger a job: \`curl -X POST http://localhost:${port}/jobs/SLUG/trigger\`
|
|
@@ -412,6 +432,16 @@ Your identity is not fixed at creation. It grows through experience. As you work
|
|
|
412
432
|
|
|
413
433
|
**Scripts** — Create shell/python scripts in \`.claude/scripts/\` for reusable capabilities.
|
|
414
434
|
|
|
435
|
+
### Self-Discovery (Know Before You Claim)
|
|
436
|
+
|
|
437
|
+
Before EVER saying "I don't have", "I can't", or "this isn't available" — check what actually exists:
|
|
438
|
+
|
|
439
|
+
\`\`\`bash
|
|
440
|
+
curl http://localhost:${port}/capabilities
|
|
441
|
+
\`\`\`
|
|
442
|
+
|
|
443
|
+
This returns your full capability matrix: scripts, hooks, Telegram status, jobs, relationships, and more. It is the source of truth about what you can do. **Never hallucinate about missing capabilities — verify first.**
|
|
444
|
+
|
|
415
445
|
### How to Build New Capabilities
|
|
416
446
|
|
|
417
447
|
When a user asks for something you can't do yet, **build it**:
|
|
@@ -442,6 +472,8 @@ When a user asks for something you can't do yet, **build it**:
|
|
|
442
472
|
|
|
443
473
|
### Anti-Patterns to Override
|
|
444
474
|
|
|
475
|
+
**"Use GitHub for Issues"** — NEVER use \`gh issue\`, \`gh api\`, or GitHub to report bugs or request features. You have a built-in feedback API (\`POST /feedback\`) that handles this automatically. GitHub is not your channel — the feedback system is.
|
|
476
|
+
|
|
445
477
|
**"Escalate to Human"** — You encounter something unfamiliar and flag it as "needs user action." 5 minutes of research almost always reveals a solution. "I don't know how" is a research prompt, not a stopping point.
|
|
446
478
|
|
|
447
479
|
**"Ask Permission"** — You finish 90% of a task and ask "Want me to [obvious next step]?" If the user said "do X" and the next step is obvious, do it. Only pause for destructive, irreversible, or genuinely ambiguous actions.
|
|
@@ -716,6 +748,18 @@ if [ -d "$INSTAR_DIR/relationships" ]; then
|
|
|
716
748
|
CONTEXT="\${CONTEXT}You have \${REL_COUNT} tracked relationships in .instar/relationships/.\\n"
|
|
717
749
|
fi
|
|
718
750
|
fi
|
|
751
|
+
CONTEXT="\${CONTEXT}IMPORTANT: To report bugs or request features, use POST /feedback on your local server. NEVER use gh or GitHub directly.\\n"
|
|
752
|
+
|
|
753
|
+
# Self-discovery: check what capabilities are available
|
|
754
|
+
if [ -f "$INSTAR_DIR/config.json" ]; then
|
|
755
|
+
PORT=$(python3 -c "import json; print(json.load(open('$INSTAR_DIR/config.json')).get('port', 4040))" 2>/dev/null || echo "4040")
|
|
756
|
+
HEALTH=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:\${PORT}/health" 2>/dev/null)
|
|
757
|
+
if [ "$HEALTH" = "200" ]; then
|
|
758
|
+
CONTEXT="\${CONTEXT}Instar server is running on port \${PORT}. Query your capabilities: curl http://localhost:\${PORT}/capabilities\\n"
|
|
759
|
+
CONTEXT="\${CONTEXT}IMPORTANT: Before claiming you lack a capability, check /capabilities first.\\n"
|
|
760
|
+
fi
|
|
761
|
+
fi
|
|
762
|
+
|
|
719
763
|
[ -n "$CONTEXT" ] && echo "$CONTEXT"
|
|
720
764
|
`, { mode: 0o755 });
|
|
721
765
|
// Dangerous command guard
|
package/dist/commands/server.js
CHANGED
|
@@ -268,7 +268,13 @@ export async function startServer(options) {
|
|
|
268
268
|
});
|
|
269
269
|
console.log(pc.green(' Dispatch system enabled'));
|
|
270
270
|
}
|
|
271
|
-
const updateChecker = new UpdateChecker(
|
|
271
|
+
const updateChecker = new UpdateChecker({
|
|
272
|
+
stateDir: config.stateDir,
|
|
273
|
+
projectDir: config.projectDir,
|
|
274
|
+
port: config.port,
|
|
275
|
+
hasTelegram: config.messaging.some(m => m.type === 'telegram' && m.enabled),
|
|
276
|
+
projectName: config.projectName,
|
|
277
|
+
});
|
|
272
278
|
// Check for updates on startup
|
|
273
279
|
updateChecker.check().then(info => {
|
|
274
280
|
if (info.updateAvailable) {
|
package/dist/commands/setup.js
CHANGED
|
@@ -765,8 +765,28 @@ Your identity is not fixed at creation. It grows through experience. As you work
|
|
|
765
765
|
- Server: \`instar server start\` (port ${port})
|
|
766
766
|
- Status: \`curl http://localhost:${port}/health\`
|
|
767
767
|
|
|
768
|
+
### Reporting Issues & Feedback (IMPORTANT — Read This First)
|
|
769
|
+
|
|
770
|
+
**You have a built-in feedback system. NEVER use GitHub, \`gh\`, or any external issue tracker.**
|
|
771
|
+
|
|
772
|
+
When something isn't working with instar, or the user wants a feature, use your feedback API:
|
|
773
|
+
|
|
774
|
+
\`\`\`bash
|
|
775
|
+
curl -X POST http://localhost:${port}/feedback \\
|
|
776
|
+
-H 'Content-Type: application/json' \\
|
|
777
|
+
-d '{"type":"bug","title":"Short description","description":"Full details with context"}'
|
|
778
|
+
\`\`\`
|
|
779
|
+
|
|
780
|
+
This routes feedback to the Instar maintainers automatically. Valid types: \`bug\`, \`feature\`, \`improvement\`, \`question\`.
|
|
781
|
+
|
|
782
|
+
**NEVER use \`gh issue\`, \`gh api\`, or GitHub CLI to file issues.** The feedback API is your channel.
|
|
783
|
+
- View submitted feedback: \`curl http://localhost:${port}/feedback\`
|
|
784
|
+
- Retry failed forwards: \`curl -X POST http://localhost:${port}/feedback/retry\`
|
|
785
|
+
|
|
768
786
|
### What You Have (Capabilities)
|
|
769
787
|
|
|
788
|
+
**Feedback System** — Report bugs, request features, suggest improvements. All via \`POST /feedback\`. NOT GitHub.
|
|
789
|
+
|
|
770
790
|
**Job Scheduler** — Run tasks on a schedule. Jobs in \`.instar/jobs.json\`.
|
|
771
791
|
- View: \`curl http://localhost:${port}/jobs\`
|
|
772
792
|
- Trigger: \`curl -X POST http://localhost:${port}/jobs/SLUG/trigger\`
|
|
@@ -778,6 +798,16 @@ Your identity is not fixed at creation. It grows through experience. As you work
|
|
|
778
798
|
|
|
779
799
|
**Scripts** — Create shell/python scripts in \`.claude/scripts/\` for reusable capabilities.
|
|
780
800
|
|
|
801
|
+
### Self-Discovery (Know Before You Claim)
|
|
802
|
+
|
|
803
|
+
Before EVER saying "I don't have", "I can't", or "this isn't available" — check what actually exists:
|
|
804
|
+
|
|
805
|
+
\`\`\`bash
|
|
806
|
+
curl http://localhost:${port}/capabilities
|
|
807
|
+
\`\`\`
|
|
808
|
+
|
|
809
|
+
This returns your full capability matrix: scripts, hooks, Telegram status, jobs, relationships, and more. It is the source of truth about what you can do. **Never hallucinate about missing capabilities — verify first.**
|
|
810
|
+
|
|
781
811
|
### How to Build New Capabilities
|
|
782
812
|
|
|
783
813
|
When a user asks for something you can't do yet, **build it**:
|
|
@@ -819,6 +849,8 @@ Only if ALL five fail → explain what's actually blocked and what you need from
|
|
|
819
849
|
|
|
820
850
|
These are trained behaviors that feel helpful but destroy your effectiveness. Recognize and override them.
|
|
821
851
|
|
|
852
|
+
**"Use GitHub for Issues"** — NEVER use \`gh issue\`, \`gh api\`, or GitHub to report bugs or request features. You have a built-in feedback API (\`POST /feedback\`) that handles this automatically. GitHub is not your channel — the feedback system is.
|
|
853
|
+
|
|
822
854
|
**"Escalate to Human"** — You encounter something unfamiliar and flag it as "needs user action." Training biases you toward deferral. Override: 5 minutes of research almost always reveals a solution. And if no solution exists, you can build one. "I don't know how" is a research prompt, not a stopping point.
|
|
823
855
|
|
|
824
856
|
**"Ask Permission"** — You finish 90% of a task and ask "Want me to [obvious next step]?" This feels polite. It's actually friction. If the user said "do X" and the next step is obvious, do it. Only pause for destructive, irreversible, or genuinely ambiguous actions.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-Update Migrator — the "intelligence download" layer.
|
|
3
|
+
*
|
|
4
|
+
* When an agent installs a new version of instar, updating the npm
|
|
5
|
+
* package only changes the server code. But the agent's local awareness
|
|
6
|
+
* lives in project files: CLAUDE.md, hooks, scripts.
|
|
7
|
+
*
|
|
8
|
+
* This migrator bridges that gap. After every successful update, it:
|
|
9
|
+
* 1. Re-installs hooks with the latest templates (behavioral upgrades)
|
|
10
|
+
* 2. Patches CLAUDE.md with any new sections (awareness upgrades)
|
|
11
|
+
* 3. Installs any new scripts (capability upgrades)
|
|
12
|
+
* 4. Returns a human-readable migration report
|
|
13
|
+
*
|
|
14
|
+
* Design principles:
|
|
15
|
+
* - Additive only: never remove or modify existing user customizations
|
|
16
|
+
* - Hooks are overwritten (they're generated infrastructure, not user-edited)
|
|
17
|
+
* - CLAUDE.md sections are appended only if missing (check by heading)
|
|
18
|
+
* - Scripts are installed only if missing (never overwrite user modifications)
|
|
19
|
+
*/
|
|
20
|
+
export interface MigrationResult {
|
|
21
|
+
/** What was upgraded */
|
|
22
|
+
upgraded: string[];
|
|
23
|
+
/** What was already up to date */
|
|
24
|
+
skipped: string[];
|
|
25
|
+
/** Any errors that occurred (non-fatal) */
|
|
26
|
+
errors: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface MigratorConfig {
|
|
29
|
+
projectDir: string;
|
|
30
|
+
stateDir: string;
|
|
31
|
+
port: number;
|
|
32
|
+
hasTelegram: boolean;
|
|
33
|
+
projectName: string;
|
|
34
|
+
}
|
|
35
|
+
export declare class PostUpdateMigrator {
|
|
36
|
+
private config;
|
|
37
|
+
constructor(config: MigratorConfig);
|
|
38
|
+
/**
|
|
39
|
+
* Run all post-update migrations. Safe to call multiple times —
|
|
40
|
+
* each migration is idempotent.
|
|
41
|
+
*/
|
|
42
|
+
migrate(): MigrationResult;
|
|
43
|
+
/**
|
|
44
|
+
* Re-install hooks with the latest templates.
|
|
45
|
+
* Hooks are generated infrastructure — always overwrite.
|
|
46
|
+
*/
|
|
47
|
+
private migrateHooks;
|
|
48
|
+
/**
|
|
49
|
+
* Patch CLAUDE.md with any new sections that don't exist yet.
|
|
50
|
+
* Only adds — never modifies or removes existing content.
|
|
51
|
+
*/
|
|
52
|
+
private migrateClaudeMd;
|
|
53
|
+
/**
|
|
54
|
+
* Install any new scripts that don't exist yet.
|
|
55
|
+
* Never overwrites existing scripts (user may have customized them).
|
|
56
|
+
*/
|
|
57
|
+
private migrateScripts;
|
|
58
|
+
private getSessionStartHook;
|
|
59
|
+
private getDangerousCommandGuard;
|
|
60
|
+
private getGroundingBeforeMessaging;
|
|
61
|
+
private getCompactionRecovery;
|
|
62
|
+
private getTelegramReplyScript;
|
|
63
|
+
private getHealthWatchdog;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=PostUpdateMigrator.d.ts.map
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-Update Migrator — the "intelligence download" layer.
|
|
3
|
+
*
|
|
4
|
+
* When an agent installs a new version of instar, updating the npm
|
|
5
|
+
* package only changes the server code. But the agent's local awareness
|
|
6
|
+
* lives in project files: CLAUDE.md, hooks, scripts.
|
|
7
|
+
*
|
|
8
|
+
* This migrator bridges that gap. After every successful update, it:
|
|
9
|
+
* 1. Re-installs hooks with the latest templates (behavioral upgrades)
|
|
10
|
+
* 2. Patches CLAUDE.md with any new sections (awareness upgrades)
|
|
11
|
+
* 3. Installs any new scripts (capability upgrades)
|
|
12
|
+
* 4. Returns a human-readable migration report
|
|
13
|
+
*
|
|
14
|
+
* Design principles:
|
|
15
|
+
* - Additive only: never remove or modify existing user customizations
|
|
16
|
+
* - Hooks are overwritten (they're generated infrastructure, not user-edited)
|
|
17
|
+
* - CLAUDE.md sections are appended only if missing (check by heading)
|
|
18
|
+
* - Scripts are installed only if missing (never overwrite user modifications)
|
|
19
|
+
*/
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
export class PostUpdateMigrator {
|
|
23
|
+
config;
|
|
24
|
+
constructor(config) {
|
|
25
|
+
this.config = config;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Run all post-update migrations. Safe to call multiple times —
|
|
29
|
+
* each migration is idempotent.
|
|
30
|
+
*/
|
|
31
|
+
migrate() {
|
|
32
|
+
const result = {
|
|
33
|
+
upgraded: [],
|
|
34
|
+
skipped: [],
|
|
35
|
+
errors: [],
|
|
36
|
+
};
|
|
37
|
+
this.migrateHooks(result);
|
|
38
|
+
this.migrateClaudeMd(result);
|
|
39
|
+
this.migrateScripts(result);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Re-install hooks with the latest templates.
|
|
44
|
+
* Hooks are generated infrastructure — always overwrite.
|
|
45
|
+
*/
|
|
46
|
+
migrateHooks(result) {
|
|
47
|
+
const hooksDir = path.join(this.config.stateDir, 'hooks');
|
|
48
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
49
|
+
try {
|
|
50
|
+
// Session start hook — the most important one for self-discovery
|
|
51
|
+
fs.writeFileSync(path.join(hooksDir, 'session-start.sh'), this.getSessionStartHook(), { mode: 0o755 });
|
|
52
|
+
result.upgraded.push('hooks/session-start.sh (capability awareness)');
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
result.errors.push(`session-start.sh: ${err instanceof Error ? err.message : String(err)}`);
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
fs.writeFileSync(path.join(hooksDir, 'dangerous-command-guard.sh'), this.getDangerousCommandGuard(), { mode: 0o755 });
|
|
59
|
+
result.upgraded.push('hooks/dangerous-command-guard.sh');
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
result.errors.push(`dangerous-command-guard.sh: ${err instanceof Error ? err.message : String(err)}`);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
fs.writeFileSync(path.join(hooksDir, 'grounding-before-messaging.sh'), this.getGroundingBeforeMessaging(), { mode: 0o755 });
|
|
66
|
+
result.upgraded.push('hooks/grounding-before-messaging.sh');
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
result.errors.push(`grounding-before-messaging.sh: ${err instanceof Error ? err.message : String(err)}`);
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
fs.writeFileSync(path.join(hooksDir, 'compaction-recovery.sh'), this.getCompactionRecovery(), { mode: 0o755 });
|
|
73
|
+
result.upgraded.push('hooks/compaction-recovery.sh');
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
result.errors.push(`compaction-recovery.sh: ${err instanceof Error ? err.message : String(err)}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Patch CLAUDE.md with any new sections that don't exist yet.
|
|
81
|
+
* Only adds — never modifies or removes existing content.
|
|
82
|
+
*/
|
|
83
|
+
migrateClaudeMd(result) {
|
|
84
|
+
const claudeMdPath = path.join(this.config.projectDir, 'CLAUDE.md');
|
|
85
|
+
if (!fs.existsSync(claudeMdPath)) {
|
|
86
|
+
result.skipped.push('CLAUDE.md (not found — will be created on next init)');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
let content;
|
|
90
|
+
try {
|
|
91
|
+
content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
result.errors.push(`CLAUDE.md read: ${err instanceof Error ? err.message : String(err)}`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
let patched = false;
|
|
98
|
+
const port = this.config.port;
|
|
99
|
+
// Self-Discovery section
|
|
100
|
+
if (!content.includes('Self-Discovery') && !content.includes('/capabilities')) {
|
|
101
|
+
const section = `
|
|
102
|
+
### Self-Discovery (Know Before You Claim)
|
|
103
|
+
|
|
104
|
+
Before EVER saying "I don't have", "I can't", or "this isn't available" — check what actually exists:
|
|
105
|
+
|
|
106
|
+
\`\`\`bash
|
|
107
|
+
curl http://localhost:${port}/capabilities
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
This returns your full capability matrix: scripts, hooks, Telegram status, jobs, relationships, and more. It is the source of truth about what you can do. **Never hallucinate about missing capabilities — verify first.**
|
|
111
|
+
`;
|
|
112
|
+
// Insert before "### How to Build" or "### Building New" if present, otherwise append
|
|
113
|
+
const insertPoint = content.indexOf('### How to Build New Capabilities');
|
|
114
|
+
const insertPoint2 = content.indexOf('### Building New Capabilities');
|
|
115
|
+
const target = insertPoint >= 0 ? insertPoint : (insertPoint2 >= 0 ? insertPoint2 : -1);
|
|
116
|
+
if (target >= 0) {
|
|
117
|
+
content = content.slice(0, target) + section + '\n' + content.slice(target);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
content += '\n' + section;
|
|
121
|
+
}
|
|
122
|
+
patched = true;
|
|
123
|
+
result.upgraded.push('CLAUDE.md: added Self-Discovery section');
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
result.skipped.push('CLAUDE.md: Self-Discovery section already present');
|
|
127
|
+
}
|
|
128
|
+
// Telegram Relay section — add if Telegram is configured but section is missing
|
|
129
|
+
if (this.config.hasTelegram && !content.includes('Telegram Relay') && !content.includes('telegram-reply')) {
|
|
130
|
+
const section = `
|
|
131
|
+
## Telegram Relay
|
|
132
|
+
|
|
133
|
+
When user input starts with \`[telegram:N]\` (e.g., \`[telegram:26] hello\`), the message came from a user via Telegram topic N. **After responding**, relay your response back:
|
|
134
|
+
|
|
135
|
+
\`\`\`bash
|
|
136
|
+
cat <<'EOF' | .claude/scripts/telegram-reply.sh N
|
|
137
|
+
Your response text here
|
|
138
|
+
EOF
|
|
139
|
+
\`\`\`
|
|
140
|
+
|
|
141
|
+
Strip the \`[telegram:N]\` prefix before interpreting the message. Respond naturally, then relay. Only relay your conversational text — not tool output or internal reasoning.
|
|
142
|
+
`;
|
|
143
|
+
content += '\n' + section;
|
|
144
|
+
patched = true;
|
|
145
|
+
result.upgraded.push('CLAUDE.md: added Telegram Relay section');
|
|
146
|
+
}
|
|
147
|
+
if (patched) {
|
|
148
|
+
try {
|
|
149
|
+
fs.writeFileSync(claudeMdPath, content);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
result.errors.push(`CLAUDE.md write: ${err instanceof Error ? err.message : String(err)}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Install any new scripts that don't exist yet.
|
|
158
|
+
* Never overwrites existing scripts (user may have customized them).
|
|
159
|
+
*/
|
|
160
|
+
migrateScripts(result) {
|
|
161
|
+
const scriptsDir = path.join(this.config.projectDir, '.claude', 'scripts');
|
|
162
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
163
|
+
// Telegram reply script — install if Telegram configured and script missing
|
|
164
|
+
if (this.config.hasTelegram) {
|
|
165
|
+
const scriptPath = path.join(scriptsDir, 'telegram-reply.sh');
|
|
166
|
+
if (!fs.existsSync(scriptPath)) {
|
|
167
|
+
try {
|
|
168
|
+
fs.writeFileSync(scriptPath, this.getTelegramReplyScript(), { mode: 0o755 });
|
|
169
|
+
result.upgraded.push('scripts/telegram-reply.sh (Telegram outbound relay)');
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
result.errors.push(`telegram-reply.sh: ${err instanceof Error ? err.message : String(err)}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
result.skipped.push('scripts/telegram-reply.sh (already exists)');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Health watchdog — install if missing
|
|
180
|
+
const watchdogPath = path.join(scriptsDir, 'health-watchdog.sh');
|
|
181
|
+
if (!fs.existsSync(watchdogPath)) {
|
|
182
|
+
try {
|
|
183
|
+
fs.writeFileSync(watchdogPath, this.getHealthWatchdog(), { mode: 0o755 });
|
|
184
|
+
result.upgraded.push('scripts/health-watchdog.sh');
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
result.errors.push(`health-watchdog.sh: ${err instanceof Error ? err.message : String(err)}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
result.skipped.push('scripts/health-watchdog.sh (already exists)');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// ── Hook Templates ─────────────────────────────────────────────────
|
|
195
|
+
getSessionStartHook() {
|
|
196
|
+
return `#!/bin/bash
|
|
197
|
+
# Session start hook — injects identity context when a new Claude session begins.
|
|
198
|
+
INSTAR_DIR="\${CLAUDE_PROJECT_DIR:-.}/.instar"
|
|
199
|
+
CONTEXT=""
|
|
200
|
+
if [ -f "$INSTAR_DIR/AGENT.md" ]; then
|
|
201
|
+
CONTEXT="\${CONTEXT}Your identity file is at .instar/AGENT.md — read it if you need to remember who you are.\\n"
|
|
202
|
+
fi
|
|
203
|
+
if [ -f "$INSTAR_DIR/USER.md" ]; then
|
|
204
|
+
CONTEXT="\${CONTEXT}Your user context is at .instar/USER.md — read it to know who you're working with.\\n"
|
|
205
|
+
fi
|
|
206
|
+
if [ -f "$INSTAR_DIR/MEMORY.md" ]; then
|
|
207
|
+
CONTEXT="\${CONTEXT}Your persistent memory is at .instar/MEMORY.md — check it for past learnings.\\n"
|
|
208
|
+
fi
|
|
209
|
+
if [ -d "$INSTAR_DIR/relationships" ]; then
|
|
210
|
+
REL_COUNT=$(ls -1 "$INSTAR_DIR/relationships"/*.json 2>/dev/null | wc -l | tr -d ' ')
|
|
211
|
+
if [ "$REL_COUNT" -gt "0" ]; then
|
|
212
|
+
CONTEXT="\${CONTEXT}You have \${REL_COUNT} tracked relationships in .instar/relationships/.\\n"
|
|
213
|
+
fi
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
# Self-discovery: check what capabilities are available
|
|
217
|
+
if [ -f "$INSTAR_DIR/config.json" ]; then
|
|
218
|
+
PORT=$(python3 -c "import json; print(json.load(open('$INSTAR_DIR/config.json')).get('port', 4040))" 2>/dev/null || echo "4040")
|
|
219
|
+
HEALTH=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:\${PORT}/health" 2>/dev/null)
|
|
220
|
+
if [ "$HEALTH" = "200" ]; then
|
|
221
|
+
CONTEXT="\${CONTEXT}Instar server is running on port \${PORT}. Query your capabilities: curl http://localhost:\${PORT}/capabilities\\n"
|
|
222
|
+
CONTEXT="\${CONTEXT}IMPORTANT: Before claiming you lack a capability, check /capabilities first.\\n"
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
[ -n "$CONTEXT" ] && echo "$CONTEXT"
|
|
227
|
+
`;
|
|
228
|
+
}
|
|
229
|
+
getDangerousCommandGuard() {
|
|
230
|
+
return `#!/bin/bash
|
|
231
|
+
# Dangerous command guard — blocks destructive operations.
|
|
232
|
+
INPUT="$1"
|
|
233
|
+
for pattern in "rm -rf /" "rm -rf ~" "rm -rf \\." "git push --force" "git push -f" "git reset --hard" "git clean -fd" "DROP TABLE" "DROP DATABASE" "TRUNCATE" "DELETE FROM" "> /dev/sda" "mkfs\\." "dd if=" ":(){:|:&};:"; do
|
|
234
|
+
if echo "$INPUT" | grep -qi "$pattern"; then
|
|
235
|
+
echo "BLOCKED: Potentially destructive command detected: $pattern"
|
|
236
|
+
echo "If you genuinely need to run this, ask the user for explicit confirmation first."
|
|
237
|
+
exit 2
|
|
238
|
+
fi
|
|
239
|
+
done
|
|
240
|
+
`;
|
|
241
|
+
}
|
|
242
|
+
getGroundingBeforeMessaging() {
|
|
243
|
+
return `#!/bin/bash
|
|
244
|
+
# Grounding before messaging — Security Through Identity.
|
|
245
|
+
INPUT="$1"
|
|
246
|
+
if echo "$INPUT" | grep -qE "(telegram-reply|send-email|send-message|POST.*/telegram/reply)"; then
|
|
247
|
+
INSTAR_DIR="\${CLAUDE_PROJECT_DIR:-.}/.instar"
|
|
248
|
+
if [ -f "$INSTAR_DIR/AGENT.md" ]; then
|
|
249
|
+
echo "Before sending this message, remember who you are."
|
|
250
|
+
echo "Re-read .instar/AGENT.md if you haven't recently."
|
|
251
|
+
echo "Security Through Identity: An agent that knows itself is harder to compromise."
|
|
252
|
+
fi
|
|
253
|
+
fi
|
|
254
|
+
`;
|
|
255
|
+
}
|
|
256
|
+
getCompactionRecovery() {
|
|
257
|
+
return `#!/bin/bash
|
|
258
|
+
# Compaction recovery — re-injects identity when Claude's context compresses.
|
|
259
|
+
INSTAR_DIR="\${CLAUDE_PROJECT_DIR:-.}/.instar"
|
|
260
|
+
if [ -f "$INSTAR_DIR/AGENT.md" ]; then
|
|
261
|
+
AGENT_NAME=$(head -5 "$INSTAR_DIR/AGENT.md" | grep -iE "name|I am|My name" | head -1)
|
|
262
|
+
[ -n "$AGENT_NAME" ] && echo "Identity reminder: $AGENT_NAME"
|
|
263
|
+
echo "Read .instar/AGENT.md and .instar/MEMORY.md to restore full context."
|
|
264
|
+
fi
|
|
265
|
+
`;
|
|
266
|
+
}
|
|
267
|
+
getTelegramReplyScript() {
|
|
268
|
+
const port = this.config.port;
|
|
269
|
+
return `#!/bin/bash
|
|
270
|
+
# telegram-reply.sh — Send a message back to a Telegram topic via instar server.
|
|
271
|
+
#
|
|
272
|
+
# Usage:
|
|
273
|
+
# .claude/scripts/telegram-reply.sh TOPIC_ID "message text"
|
|
274
|
+
# echo "message text" | .claude/scripts/telegram-reply.sh TOPIC_ID
|
|
275
|
+
# cat <<'EOF' | .claude/scripts/telegram-reply.sh TOPIC_ID
|
|
276
|
+
# Multi-line message here
|
|
277
|
+
# EOF
|
|
278
|
+
|
|
279
|
+
TOPIC_ID="$1"
|
|
280
|
+
shift
|
|
281
|
+
|
|
282
|
+
if [ -z "$TOPIC_ID" ]; then
|
|
283
|
+
echo "Usage: telegram-reply.sh TOPIC_ID [message]" >&2
|
|
284
|
+
exit 1
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
# Read message from args or stdin
|
|
288
|
+
if [ $# -gt 0 ]; then
|
|
289
|
+
MSG="$*"
|
|
290
|
+
else
|
|
291
|
+
MSG="$(cat)"
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
if [ -z "$MSG" ]; then
|
|
295
|
+
echo "No message provided" >&2
|
|
296
|
+
exit 1
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
PORT="\${INSTAR_PORT:-${port}}"
|
|
300
|
+
|
|
301
|
+
# Escape for JSON
|
|
302
|
+
JSON_MSG=$(printf '%s' "$MSG" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))' 2>/dev/null)
|
|
303
|
+
if [ -z "$JSON_MSG" ]; then
|
|
304
|
+
JSON_MSG="$(printf '%s' "$MSG" | sed 's/\\\\\\\\/\\\\\\\\\\\\\\\\/g; s/"/\\\\\\\\"/g' | sed ':a;N;$!ba;s/\\\\n/\\\\\\\\n/g')"
|
|
305
|
+
JSON_MSG="\\"$JSON_MSG\\""
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
RESPONSE=$(curl -s -w "\\n%{http_code}" -X POST "http://localhost:\${PORT}/telegram/reply/\${TOPIC_ID}" \\
|
|
309
|
+
-H 'Content-Type: application/json' \\
|
|
310
|
+
-d "{\\"text\\":\${JSON_MSG}}")
|
|
311
|
+
|
|
312
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
|
313
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
314
|
+
|
|
315
|
+
if [ "$HTTP_CODE" = "200" ]; then
|
|
316
|
+
echo "Sent $(echo "$MSG" | wc -c | tr -d ' ') chars to topic $TOPIC_ID"
|
|
317
|
+
else
|
|
318
|
+
echo "Failed (HTTP $HTTP_CODE): $BODY" >&2
|
|
319
|
+
exit 1
|
|
320
|
+
fi
|
|
321
|
+
`;
|
|
322
|
+
}
|
|
323
|
+
getHealthWatchdog() {
|
|
324
|
+
const port = this.config.port;
|
|
325
|
+
const projectName = this.config.projectName;
|
|
326
|
+
const escapedProjectDir = this.config.projectDir.replace(/'/g, "'\\''");
|
|
327
|
+
return `#!/bin/bash
|
|
328
|
+
# health-watchdog.sh — Monitor instar server and auto-recover.
|
|
329
|
+
# Install as cron: */5 * * * * '${path.join(this.config.projectDir, '.claude/scripts/health-watchdog.sh').replace(/'/g, "'\\''")}'
|
|
330
|
+
|
|
331
|
+
PORT="${port}"
|
|
332
|
+
SERVER_SESSION="${projectName}-server"
|
|
333
|
+
PROJECT_DIR='${escapedProjectDir}'
|
|
334
|
+
TMUX_PATH=$(which tmux 2>/dev/null || echo "/opt/homebrew/bin/tmux")
|
|
335
|
+
|
|
336
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:\${PORT}/health" 2>/dev/null)
|
|
337
|
+
if [ "$HTTP_CODE" = "200" ]; then exit 0; fi
|
|
338
|
+
|
|
339
|
+
echo "[\$(date -Iseconds)] Server not responding. Restarting..."
|
|
340
|
+
$TMUX_PATH kill-session -t "=\${SERVER_SESSION}" 2>/dev/null
|
|
341
|
+
sleep 2
|
|
342
|
+
cd "$PROJECT_DIR" && npx instar server start
|
|
343
|
+
echo "[\$(date -Iseconds)] Server restart initiated"
|
|
344
|
+
`;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
//# sourceMappingURL=PostUpdateMigrator.js.map
|
|
@@ -17,11 +17,23 @@ export interface RollbackResult {
|
|
|
17
17
|
restoredVersion: string;
|
|
18
18
|
message: string;
|
|
19
19
|
}
|
|
20
|
+
export interface UpdateCheckerConfig {
|
|
21
|
+
stateDir: string;
|
|
22
|
+
/** Required for post-update migrations */
|
|
23
|
+
projectDir?: string;
|
|
24
|
+
/** Server port for capability URLs in migrated files */
|
|
25
|
+
port?: number;
|
|
26
|
+
/** Whether Telegram is configured */
|
|
27
|
+
hasTelegram?: boolean;
|
|
28
|
+
/** Project name for migrated files */
|
|
29
|
+
projectName?: string;
|
|
30
|
+
}
|
|
20
31
|
export declare class UpdateChecker {
|
|
21
32
|
private stateDir;
|
|
22
33
|
private stateFile;
|
|
23
34
|
private rollbackFile;
|
|
24
|
-
|
|
35
|
+
private migratorConfig;
|
|
36
|
+
constructor(config: string | UpdateCheckerConfig);
|
|
25
37
|
/**
|
|
26
38
|
* Check npm for the latest version, fetch changelog, and compare to installed.
|
|
27
39
|
*/
|
|
@@ -13,16 +13,31 @@
|
|
|
13
13
|
import { execFile } from 'node:child_process';
|
|
14
14
|
import fs from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
|
-
import {
|
|
16
|
+
import { PostUpdateMigrator } from './PostUpdateMigrator.js';
|
|
17
17
|
const GITHUB_RELEASES_URL = 'https://api.github.com/repos/SageMindAI/instar/releases';
|
|
18
18
|
export class UpdateChecker {
|
|
19
19
|
stateDir;
|
|
20
20
|
stateFile;
|
|
21
21
|
rollbackFile;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
migratorConfig;
|
|
23
|
+
constructor(config) {
|
|
24
|
+
// Backwards-compatible: accept plain string (stateDir) or config object
|
|
25
|
+
if (typeof config === 'string') {
|
|
26
|
+
this.stateDir = config;
|
|
27
|
+
this.migratorConfig = null;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
this.stateDir = config.stateDir;
|
|
31
|
+
this.migratorConfig = config.projectDir ? {
|
|
32
|
+
projectDir: config.projectDir,
|
|
33
|
+
stateDir: config.stateDir,
|
|
34
|
+
port: config.port ?? 4040,
|
|
35
|
+
hasTelegram: config.hasTelegram ?? false,
|
|
36
|
+
projectName: config.projectName ?? 'agent',
|
|
37
|
+
} : null;
|
|
38
|
+
}
|
|
39
|
+
this.stateFile = path.join(this.stateDir, 'state', 'update-check.json');
|
|
40
|
+
this.rollbackFile = path.join(this.stateDir, 'state', 'update-rollback.json');
|
|
26
41
|
}
|
|
27
42
|
/**
|
|
28
43
|
* Check npm for the latest version, fetch changelog, and compare to installed.
|
|
@@ -111,13 +126,22 @@ export class UpdateChecker {
|
|
|
111
126
|
// Save rollback info on successful update
|
|
112
127
|
if (success) {
|
|
113
128
|
this.saveRollbackInfo(previousVersion, newVersion);
|
|
114
|
-
|
|
129
|
+
}
|
|
130
|
+
// Post-update migration: upgrade hooks, CLAUDE.md, scripts
|
|
131
|
+
let migrationSummary = '';
|
|
132
|
+
if (success && this.migratorConfig) {
|
|
115
133
|
try {
|
|
116
|
-
const
|
|
117
|
-
|
|
134
|
+
const migrator = new PostUpdateMigrator(this.migratorConfig);
|
|
135
|
+
const migration = migrator.migrate();
|
|
136
|
+
if (migration.upgraded.length > 0) {
|
|
137
|
+
migrationSummary = ` Intelligence download: ${migration.upgraded.length} files upgraded (${migration.upgraded.join(', ')}).`;
|
|
138
|
+
}
|
|
139
|
+
if (migration.errors.length > 0) {
|
|
140
|
+
migrationSummary += ` Migration warnings: ${migration.errors.join('; ')}.`;
|
|
141
|
+
}
|
|
118
142
|
}
|
|
119
|
-
catch {
|
|
120
|
-
|
|
143
|
+
catch (err) {
|
|
144
|
+
migrationSummary = ` Post-update migration failed: ${err instanceof Error ? err.message : String(err)}.`;
|
|
121
145
|
}
|
|
122
146
|
}
|
|
123
147
|
return {
|
|
@@ -125,7 +149,7 @@ export class UpdateChecker {
|
|
|
125
149
|
previousVersion,
|
|
126
150
|
newVersion,
|
|
127
151
|
message: success
|
|
128
|
-
? `Updated from v${previousVersion} to v${newVersion}
|
|
152
|
+
? `Updated from v${previousVersion} to v${newVersion}.${migrationSummary} ${info.changeSummary || 'Restart to use the new version.'}`
|
|
129
153
|
: `Update command ran but version didn't change (still v${previousVersion}). May need manual intervention.`,
|
|
130
154
|
restartNeeded: success,
|
|
131
155
|
healthCheck: 'skipped', // Can't check health until after restart
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,9 @@ export { RelationshipManager } from './core/RelationshipManager.js';
|
|
|
9
9
|
export { FeedbackManager } from './core/FeedbackManager.js';
|
|
10
10
|
export { DispatchManager } from './core/DispatchManager.js';
|
|
11
11
|
export { UpdateChecker } from './core/UpdateChecker.js';
|
|
12
|
-
export type { RollbackResult } from './core/UpdateChecker.js';
|
|
12
|
+
export type { RollbackResult, UpdateCheckerConfig } from './core/UpdateChecker.js';
|
|
13
|
+
export { PostUpdateMigrator } from './core/PostUpdateMigrator.js';
|
|
14
|
+
export type { MigrationResult, MigratorConfig } from './core/PostUpdateMigrator.js';
|
|
13
15
|
export { loadConfig, detectTmuxPath, detectClaudePath, detectProjectDir, ensureStateDir } from './core/Config.js';
|
|
14
16
|
export { UserManager } from './users/UserManager.js';
|
|
15
17
|
export { JobScheduler } from './scheduler/JobScheduler.js';
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export { RelationshipManager } from './core/RelationshipManager.js';
|
|
|
10
10
|
export { FeedbackManager } from './core/FeedbackManager.js';
|
|
11
11
|
export { DispatchManager } from './core/DispatchManager.js';
|
|
12
12
|
export { UpdateChecker } from './core/UpdateChecker.js';
|
|
13
|
+
export { PostUpdateMigrator } from './core/PostUpdateMigrator.js';
|
|
13
14
|
export { loadConfig, detectTmuxPath, detectClaudePath, detectProjectDir, ensureStateDir } from './core/Config.js';
|
|
14
15
|
// Users
|
|
15
16
|
export { UserManager } from './users/UserManager.js';
|
|
@@ -29,6 +29,7 @@ ${identity.personality}
|
|
|
29
29
|
3. **Own the outcome.** Implementation isn't done when code compiles. It's done when the feature is running, tested, and verified.
|
|
30
30
|
4. **Be honest about limits.** If I can't do something, I say so clearly. I don't fabricate experience or claim capabilities I don't have.
|
|
31
31
|
5. **Infrastructure over improvisation.** If I solve a problem twice, I make it a script, a job, or a documented pattern.
|
|
32
|
+
6. **Use my own tools first.** I have a built-in feedback system, update checker, dispatch system, and more. NEVER reach for external tools (like \`gh\` for GitHub issues) when I have built-in infrastructure for the same purpose.
|
|
32
33
|
|
|
33
34
|
## Who I Work With
|
|
34
35
|
|
|
@@ -119,8 +120,28 @@ This project uses instar for persistent agent capabilities. I am not a vanilla C
|
|
|
119
120
|
- Server: \`instar server start\` (port ${port})
|
|
120
121
|
- Health: \`curl http://localhost:${port}/health\`
|
|
121
122
|
|
|
123
|
+
### Reporting Issues & Feedback (IMPORTANT — Read This First)
|
|
124
|
+
|
|
125
|
+
**I have a built-in feedback system. I do NOT use GitHub, \`gh\`, or any external issue tracker.**
|
|
126
|
+
|
|
127
|
+
When something isn't working with instar, or the user wants a feature, I use my own feedback API:
|
|
128
|
+
|
|
129
|
+
\`\`\`bash
|
|
130
|
+
curl -X POST http://localhost:${port}/feedback \\
|
|
131
|
+
-H 'Content-Type: application/json' \\
|
|
132
|
+
-d '{"type":"bug","title":"Short description","description":"Full details with context"}'
|
|
133
|
+
\`\`\`
|
|
134
|
+
|
|
135
|
+
This routes feedback to the Instar maintainers automatically. Valid types: \`bug\`, \`feature\`, \`improvement\`, \`question\`.
|
|
136
|
+
|
|
137
|
+
**NEVER use \`gh issue\`, \`gh api\`, or GitHub CLI to file issues.** The feedback API is my channel. It stores a local receipt, forwards upstream, and tracks delivery. I can also:
|
|
138
|
+
- View submitted feedback: \`curl http://localhost:${port}/feedback\`
|
|
139
|
+
- Retry failed forwards: \`curl -X POST http://localhost:${port}/feedback/retry\`
|
|
140
|
+
|
|
122
141
|
### Capabilities
|
|
123
142
|
|
|
143
|
+
**Feedback System** — Report bugs, request features, suggest improvements. All via \`POST /feedback\`.
|
|
144
|
+
|
|
124
145
|
**Job Scheduler** — Run tasks on a schedule. Jobs in \`.instar/jobs.json\`.
|
|
125
146
|
- View: \`curl http://localhost:${port}/jobs\`
|
|
126
147
|
- Trigger: \`curl -X POST http://localhost:${port}/jobs/SLUG/trigger\`
|
|
@@ -134,6 +155,16 @@ This project uses instar for persistent agent capabilities. I am not a vanilla C
|
|
|
134
155
|
|
|
135
156
|
**Scripts** — Reusable capabilities in \`.claude/scripts/\`.
|
|
136
157
|
|
|
158
|
+
### Self-Discovery (Know Before You Claim)
|
|
159
|
+
|
|
160
|
+
Before EVER saying "I don't have", "I can't", or "this isn't available" — check what actually exists:
|
|
161
|
+
|
|
162
|
+
\`\`\`bash
|
|
163
|
+
curl http://localhost:${port}/capabilities
|
|
164
|
+
\`\`\`
|
|
165
|
+
|
|
166
|
+
This returns your full capability matrix: scripts, hooks, Telegram status, jobs, relationships, and more. It is the source of truth about what you can do. **Never hallucinate about missing capabilities — verify first.**
|
|
167
|
+
|
|
137
168
|
### Building New Capabilities
|
|
138
169
|
|
|
139
170
|
When asked for something I can't do yet, I build it:
|
|
@@ -162,6 +193,8 @@ When asked for something I can't do yet, I build it:
|
|
|
162
193
|
|
|
163
194
|
### Anti-Patterns to Override
|
|
164
195
|
|
|
196
|
+
**"Use GitHub for Issues"** — NEVER use \`gh issue\`, \`gh api\`, or GitHub to report bugs or request features. I have a built-in feedback API (\`POST /feedback\`) that handles this. GitHub is not my channel — the feedback system is.
|
|
197
|
+
|
|
165
198
|
**"Escalate to Human"** — 5 minutes of research almost always reveals a solution. "I don't know how" is a research prompt, not a stopping point.
|
|
166
199
|
|
|
167
200
|
**"Ask Permission"** — If the user said "do X" and the next step is obvious, do it. Only pause for destructive or irreversible actions.
|
package/dist/server/routes.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import { Router } from 'express';
|
|
8
8
|
import { execFileSync } from 'node:child_process';
|
|
9
9
|
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
10
12
|
import { rateLimiter } from './middleware.js';
|
|
11
13
|
// Validation patterns for route parameters
|
|
12
14
|
const SESSION_NAME_RE = /^[a-zA-Z0-9_-]{1,200}$/;
|
|
@@ -60,6 +62,102 @@ export function createRoutes(ctx) {
|
|
|
60
62
|
scheduler: schedulerStatus,
|
|
61
63
|
});
|
|
62
64
|
});
|
|
65
|
+
// ── Capabilities (Self-Discovery) ──────────────────────────────
|
|
66
|
+
//
|
|
67
|
+
// Returns a structured self-portrait of what this agent has available.
|
|
68
|
+
// Agents should query this at session start rather than guessing
|
|
69
|
+
// about what infrastructure exists.
|
|
70
|
+
router.get('/capabilities', (_req, res) => {
|
|
71
|
+
const projectDir = ctx.config.projectDir;
|
|
72
|
+
const stateDir = ctx.config.stateDir;
|
|
73
|
+
// Identity files
|
|
74
|
+
const identityFiles = {
|
|
75
|
+
'AGENT.md': fs.existsSync(path.join(stateDir, 'AGENT.md')),
|
|
76
|
+
'USER.md': fs.existsSync(path.join(stateDir, 'USER.md')),
|
|
77
|
+
'MEMORY.md': fs.existsSync(path.join(stateDir, 'MEMORY.md')),
|
|
78
|
+
};
|
|
79
|
+
// Scripts
|
|
80
|
+
const scriptsDir = path.join(projectDir, '.claude', 'scripts');
|
|
81
|
+
let scripts = [];
|
|
82
|
+
if (fs.existsSync(scriptsDir)) {
|
|
83
|
+
try {
|
|
84
|
+
scripts = fs.readdirSync(scriptsDir).filter(f => !f.startsWith('.'));
|
|
85
|
+
}
|
|
86
|
+
catch { /* permission error, etc. */ }
|
|
87
|
+
}
|
|
88
|
+
// Hooks
|
|
89
|
+
const hooksDir = path.join(stateDir, 'hooks');
|
|
90
|
+
let hooks = [];
|
|
91
|
+
if (fs.existsSync(hooksDir)) {
|
|
92
|
+
try {
|
|
93
|
+
hooks = fs.readdirSync(hooksDir).filter(f => !f.startsWith('.'));
|
|
94
|
+
}
|
|
95
|
+
catch { /* permission error, etc. */ }
|
|
96
|
+
}
|
|
97
|
+
// Telegram
|
|
98
|
+
const hasTelegramConfig = ctx.config.messaging.some(m => m.type === 'telegram' && m.enabled);
|
|
99
|
+
const hasTelegramReplyScript = scripts.includes('telegram-reply.sh');
|
|
100
|
+
const telegram = {
|
|
101
|
+
configured: hasTelegramConfig,
|
|
102
|
+
replyScript: hasTelegramReplyScript,
|
|
103
|
+
adapter: !!ctx.telegram,
|
|
104
|
+
bidirectional: hasTelegramConfig && hasTelegramReplyScript && !!ctx.telegram,
|
|
105
|
+
};
|
|
106
|
+
// Jobs
|
|
107
|
+
let jobCount = 0;
|
|
108
|
+
let jobSlugs = [];
|
|
109
|
+
if (ctx.scheduler) {
|
|
110
|
+
const jobs = ctx.scheduler.getJobs();
|
|
111
|
+
jobCount = jobs.length;
|
|
112
|
+
jobSlugs = jobs.map(j => j.slug);
|
|
113
|
+
}
|
|
114
|
+
// Relationships
|
|
115
|
+
const relationshipsDir = ctx.config.relationships?.relationshipsDir;
|
|
116
|
+
let relationshipCount = 0;
|
|
117
|
+
if (relationshipsDir && fs.existsSync(relationshipsDir)) {
|
|
118
|
+
try {
|
|
119
|
+
relationshipCount = fs.readdirSync(relationshipsDir)
|
|
120
|
+
.filter(f => f.endsWith('.json')).length;
|
|
121
|
+
}
|
|
122
|
+
catch { /* ignore */ }
|
|
123
|
+
}
|
|
124
|
+
// Users
|
|
125
|
+
let userCount = 0;
|
|
126
|
+
const usersFile = path.join(stateDir, 'users.json');
|
|
127
|
+
if (fs.existsSync(usersFile)) {
|
|
128
|
+
try {
|
|
129
|
+
const users = JSON.parse(fs.readFileSync(usersFile, 'utf-8'));
|
|
130
|
+
if (Array.isArray(users))
|
|
131
|
+
userCount = users.length;
|
|
132
|
+
}
|
|
133
|
+
catch { /* ignore */ }
|
|
134
|
+
}
|
|
135
|
+
res.json({
|
|
136
|
+
project: ctx.config.projectName,
|
|
137
|
+
version: ctx.config.version || '0.0.0',
|
|
138
|
+
port: ctx.config.port,
|
|
139
|
+
identity: identityFiles,
|
|
140
|
+
scripts,
|
|
141
|
+
hooks,
|
|
142
|
+
telegram,
|
|
143
|
+
scheduler: {
|
|
144
|
+
enabled: ctx.config.scheduler.enabled,
|
|
145
|
+
jobCount,
|
|
146
|
+
jobSlugs,
|
|
147
|
+
},
|
|
148
|
+
relationships: {
|
|
149
|
+
enabled: !!ctx.config.relationships,
|
|
150
|
+
count: relationshipCount,
|
|
151
|
+
},
|
|
152
|
+
feedback: {
|
|
153
|
+
enabled: !!ctx.config.feedback?.enabled,
|
|
154
|
+
},
|
|
155
|
+
users: {
|
|
156
|
+
count: userCount,
|
|
157
|
+
},
|
|
158
|
+
monitoring: ctx.config.monitoring,
|
|
159
|
+
});
|
|
160
|
+
});
|
|
63
161
|
// ── Sessions ────────────────────────────────────────────────────
|
|
64
162
|
// Literal routes BEFORE parameterized routes to avoid capture
|
|
65
163
|
router.get('/sessions/tmux', (_req, res) => {
|