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
- if (!header || !header.startsWith('Bearer ')) {
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();
@@ -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: ctx.tunnel?.getExternalUrl(`/view/${v.id}`) ?? null,
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: ctx.tunnel?.getExternalUrl(`/view/${updated.id}`) ?? null,
981
+ tunnelUrl: viewTunnelUrl(updated.id),
973
982
  updatedAt: updated.updatedAt,
974
983
  });
975
984
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",