surfcode 0.0.0-stage → 0.1.1

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.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +325 -2
  3. package/assets/surfcode-logo.gif +0 -0
  4. package/dist/cli/auth.js +315 -0
  5. package/dist/cli/auth.js.map +1 -0
  6. package/dist/cli/config.js +228 -0
  7. package/dist/cli/config.js.map +1 -0
  8. package/dist/cli/headless.js +355 -0
  9. package/dist/cli/headless.js.map +1 -0
  10. package/dist/cli/inspect.js +462 -0
  11. package/dist/cli/inspect.js.map +1 -0
  12. package/dist/cli/interactive.js +130 -0
  13. package/dist/cli/interactive.js.map +1 -0
  14. package/dist/cli/sessions.js +170 -0
  15. package/dist/cli/sessions.js.map +1 -0
  16. package/dist/cli/sync.js +190 -0
  17. package/dist/cli/sync.js.map +1 -0
  18. package/dist/cli/util.js +345 -0
  19. package/dist/cli/util.js.map +1 -0
  20. package/dist/cli.js +485 -0
  21. package/dist/cli.js.map +1 -0
  22. package/dist/core/agent.js +298 -0
  23. package/dist/core/agent.js.map +1 -0
  24. package/dist/core/agents.js +55 -0
  25. package/dist/core/agents.js.map +1 -0
  26. package/dist/core/auth.js +107 -0
  27. package/dist/core/auth.js.map +1 -0
  28. package/dist/core/catalog.js +306 -0
  29. package/dist/core/catalog.js.map +1 -0
  30. package/dist/core/chat/anthropic.js +222 -0
  31. package/dist/core/chat/anthropic.js.map +1 -0
  32. package/dist/core/chat/common.js +367 -0
  33. package/dist/core/chat/common.js.map +1 -0
  34. package/dist/core/chat/google.js +180 -0
  35. package/dist/core/chat/google.js.map +1 -0
  36. package/dist/core/chat/index.js +80 -0
  37. package/dist/core/chat/index.js.map +1 -0
  38. package/dist/core/chat/openai.js +164 -0
  39. package/dist/core/chat/openai.js.map +1 -0
  40. package/dist/core/chat/types.js +2 -0
  41. package/dist/core/chat/types.js.map +1 -0
  42. package/dist/core/clipboard.js +34 -0
  43. package/dist/core/clipboard.js.map +1 -0
  44. package/dist/core/compact.js +39 -0
  45. package/dist/core/compact.js.map +1 -0
  46. package/dist/core/config.js +211 -0
  47. package/dist/core/config.js.map +1 -0
  48. package/dist/core/delegate.js +184 -0
  49. package/dist/core/delegate.js.map +1 -0
  50. package/dist/core/effort.js +80 -0
  51. package/dist/core/effort.js.map +1 -0
  52. package/dist/core/errors.js +32 -0
  53. package/dist/core/errors.js.map +1 -0
  54. package/dist/core/free.js +21 -0
  55. package/dist/core/free.js.map +1 -0
  56. package/dist/core/github.js +445 -0
  57. package/dist/core/github.js.map +1 -0
  58. package/dist/core/paths.js +51 -0
  59. package/dist/core/paths.js.map +1 -0
  60. package/dist/core/prompt.js +95 -0
  61. package/dist/core/prompt.js.map +1 -0
  62. package/dist/core/provider.js +298 -0
  63. package/dist/core/provider.js.map +1 -0
  64. package/dist/core/session.js +159 -0
  65. package/dist/core/session.js.map +1 -0
  66. package/dist/core/sync.js +126 -0
  67. package/dist/core/sync.js.map +1 -0
  68. package/dist/core/types.js +2 -0
  69. package/dist/core/types.js.map +1 -0
  70. package/dist/core/usage.js +127 -0
  71. package/dist/core/usage.js.map +1 -0
  72. package/dist/core/workspace.js +70 -0
  73. package/dist/core/workspace.js.map +1 -0
  74. package/dist/data/models.json +1 -0
  75. package/dist/sync/server.js +179 -0
  76. package/dist/sync/server.js.map +1 -0
  77. package/dist/tools/diff.js +21 -0
  78. package/dist/tools/diff.js.map +1 -0
  79. package/dist/tools/files.js +227 -0
  80. package/dist/tools/files.js.map +1 -0
  81. package/dist/tools/github.js +238 -0
  82. package/dist/tools/github.js.map +1 -0
  83. package/dist/tools/index.js +34 -0
  84. package/dist/tools/index.js.map +1 -0
  85. package/dist/tools/search.js +231 -0
  86. package/dist/tools/search.js.map +1 -0
  87. package/dist/tools/shell.js +118 -0
  88. package/dist/tools/shell.js.map +1 -0
  89. package/dist/tools/todos.js +50 -0
  90. package/dist/tools/todos.js.map +1 -0
  91. package/dist/tools/webfetch.js +108 -0
  92. package/dist/tools/webfetch.js.map +1 -0
  93. package/dist/tui/App.js +2984 -0
  94. package/dist/tui/App.js.map +1 -0
  95. package/dist/tui/ansi.js +151 -0
  96. package/dist/tui/ansi.js.map +1 -0
  97. package/dist/tui/commands.js +61 -0
  98. package/dist/tui/commands.js.map +1 -0
  99. package/dist/tui/explorer.js +152 -0
  100. package/dist/tui/explorer.js.map +1 -0
  101. package/dist/tui/icons.js +21 -0
  102. package/dist/tui/icons.js.map +1 -0
  103. package/dist/tui/layout.js +324 -0
  104. package/dist/tui/layout.js.map +1 -0
  105. package/dist/tui/markdown.js +355 -0
  106. package/dist/tui/markdown.js.map +1 -0
  107. package/dist/tui/overlays.js +316 -0
  108. package/dist/tui/overlays.js.map +1 -0
  109. package/dist/tui/render.js +234 -0
  110. package/dist/tui/render.js.map +1 -0
  111. package/dist/tui/theme.js +148 -0
  112. package/dist/tui/theme.js.map +1 -0
  113. package/dist/tui/types.js +73 -0
  114. package/dist/tui/types.js.map +1 -0
  115. package/dist/version.js +3 -0
  116. package/dist/version.js.map +1 -0
  117. package/package.json +75 -4
