instar 0.6.2 → 0.6.4
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.
|
@@ -163,6 +163,39 @@ Strip the \`[telegram:N]\` prefix before interpreting the message. Respond natur
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
|
+
// Private Viewer + Tunnel section
|
|
167
|
+
if (!content.includes('Private Viewing') && !content.includes('POST /view')) {
|
|
168
|
+
const section = `
|
|
169
|
+
**Private Viewing** — Render markdown as auth-gated HTML pages, accessible only through the agent's server (local or via tunnel).
|
|
170
|
+
- Create: \`curl -X POST http://localhost:${port}/view -H 'Content-Type: application/json' -d '{"title":"Report","markdown":"# Private content"}'\`
|
|
171
|
+
- View (HTML): Open \`http://localhost:${port}/view/VIEW_ID\` in a browser
|
|
172
|
+
- List: \`curl http://localhost:${port}/views\`
|
|
173
|
+
- Update: \`curl -X PUT http://localhost:${port}/view/VIEW_ID -H 'Content-Type: application/json' -d '{"title":"Updated","markdown":"# New content"}'\`
|
|
174
|
+
- Delete: \`curl -X DELETE http://localhost:${port}/view/VIEW_ID\`
|
|
175
|
+
|
|
176
|
+
**Use private views for sensitive content. Use Telegraph for public content.**
|
|
177
|
+
|
|
178
|
+
**Cloudflare Tunnel** — Expose the local server to the internet via Cloudflare. Enables remote access to private views, the API, and file serving.
|
|
179
|
+
- Status: \`curl http://localhost:${port}/tunnel\`
|
|
180
|
+
- Configure in \`.instar/config.json\`: \`{"tunnel": {"enabled": true, "type": "quick"}}\`
|
|
181
|
+
- Quick tunnels (default): Zero-config, ephemeral URL (*.trycloudflare.com), no account needed
|
|
182
|
+
- Named tunnels: Persistent custom domain, requires token from Cloudflare dashboard
|
|
183
|
+
- When a tunnel is running, private view responses include a \`tunnelUrl\` with auth token for browser-clickable access
|
|
184
|
+
`;
|
|
185
|
+
// Insert after Publishing section or before Scripts section
|
|
186
|
+
const publishIdx = content.indexOf('**Scripts**');
|
|
187
|
+
if (publishIdx >= 0) {
|
|
188
|
+
content = content.slice(0, publishIdx) + section + '\n' + content.slice(publishIdx);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
content += '\n' + section;
|
|
192
|
+
}
|
|
193
|
+
patched = true;
|
|
194
|
+
result.upgraded.push('CLAUDE.md: added Private Viewer + Cloudflare Tunnel section');
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
result.skipped.push('CLAUDE.md: Private Viewer section already present');
|
|
198
|
+
}
|
|
166
199
|
if (patched) {
|
|
167
200
|
try {
|
|
168
201
|
fs.writeFileSync(claudeMdPath, content);
|
|
@@ -283,6 +316,12 @@ if [ -f "$INSTAR_DIR/config.json" ]; then
|
|
|
283
316
|
if [ "$HEALTH" = "200" ]; then
|
|
284
317
|
CONTEXT="\${CONTEXT}Instar server is running on port \${PORT}. Query your capabilities: curl http://localhost:\${PORT}/capabilities\\n"
|
|
285
318
|
CONTEXT="\${CONTEXT}IMPORTANT: Before claiming you lack a capability, check /capabilities first.\\n"
|
|
319
|
+
# Check for new features
|
|
320
|
+
CAPS=$(curl -s "http://localhost:\${PORT}/capabilities" 2>/dev/null)
|
|
321
|
+
if echo "$CAPS" | python3 -c "import sys,json; d=json.load(sys.stdin); print('tunnel' if d.get('tunnel',{}).get('enabled') else '')" 2>/dev/null | grep -q tunnel; then
|
|
322
|
+
TUNNEL_URL=$(echo "$CAPS" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tunnel',{}).get('url',''))" 2>/dev/null)
|
|
323
|
+
[ -n "$TUNNEL_URL" ] && CONTEXT="\${CONTEXT}Cloudflare Tunnel active: $TUNNEL_URL — your server is accessible remotely.\\n"
|
|
324
|
+
fi
|
|
286
325
|
fi
|
|
287
326
|
fi
|
|
288
327
|
|
|
@@ -32,12 +32,21 @@ export function authMiddleware(authToken) {
|
|
|
32
32
|
next();
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
|
+
// Accept token from Authorization header or ?token= query parameter.
|
|
36
|
+
// Query parameter enables browser-friendly access to rendered views.
|
|
35
37
|
const header = req.headers.authorization;
|
|
36
|
-
|
|
38
|
+
const queryToken = typeof req.query.token === 'string' ? req.query.token : null;
|
|
39
|
+
let token;
|
|
40
|
+
if (header?.startsWith('Bearer ')) {
|
|
41
|
+
token = header.slice(7);
|
|
42
|
+
}
|
|
43
|
+
else if (queryToken) {
|
|
44
|
+
token = queryToken;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
37
47
|
res.status(401).json({ error: 'Missing or invalid Authorization header' });
|
|
38
48
|
return;
|
|
39
49
|
}
|
|
40
|
-
const token = header.slice(7);
|
|
41
50
|
// Hash both sides so lengths are always equal — prevents timing leak of token length
|
|
42
51
|
const ha = createHash('sha256').update(token).digest();
|
|
43
52
|
const hb = createHash('sha256').update(authToken).digest();
|
package/dist/server/routes.js
CHANGED
|
@@ -880,6 +880,16 @@ export function createRoutes(ctx) {
|
|
|
880
880
|
});
|
|
881
881
|
// ── Private Views (auth-gated rendered markdown) ────────────────
|
|
882
882
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
883
|
+
/** Build a browser-clickable tunnel URL with token embedded as query param */
|
|
884
|
+
function viewTunnelUrl(viewId) {
|
|
885
|
+
const base = ctx.tunnel?.getExternalUrl(`/view/${viewId}`);
|
|
886
|
+
if (!base)
|
|
887
|
+
return null;
|
|
888
|
+
if (ctx.config.authToken) {
|
|
889
|
+
return `${base}?token=${encodeURIComponent(ctx.config.authToken)}`;
|
|
890
|
+
}
|
|
891
|
+
return base;
|
|
892
|
+
}
|
|
883
893
|
router.post('/view', (req, res) => {
|
|
884
894
|
if (!ctx.viewer) {
|
|
885
895
|
res.status(503).json({ error: 'Private viewer not configured' });
|
|
@@ -899,12 +909,11 @@ export function createRoutes(ctx) {
|
|
|
899
909
|
return;
|
|
900
910
|
}
|
|
901
911
|
const view = ctx.viewer.create(title, markdown);
|
|
902
|
-
const tunnelUrl = ctx.tunnel?.getExternalUrl(`/view/${view.id}`) ?? null;
|
|
903
912
|
res.status(201).json({
|
|
904
913
|
id: view.id,
|
|
905
914
|
title: view.title,
|
|
906
915
|
localUrl: `/view/${view.id}`,
|
|
907
|
-
tunnelUrl,
|
|
916
|
+
tunnelUrl: viewTunnelUrl(view.id),
|
|
908
917
|
createdAt: view.createdAt,
|
|
909
918
|
});
|
|
910
919
|
});
|
|
@@ -936,7 +945,7 @@ export function createRoutes(ctx) {
|
|
|
936
945
|
id: v.id,
|
|
937
946
|
title: v.title,
|
|
938
947
|
localUrl: `/view/${v.id}`,
|
|
939
|
-
tunnelUrl:
|
|
948
|
+
tunnelUrl: viewTunnelUrl(v.id),
|
|
940
949
|
createdAt: v.createdAt,
|
|
941
950
|
updatedAt: v.updatedAt,
|
|
942
951
|
}));
|
|
@@ -969,7 +978,7 @@ export function createRoutes(ctx) {
|
|
|
969
978
|
id: updated.id,
|
|
970
979
|
title: updated.title,
|
|
971
980
|
localUrl: `/view/${updated.id}`,
|
|
972
|
-
tunnelUrl:
|
|
981
|
+
tunnelUrl: viewTunnelUrl(updated.id),
|
|
973
982
|
updatedAt: updated.updatedAt,
|
|
974
983
|
});
|
|
975
984
|
});
|