@@ -0,0 +1,118 @@
1
+ import { spawn } from 'node:child_process';
2
+ import stripAnsi from 'strip-ansi';
3
+ import { SurfError } from '../core/errors.js';
4
+ import { clip, normalizeOutput } from '../core/workspace.js';
5
+ export const shellTool = {
6
+ name: 'bash',
7
+ description: 'Run a shell command in the workspace and return combined stdout/stderr. Use for builds, tests, git, installing dependencies, and other terminal tasks. Do not run interactive commands. Output is truncated.',
8
+ readOnly: false,
9
+ action: 'shell',
10
+ parameters: {
11
+ type: 'object',
12
+ properties: {
13
+ command: { type: 'string', description: 'Shell command to run.' },
14
+ timeout: { type: 'number', description: 'Timeout in milliseconds (default 120000, max 600000).' }
15
+ },
16
+ required: ['command']
17
+ },
18
+ async execute(args, context) {
19
+ const command = String(args.command ?? '').trim();
20
+ if (!command)
21
+ throw new SurfError('A command is required.');
22
+ const timeout = Math.max(1000, Math.min(Number(args.timeout) || 120_000, 600_000));
23
+ const started = Date.now();
24
+ const result = await runShell(command, context.cwd, timeout, context.signal);
25
+ const duration = ((Date.now() - started) / 1000).toFixed(1);
26
+ const combined = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join('\n');
27
+ const cleaned = stripAnsi(normalizeOutput(combined || '(no output)'));
28
+ const clipped = clip(cleaned, 60_000);
29
+ const exit = result.timedOut ? 'timed out' : `exit ${result.code ?? '?'}`;
30
+ const output = `$ ${command}\n(${exit} · ${duration}s)\n${clipped.text}`;
31
+ return {
32
+ output,
33
+ title: command,
34
+ isError: result.timedOut || (result.code ?? 0) !== 0,
35
+ display: {
36
+ kind: 'shell',
37
+ summary: `${result.timedOut ? 'timed out' : result.code === 0 ? 'ok' : `exit ${result.code}`} · ${duration}s`,
38
+ lines: cleaned.split('\n').length,
39
+ truncated: clipped.truncated
40
+ }
41
+ };
42
+ }
43
+ };
44
+ function runShell(command, cwd, timeout, signal) {
45
+ return new Promise((resolvePromise, reject) => {
46
+ const detached = process.platform !== 'win32';
47
+ const child = spawn(command, {
48
+ cwd,
49
+ shell: true,
50
+ detached,
51
+ env: { ...process.env, TERM: 'dumb', NO_COLOR: '1', FORCE_COLOR: '0' },
52
+ stdio: ['ignore', 'pipe', 'pipe']
53
+ });
54
+ let stdout = '';
55
+ let stderr = '';
56
+ let timedOut = false;
57
+ let settled = false;
58
+ let truncationKilled = false;
59
+ const kill = (sig) => {
60
+ try {
61
+ if (detached && child.pid)
62
+ process.kill(-child.pid, sig);
63
+ else
64
+ child.kill(sig);
65
+ }
66
+ catch {
67
+ child.kill(sig);
68
+ }
69
+ };
70
+ const finish = (code) => {
71
+ if (settled)
72
+ return;
73
+ settled = true;
74
+ clearTimeout(timer);
75
+ signal.removeEventListener('abort', onAbort);
76
+ resolvePromise({ stdout, stderr, code, timedOut });
77
+ };
78
+ const timer = setTimeout(() => {
79
+ timedOut = true;
80
+ kill('SIGTERM');
81
+ setTimeout(() => kill('SIGKILL'), 800).unref();
82
+ }, timeout);
83
+ const onAbort = () => {
84
+ kill('SIGTERM');
85
+ setTimeout(() => kill('SIGKILL'), 300).unref();
86
+ };
87
+ signal.addEventListener('abort', onAbort, { once: true });
88
+ child.stdout.on('data', (chunk) => {
89
+ if (stdout.length < 200_000)
90
+ stdout += chunk.toString('utf8');
91
+ else if (!truncationKilled) {
92
+ truncationKilled = true;
93
+ stderr += '\n(process output exceeded 200 KB and was stopped)';
94
+ kill('SIGTERM');
95
+ }
96
+ });
97
+ child.stderr.on('data', (chunk) => {
98
+ if (stderr.length < 100_000)
99
+ stderr += chunk.toString('utf8');
100
+ });
101
+ child.on('error', (error) => {
102
+ if (settled)
103
+ return;
104
+ settled = true;
105
+ clearTimeout(timer);
106
+ signal.removeEventListener('abort', onAbort);
107
+ reject(new SurfError(`Could not run command: ${error.message}`));
108
+ });
109
+ child.on('close', (code) => {
110
+ if (signal.aborted && !timedOut) {
111
+ finish(code);
112
+ return;
113
+ }
114
+ finish(code);
115
+ });
116
+ });
117
+ }
118
+ //# sourceMappingURL=shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/tools/shell.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { SurfError } from '../core/errors.js';
2
+ export const todoTool = {
3
+ name: 'todo_write',
4
+ description: 'Maintain a short task list for multi-step work. Send the full list each time. Keep exactly one item in_progress when actively working. Use this for complex tasks; skip it for simple edits.',
5
+ readOnly: true,
6
+ action: 'read',
7
+ parameters: {
8
+ type: 'object',
9
+ properties: {
10
+ todos: {
11
+ type: 'array',
12
+ description: 'The complete todo list.',
13
+ items: {
14
+ type: 'object',
15
+ properties: {
16
+ content: { type: 'string', description: 'Task description.' },
17
+ status: { type: 'string', enum: ['pending', 'in_progress', 'completed'] }
18
+ },
19
+ required: ['content', 'status']
20
+ }
21
+ }
22
+ },
23
+ required: ['todos']
24
+ },
25
+ async execute(args, context) {
26
+ if (!Array.isArray(args.todos))
27
+ throw new SurfError('todos must be an array.');
28
+ const todos = args.todos.map((item) => {
29
+ const content = String(item?.content ?? '').trim();
30
+ const status = item?.status;
31
+ if (!content)
32
+ throw new SurfError('Every todo needs content.');
33
+ if (!['pending', 'in_progress', 'completed'].includes(status)) {
34
+ throw new SurfError(`Invalid todo status: ${String(status)}`);
35
+ }
36
+ return { content, status };
37
+ });
38
+ context.setTodos?.(todos);
39
+ const marks = { pending: '○', in_progress: '◐', completed: '●' };
40
+ const lines = todos.map((todo) => `${marks[todo.status]} ${todo.content}`);
41
+ return {
42
+ output: todos.length === 0
43
+ ? 'Todo list cleared.'
44
+ : `${todos.filter((todo) => todo.status === 'completed').length}/${todos.length} complete\n${lines.join('\n')}`,
45
+ title: 'Plan',
46
+ display: { kind: 'todos', summary: `${todos.filter((todo) => todo.status === 'completed').length}/${todos.length}`, todos }
47
+ };
48
+ }
49
+ };
50
+ //# sourceMappingURL=todos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todos.js","sourceRoot":"","sources":["../../src/tools/todos.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,IAAI,EAAE,YAAY;IAClB,WAAW,EACT,8LAA8L;IAChM,QAAQ,EAAE,IAAI;IACd,MAAM,EAAE,MAAM;IACd,UAAU,EAAE;QACV,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,KAAK,EAAE;gBACL,IAAI,EAAE,OAAO;gBACb,WAAW,EAAE,yBAAyB;gBACtC,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,mBAAmB,EAAE;wBAC7D,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,WAAW,CAAC,EAAE;qBAC1E;oBACD,QAAQ,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC;iBAChC;aACF;SACF;QACD,QAAQ,EAAE,CAAC,OAAO,CAAC;KACpB;IACD,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO;QACzB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,CAAC;QAC/E,MAAM,KAAK,GAAe,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE;YACrD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YACnD,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC;YAC5B,IAAI,CAAC,OAAO;gBAAE,MAAM,IAAI,SAAS,CAAC,2BAA2B,CAAC,CAAC;YAC/D,IAAI,CAAC,CAAC,SAAS,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC9D,MAAM,IAAI,SAAS,CAAC,wBAAwB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAChE,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAc,CAAC;QACzC,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC;QAC1B,MAAM,KAAK,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAW,CAAC;QAC1E,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3E,OAAO;YACL,MAAM,EACJ,KAAK,CAAC,MAAM,KAAK,CAAC;gBAChB,CAAC,CAAC,oBAAoB;gBACtB,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,cAAc,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACnH,KAAK,EAAE,MAAM;YACb,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE;SAC5H,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,108 @@
1
+ import { convert } from 'html-to-text';
2
+ import { SurfError } from '../core/errors.js';
3
+ import { clip } from '../core/workspace.js';
4
+ const PRIVATE_HOST = /^(localhost|127\.|0\.0\.0\.0|10\.|192\.168\.|169\.254\.|172\.(1[6-9]|2\d|3[01])\.|\[?::1\]?)/i;
5
+ export const webfetchTool = {
6
+ name: 'webfetch',
7
+ description: 'Fetch a public http(s) URL and return readable text. Useful for documentation, APIs and error references. Private/local network addresses are blocked.',
8
+ readOnly: true,
9
+ action: 'fetch',
10
+ parameters: {
11
+ type: 'object',
12
+ properties: {
13
+ url: { type: 'string', description: 'Absolute http(s) URL.' },
14
+ format: { type: 'string', enum: ['text', 'markdown', 'html'], description: 'Preferred output format. Default text.' }
15
+ },
16
+ required: ['url']
17
+ },
18
+ async execute(args, context) {
19
+ const raw = String(args.url ?? '').trim();
20
+ let url;
21
+ try {
22
+ url = new URL(raw);
23
+ }
24
+ catch {
25
+ throw new SurfError(`Invalid URL: ${raw}`);
26
+ }
27
+ if (!['http:', 'https:'].includes(url.protocol))
28
+ throw new SurfError('Only http and https URLs are supported.');
29
+ if (PRIVATE_HOST.test(url.hostname))
30
+ throw new SurfError('Private and local network URLs are blocked.');
31
+ const response = await fetch(url, {
32
+ headers: {
33
+ 'user-agent': 'Mozilla/5.0 (compatible; SurfCode/0.1; +https://surfcode.dev)',
34
+ accept: 'text/html,application/xhtml+xml,application/json,text/plain;q=0.9,*/*;q=0.5'
35
+ },
36
+ redirect: 'follow',
37
+ signal: AbortSignal.any([context.signal, AbortSignal.timeout(30_000)])
38
+ });
39
+ if (!response.ok)
40
+ throw new SurfError(`Fetch failed: HTTP ${response.status} ${response.statusText}`);
41
+ const contentType = response.headers.get('content-type') ?? '';
42
+ const reader = response.body?.getReader();
43
+ if (!reader)
44
+ throw new SurfError('Fetch returned an empty response.');
45
+ const chunks = [];
46
+ let bytes = 0;
47
+ while (true) {
48
+ const { done, value } = await reader.read();
49
+ if (done)
50
+ break;
51
+ bytes += value.byteLength;
52
+ if (bytes > 2_000_000) {
53
+ await reader.cancel();
54
+ break;
55
+ }
56
+ chunks.push(value);
57
+ }
58
+ const rawBody = Buffer.concat(chunks).toString('utf8');
59
+ const requested = String(args.format ?? 'text');
60
+ let body;
61
+ if (contentType.includes('text/html') || /^\s*<!doctype html/i.test(rawBody)) {
62
+ body =
63
+ requested === 'html'
64
+ ? rawBody
65
+ : convert(rawBody, {
66
+ wordwrap: false,
67
+ selectors: [
68
+ { selector: 'a', options: { hideEmpty: false, ignoreHref: false } },
69
+ { selector: 'img', format: 'skip' },
70
+ { selector: 'script', format: 'skip' },
71
+ { selector: 'style', format: 'skip' },
72
+ { selector: 'nav', format: 'skip' },
73
+ { selector: 'footer', format: 'skip' }
74
+ ]
75
+ });
76
+ }
77
+ else if (contentType.includes('application/json')) {
78
+ try {
79
+ body = JSON.stringify(JSON.parse(rawBody), null, 2);
80
+ }
81
+ catch {
82
+ body = rawBody;
83
+ }
84
+ }
85
+ else {
86
+ body = rawBody;
87
+ }
88
+ const clipped = clip(body.replace(/\n{4,}/g, '\n\n\n').trim(), 50_000);
89
+ const title = extractTitle(rawBody) ?? url.hostname;
90
+ return {
91
+ output: `${url.toString()}\n\n${clipped.text}`,
92
+ title,
93
+ display: {
94
+ kind: 'fetch',
95
+ summary: `${url.hostname} · ${(bytes / 1024).toFixed(0)} KB`,
96
+ truncated: clipped.truncated,
97
+ lines: body.split('\n').length
98
+ }
99
+ };
100
+ }
101
+ };
102
+ function extractTitle(html) {
103
+ const match = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
104
+ if (!match?.[1])
105
+ return undefined;
106
+ return match[1].replace(/\s+/g, ' ').trim().slice(0, 140);
107
+ }
108
+ //# sourceMappingURL=webfetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webfetch.js","sourceRoot":"","sources":["../../src/tools/webfetch.ts"],"names":[],"mappings":"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"}